mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-28 09:59:38 -06:00
Compare commits
295 Commits
1.2.49
...
Canary-1.2
Author | SHA1 | Date | |
---|---|---|---|
978d2c132b | |||
5d63706cea | |||
732aafd3bb | |||
3fa714bb72 | |||
7c01633f13 | |||
27c5cba10b | |||
3525d5ecd4 | |||
6286501550 | |||
61ae427a4d | |||
19d2883a35 | |||
617c03119f | |||
e43d899e1d | |||
0cd09ea0c5 | |||
4135d74e4d | |||
bd29f658b1 | |||
df150f0788 | |||
e50198b37d | |||
f426945fec | |||
172869bfba | |||
b6f88514f9 | |||
e92f52e56c | |||
318498eab0 | |||
a5cde8e006 | |||
d0a344d632 | |||
ca66298817 | |||
9ae1c4380d | |||
c88518bce2 | |||
a3888ed7cf | |||
7cbbd02973 | |||
b2e1e553e4 | |||
699e1962b1 | |||
e486b902b1 | |||
0ab5b41c4b | |||
d10a478cce | |||
ec1020b165 | |||
4082ebad1a | |||
da8ea06074 | |||
7f9dccb293 | |||
8e4a77aba0 | |||
8fd8a776c9 | |||
eec92c242c | |||
42a739d34c | |||
f362bef43d | |||
f5ce539de9 | |||
4f699afe7a | |||
6caab1aa37 | |||
9baaa2b8f8 | |||
7f376b4f45 | |||
32cdccde12 | |||
cbd851d00e | |||
f463ea1c5d | |||
1dd69912b1 | |||
1fbb0d8e7d | |||
9ee3f1ff36 | |||
d052d74ac4 | |||
df91c4c57a | |||
2aaaa7872f | |||
b5999583d6 | |||
8b3a945b5f | |||
09107b67ff | |||
12b264af44 | |||
0c21b07f19 | |||
18625cf775 | |||
77a9246825 | |||
709eeda94a | |||
7d54424048 | |||
664c63c6a8 | |||
153d1ef06b | |||
e1e4e5d2d5 | |||
44ee4190e6 | |||
9408452f93 | |||
38833ff60a | |||
1faa72f22f | |||
56e45ae648 | |||
07074272ca | |||
9eb273a0f7 | |||
ccddaa77d1 | |||
01c2e67334 | |||
4c646721d6 | |||
d3bc3a1081 | |||
c69881a0a2 | |||
1bc0159139 | |||
0733b7d0a1 | |||
9df1366fa1 | |||
c73b5bdf46 | |||
9754d247b5 | |||
267e9f6350 | |||
d7b3dd12d1 | |||
c9b2a6b1f1 | |||
17233d30da | |||
2bf48f57d2 | |||
412d4065b8 | |||
e6644626fc | |||
0bacdb8765 | |||
0ca4d6e921 | |||
f0aa7eedf6 | |||
41acc4b1f3 | |||
7aede70ba9 | |||
a0a4f78cff | |||
16a60fdf12 | |||
4d7350fc6e | |||
b05eab21a2 | |||
ff667a5c84 | |||
2f540dc88c | |||
3cb996bf5c | |||
852823104f | |||
3094df54dd | |||
278fe9d4f0 | |||
a560d2efdb | |||
a270dc721c | |||
23b0b22400 | |||
3dfbf55611 | |||
cb355f504d | |||
b5483d8fe0 | |||
8259f790d7 | |||
1ea345faa7 | |||
5913ceda40 | |||
decd37ce6d | |||
67ec10feea | |||
4c7cb54ec6 | |||
f898a5ecf4 | |||
2fac0f4db1 | |||
0f18df982f | |||
d9fe0da345 | |||
1f0fa525a3 | |||
e15a207656 | |||
77ef82d92a | |||
ba199f4325 | |||
4171913baf | |||
5b36a9cf9f | |||
a460eda195 | |||
c77c1acd08 | |||
d68295e57d | |||
bb3d95722e | |||
2fa3a7bfa1 | |||
381921390a | |||
221524d879 | |||
9cddf3b66b | |||
0adaa4cb96 | |||
ff6628149d | |||
8db5a7e98b | |||
8e00cb5232 | |||
362f62cd39 | |||
2cd54d0da0 | |||
b97b8ca8f5 | |||
d7d4225e0d | |||
a9e0fac9dc | |||
1b9656e960 | |||
8994e7476c | |||
b3944a18b7 | |||
18c957f90b | |||
1a005f96e7 | |||
072cd2824a | |||
9da97bc911 | |||
39252b7267 | |||
ec11bf2af9 | |||
8ae72c1a00 | |||
06abba25c1 | |||
de00a71690 | |||
315a1819c0 | |||
4ffb8aef12 | |||
290a6ad5de | |||
eda4f4349b | |||
5fbcb1f3a7 | |||
d00754477e | |||
0bc1eddaeb | |||
baad1e313f | |||
a1e6d11dcb | |||
3d168a8bfa | |||
000c1756de | |||
1d0152b961 | |||
07690e4527 | |||
08b7257be5 | |||
17483aad24 | |||
6b5cb151c3 | |||
3680df6092 | |||
facc12a94a | |||
8e55e6d6d7 | |||
346dfe9542 | |||
8a2b56cae6 | |||
baf179efdb | |||
2a72fb2088 | |||
0caeab2270 | |||
f72d2c1b2b | |||
a18cecbc30 | |||
2e6794e69b | |||
7e16fccfc1 | |||
a81212bbf1 | |||
e8d3ad4d8b | |||
3b6731a351 | |||
e653848a2c | |||
5534001152 | |||
e05875a079 | |||
49eeb26b6f | |||
f8d63f9a2f | |||
e2b7738465 | |||
1d42c29335 | |||
c2de5cc700 | |||
aaaf60b7a4 | |||
150e06e0de | |||
c0a4d95c5d | |||
c3831428e0 | |||
fda79efed4 | |||
8444e4dca0 | |||
008d908c5a | |||
722953211d | |||
df5002bdbf | |||
f4b757c584 | |||
25d69079cb | |||
2e1ede5348 | |||
52f42d450f | |||
11416e2167 | |||
e5d076a1b2 | |||
d394dd769a | |||
6de3afc43d | |||
9b90e81817 | |||
1e53a17041 | |||
0c23104792 | |||
1ed2aea029 | |||
34caa03385 | |||
104701e80d | |||
cef88febb2 | |||
5fccfb76b9 | |||
4cb5946be4 | |||
e1dfb48e23 | |||
6d8738c048 | |||
abfcfcaf0f | |||
d404a8b05b | |||
42cbe24bb1 | |||
79ba9d1258 | |||
826ffd4a04 | |||
7369079459 | |||
a506d81989 | |||
15c20920b3 | |||
285ee276b6 | |||
617b81e209 | |||
eb6ce7bcb3 | |||
69f75f2df1 | |||
10c8d73b60 | |||
e01a30016e | |||
e26625dfd5 | |||
9c82d98ec4 | |||
4aae82bad1 | |||
299be822c4 | |||
b17e4f79fb | |||
a7b58df3fe | |||
8c2d6192ba | |||
2a23000fed | |||
ab7d0a2e6d | |||
bd2681b2f9 | |||
640d7f9e77 | |||
02e8278438 | |||
6acd86c890 | |||
708256ce96 | |||
5bf50836e1 | |||
730ba44043 | |||
36c374cc7a | |||
75f714488e | |||
4831965404 | |||
47b8145809 | |||
20cc21add6 | |||
683baec1af | |||
f4957d2a09 | |||
3e1182af22 | |||
6664ed1b11 | |||
0c88b9eff7 | |||
8a064bcd7e | |||
5ff962be37 | |||
d9c8b7d937 | |||
feeeafe8fe | |||
04f014c777 | |||
4a677deb50 | |||
1c07bf3dd0 | |||
4c83794254 | |||
6911e288bc | |||
67ab54e2bb | |||
139c195eb7 | |||
9305d171e7 | |||
fb4ab5ea08 | |||
d7e17abade | |||
bdb92224f9 | |||
b21740c931 | |||
4f06c343a4 | |||
6c6f18509b | |||
70b7c4c1c3 | |||
7764a74a6d | |||
5de2c4f292 | |||
5845787325 | |||
9c94db1130 | |||
1c3347c95a | |||
b70580bc9f | |||
4926df42a4 | |||
7038a902c3 | |||
6be8838043 | |||
033ea86c1b |
10
.github/labeler.yml
vendored
10
.github/labeler.yml
vendored
@ -32,4 +32,12 @@ kernel:
|
||||
|
||||
infra:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
|
||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
|
||||
|
||||
documentation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'docs/**'
|
||||
|
||||
ldn:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'
|
||||
|
25
.github/workflows/build.yml
vendored
25
.github/workflows/build.yml
vendored
@ -61,17 +61,12 @@ jobs:
|
||||
if: matrix.platform.name != 'linux-arm64'
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
|
||||
- name: Build AppImage
|
||||
@ -119,13 +114,6 @@ jobs:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
|
||||
path: publish_appimage
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
runs-on: ubuntu-latest
|
||||
@ -171,20 +159,9 @@ jobs:
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_headless/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
|
265
.github/workflows/canary.yml
vendored
Normal file
265
.github/workflows/canary.yml
vendored
Normal file
@ -0,0 +1,265 @@
|
||||
name: Canary release job
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- 'docs/**'
|
||||
- 'assets/**'
|
||||
- '*.yml'
|
||||
- '*.json'
|
||||
- '*.config'
|
||||
- '*.md'
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.2"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Canary-Releases"
|
||||
RELEASE: 1
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
|
||||
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}",
|
||||
ref: 'refs/tags/Canary-${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||
tag: ${{ 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
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
release:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Overwrite csc problem matcher
|
||||
run: echo "::add-matcher::.github/csc.json"
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
run: "mkdir release_output"
|
||||
|
||||
- name: Publish
|
||||
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
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish_ava
|
||||
rm publish/libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_ava
|
||||
rm publish/libarmeilleure-jitsupport.dylib
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
#- name: Build AppImage (Linux)
|
||||
# if: matrix.platform.os == 'ubuntu-latest'
|
||||
# run: |
|
||||
# BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||
# PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||
|
||||
# sudo apt install -y zsync desktop-file-utils appstream
|
||||
|
||||
# mkdir -p tools
|
||||
# export PATH="$PATH:$(readlink -f tools)"
|
||||
|
||||
# Setup appimagetool
|
||||
# wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
# chmod +x tools/appimagetool
|
||||
# chmod +x distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||
# if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||
# ARCH_NAME=x64
|
||||
# export ARCH=x86_64
|
||||
# elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||
# ARCH_NAME=arm64
|
||||
# export ARCH=aarch64
|
||||
# else
|
||||
# echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
# BUILDDIR=publish_ava OUTDIR=publish_ava_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Add to release output
|
||||
# pushd publish_ava_appimage
|
||||
# mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
# mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
# popd
|
||||
# shell: bash
|
||||
|
||||
- name: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "release_output/*.tar.gz,release_output/*.zip"
|
||||
#artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
||||
tag: ${{ 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
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 15
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 15
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
mkdir -p $HOME/.bin
|
||||
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
|
||||
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
|
||||
rm apple-codesign.tar.gz
|
||||
mv rcodesign $HOME/.bin/
|
||||
echo "$HOME/.bin" >> $GITHUB_PATH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
|
||||
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
|
||||
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Configure for release
|
||||
run: |
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
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 1
|
||||
|
||||
- name: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||
artifacts: "publish_ava/*.tar.gz"
|
||||
tag: ${{ steps.version_info.outputs.build_version }}
|
||||
body: ""
|
||||
omitBodyDuringUpdate: true
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
12
.github/workflows/nightly_pr_comment.yml
vendored
12
.github/workflows/nightly_pr_comment.yml
vendored
@ -37,21 +37,17 @@ jobs:
|
||||
if (!artifacts.length) {
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
let body = `Download the artifacts for this pull request:\n`;
|
||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||
let body = `*You need to be logged into GitHub to download these files.*\n\nDownload the artifacts for this pull request:\n`;
|
||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
const url = `https://github.com/Ryubing/Ryujinx/actions/runs/${run_id}/artifacts/${art.id}`;
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
hidden_debug_artifacts += `\n* [${art.name}](${url})`;
|
||||
} else {
|
||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
body += `\n* [${art.name}](${url})`;
|
||||
}
|
||||
}
|
||||
hidden_headless_artifacts += `\n</details>`;
|
||||
hidden_debug_artifacts += `\n</details>`;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
|
||||
|
86
.github/workflows/release.yml
vendored
86
.github/workflows/release.yml
vendored
@ -4,7 +4,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [ release ]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- 'docs/**'
|
||||
@ -20,8 +20,8 @@ env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.2"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryubing"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
|
||||
RELEASE: 1
|
||||
|
||||
@ -42,8 +42,8 @@ jobs:
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
owner: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}",
|
||||
repo: "${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}",
|
||||
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||
sha: context.sha
|
||||
})
|
||||
@ -53,7 +53,16 @@ jobs:
|
||||
with:
|
||||
name: ${{ 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: |
|
||||
# Stable builds:
|
||||
| Platform | Artifact |
|
||||
|--|--|
|
||||
| Windows 64-bit | [Stable 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 | [Stable 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 | [Stable 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 | [Stable 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
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
@ -93,6 +102,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
@ -101,32 +111,14 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
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 true
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||
|
||||
- name: Packing Windows builds
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish_ava
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_ava
|
||||
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
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
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
|
||||
pushd publish
|
||||
rm libarmeilleure-jitsupport.dylib
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
@ -159,22 +151,39 @@ jobs:
|
||||
fi
|
||||
|
||||
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||
BUILDDIR=publish_ava OUTDIR=publish_ava_appimage distribution/linux/appimage/build-appimage.sh
|
||||
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||
|
||||
# Add to release output
|
||||
pushd publish_ava_appimage
|
||||
pushd publish_appimage
|
||||
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish
|
||||
chmod +x Ryujinx.sh Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
|
||||
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
|
||||
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: |
|
||||
# Stable builds:
|
||||
| Platform | Artifact |
|
||||
|--|--|
|
||||
| Windows 64-bit | [Stable 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 | [Stable 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 | [Stable 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 | [Stable 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
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
@ -223,24 +232,21 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx
|
||||
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
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
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_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: Pushing new release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ steps.version_info.outputs.build_version }}
|
||||
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
||||
artifacts: "publish/*.tar.gz"
|
||||
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: ""
|
||||
omitBodyDuringUpdate: true
|
||||
allowUpdates: true
|
||||
replacesArtifacts: true
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -175,3 +175,6 @@ PublishProfiles/
|
||||
|
||||
# Glade backup files
|
||||
*.glade~
|
||||
|
||||
# Ignore MacOS Attribute Files
|
||||
._*
|
||||
|
23
COMPILING.md
Normal file
23
COMPILING.md
Normal file
@ -0,0 +1,23 @@
|
||||
## Compilation
|
||||
|
||||
Building the project is for users that want to contribute code only.
|
||||
If you wish to build the emulator yourself, follow these steps:
|
||||
|
||||
### Step 1
|
||||
|
||||
Install the [.NET 9.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/9.0).
|
||||
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
|
||||
|
||||
### Step 2
|
||||
|
||||
Either use `git clone https://github.com/GreemDev/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
||||
|
||||
### Step 3
|
||||
|
||||
To build Ryujinx, open a command prompt inside the project directory.
|
||||
You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`.
|
||||
Then type the following command: `dotnet build -c Release -o build`
|
||||
the built files will be found in the newly created build directory.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder.
|
||||
This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
@ -12,24 +12,15 @@ Please read the entire document before continuing as it can potentially save eve
|
||||
|
||||
We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible.
|
||||
|
||||
### Identify Where to Report
|
||||
|
||||
The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos:
|
||||
|
||||
* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files.
|
||||
* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list.
|
||||
* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code.
|
||||
* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code.
|
||||
|
||||
### Finding Existing Issues
|
||||
|
||||
Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists.
|
||||
Before filing a new issue, please search our [open issues](https://github.com/GreemDev/Ryujinx/issues) to check if it already exists.
|
||||
|
||||
If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog.
|
||||
|
||||
### Writing a Good Feature Request
|
||||
|
||||
Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D).
|
||||
Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/GreemDev/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D).
|
||||
|
||||
### Writing a Good Bug Report
|
||||
|
||||
@ -43,13 +34,13 @@ Ideally, a bug report should contain the following information:
|
||||
* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically.
|
||||
* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds?
|
||||
|
||||
When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D).
|
||||
When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/GreemDev/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D).
|
||||
|
||||
## Contributing Changes
|
||||
|
||||
Project maintainers will merge changes that both improve the project and meet our standards for code quality.
|
||||
|
||||
The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance.
|
||||
The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/GreemDev/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance.
|
||||
|
||||
### DOs and DON'Ts
|
||||
|
||||
@ -83,23 +74,23 @@ We use and recommend the following workflow:
|
||||
3. In your fork, create a branch off of main (`git checkout -b mybranch`).
|
||||
- Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork.
|
||||
4. Make and commit your changes to your branch.
|
||||
- [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test.
|
||||
- [Build Instructions](https://github.com/GreemDev/Ryujinx/blob/master/COMPILING.md) explains how to build and test.
|
||||
- Commit messages should be clear statements of action and intent.
|
||||
6. Build the repository with your changes.
|
||||
- Make sure that the builds are clean.
|
||||
- Make sure that `dotnet format` has been run and any corrections tested and committed.
|
||||
7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch.
|
||||
- State in the description what issue or improvement your change is addressing.
|
||||
- Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors.
|
||||
8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers)
|
||||
- Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md).
|
||||
- Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/GreemDev/Ryujinx/actions) to check for outstanding errors.
|
||||
8. Wait for feedback or approval of your changes from the core development team
|
||||
- Details about the pull request [review procedure](docs/workflow/pr-guide.md).
|
||||
9. When the team members have signed off, and all checks are green, your PR will be merged.
|
||||
- The next official build will automatically include your change.
|
||||
- You can delete the branch you used for making the change.
|
||||
|
||||
### Good First Issues
|
||||
|
||||
The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase.
|
||||
The team marks the most straightforward issues as [good first issues](https://github.com/GreemDev/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
@ -122,7 +113,7 @@ Also do your best to factor commits appropriately, not too large with unrelated
|
||||
|
||||
### PR - CI Process
|
||||
|
||||
The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change.
|
||||
The [Ryujinx continuous integration](https://github.com/GreemDev/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change.
|
||||
|
||||
If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure:
|
||||
* `dotnet format` has not been run on the PR and has outstanding stylistic issues.
|
||||
@ -143,5 +134,5 @@ Ryujinx uses some implementations and frameworks from other projects. The follow
|
||||
|
||||
- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence).
|
||||
- The license of the file is left in-tact.
|
||||
- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed.
|
||||
- The contribution is correctly attributed in the [3rd party notices](https://github.com/GreemDev/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed.
|
||||
|
||||
|
6
Directory.Build.props
Normal file
6
Directory.Build.props
Normal file
@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
@ -10,10 +10,14 @@
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
|
||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.4.0" />
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.4.0"/>
|
||||
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.4.0"/>
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0"/>
|
||||
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
||||
@ -22,7 +26,7 @@
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.1.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
@ -33,13 +37,15 @@
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<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.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="Gommon" Version="2.6.5" />
|
||||
<PackageVersion Include="Gommon" Version="2.7.0.1" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpMetal" Version="1.0.0-preview21" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
|
||||
@ -47,8 +53,8 @@
|
||||
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="9.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="9.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
93
README.md
93
README.md
@ -1,56 +1,52 @@
|
||||
<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>
|
||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
||||
<br>
|
||||
<b>Ryujinx</b>
|
||||
<br>
|
||||
<sub><sup><b>(REE-YOU-JINX)</b></sup></sub>
|
||||
<br>
|
||||
</h1>
|
||||
[](https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml)
|
||||
[](https://github.com/GreemDev/Ryujinx-Canary/releases/latest)
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p align="center">
|
||||
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.
|
||||
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 />
|
||||
</p>
|
||||
<p align="center">
|
||||
On October 1st 2024, Ryujinx was discontinued as the creator was forced to abandon the project.
|
||||
This fork is intended to be a direct continuation for existing Ryujinx users.
|
||||
Guides and documentation will not be provided at this time, though you can find the old ones on the Internet Archive.
|
||||
<br>
|
||||
This fork is intended to be a QoL uplift for existing Ryujinx users.
|
||||
<br>
|
||||
This is not a Ryujinx revival project. This is not a Phoenix project.
|
||||
<br>
|
||||
Guides and documentation can be found on the <a href="https://github.com/GreemDev/Ryujinx/wiki">Wiki tab</a>.
|
||||
</p>
|
||||
<p align="center">
|
||||
If you would like a version more true to original 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 align="center">
|
||||
<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://crwd.in/ryujinx">
|
||||
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||
alt="">
|
||||
</a>
|
||||
Click below to join the Discord:
|
||||
<br>
|
||||
<a href="https://discord.gg/dHPrkBkkyA">
|
||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||
alt="Discord">
|
||||
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
||||
</p>
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
|
||||
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
|
||||
|
||||
Anyone is free to submit a new game test or update an existing game test entry;
|
||||
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
|
||||
Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||
@ -58,36 +54,21 @@ failing to meet this requirement may result in a poor gameplay experience or une
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch.
|
||||
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
||||
Stable builds are made every so often onto a separate "release" branch that then gets put into the releases you know and love.
|
||||
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||
|
||||
You can find the latest stable release [here](https://github.com/GreemDev/Ryujinx/releases/latest).
|
||||
|
||||
Canary builds are compiled automatically for each commit on the master branch.
|
||||
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
|
||||
These canary builds are only recommended for experienced users.
|
||||
|
||||
You can find the latest canary release [here](https://github.com/GreemDev/Ryujinx-Canary/releases/latest).
|
||||
|
||||
## Documentation
|
||||
|
||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||
|
||||
## Building
|
||||
|
||||
If you wish to build the emulator yourself, follow these steps:
|
||||
|
||||
### Step 1
|
||||
|
||||
Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
|
||||
|
||||
### Step 2
|
||||
|
||||
Either use `git clone https://github.com/GreemDev/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
||||
|
||||
### Step 3
|
||||
|
||||
To build Ryujinx, open a command prompt inside the project directory.
|
||||
You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`.
|
||||
Then type the following command: `dotnet build -c Release -o build`
|
||||
the built files will be found in the newly created build directory.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder.
|
||||
This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Audio**
|
||||
|
46
Ryujinx.sln
46
Ryujinx.sln
@ -29,12 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "s
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}"
|
||||
@ -63,14 +57,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.SDL2.Common", "src\
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL2", "src\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "src\Ryujinx.Headless.SDL2\Ryujinx.Headless.SDL2.csproj", "{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "src\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj", "{D4D09B08-D580-4D69-B886-C35D2853F6C8}"
|
||||
@ -87,6 +77,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal", "src\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj", "{C08931FA-1191-417A-864F-3882D93E683B}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E} = {A602AE97-91A5-4608-8DF1-EBF4ED7A0B9E}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.BuildValidationTasks", "src\Ryujinx.BuildValidationTasks\Ryujinx.BuildValidationTasks.csproj", "{4A89A234-4F19-497D-A576-DDE8CDFC5B22}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Metal.SharpMetalExtensions", "src/Ryujinx.Graphics.Metal.SharpMetalExtensions\Ryujinx.Graphics.Metal.SharpMetalExtensions.csproj", "{81EA598C-DBA1-40B0-8DA4-4796B78F2037}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
.github\workflows\canary.yml = .github\workflows\canary.yml
|
||||
Directory.Packages.props = Directory.Packages.props
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -201,10 +209,6 @@ Global
|
||||
{D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{390DC343-5CB4-4C79-A5DD-E3ED235E4C49}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -213,10 +217,6 @@ Global
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7C1B2721-13DA-4B62-B046-C626605ECCE6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -249,6 +249,16 @@ Global
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C08931FA-1191-417A-864F-3882D93E683B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4A89A234-4F19-497D-A576-DDE8CDFC5B22}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{81EA598C-DBA1-40B0-8DA4-4796B78F2037}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -707,6 +707,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010300",
|
||||
@ -3526,6 +3542,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01400000",
|
||||
@ -4160,6 +4192,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -5848,6 +5896,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -6126,6 +6190,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -8341,6 +8421,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -9020,6 +9116,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000100",
|
||||
@ -9496,6 +9608,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010000",
|
||||
@ -9833,6 +9961,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -14667,6 +14811,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01030000",
|
||||
@ -16119,6 +16279,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01050000",
|
||||
@ -16717,6 +16893,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01070000",
|
||||
@ -19745,6 +19937,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01080000",
|
||||
@ -20503,6 +20711,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010000",
|
||||
@ -21805,6 +22029,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -22340,6 +22580,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01020100",
|
||||
@ -22990,6 +23246,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -23440,6 +23712,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -24660,6 +24948,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01410000",
|
||||
@ -24954,6 +25258,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01060000",
|
||||
@ -25286,6 +25606,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -29114,6 +29450,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010000",
|
||||
@ -32512,6 +32864,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -32928,6 +33296,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000100",
|
||||
@ -34800,6 +35184,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Red Tunic",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01000000",
|
||||
@ -37569,6 +37969,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010100",
|
||||
@ -41293,6 +41709,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Black Cat Clothes",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01020100",
|
||||
@ -45153,6 +45585,22 @@
|
||||
"0100F2C0115B6000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
||||
},
|
||||
{
|
||||
"amiiboUsage": [
|
||||
{
|
||||
"Usage": "Receive the Blue Attire",
|
||||
"write": false
|
||||
},
|
||||
{
|
||||
"Usage": "Receive random materials",
|
||||
"write": false
|
||||
}
|
||||
],
|
||||
"gameID": [
|
||||
"01008CF01BAAC000"
|
||||
],
|
||||
"gameName": "The Legend of Zelda: Echoes of Wisdom"
|
||||
}
|
||||
],
|
||||
"head": "01010000",
|
||||
@ -47896,5 +48344,5 @@
|
||||
"type": "Figure"
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2024-10-01T00:00:25.035619"
|
||||
"lastUpdated": "2024-11-17T15:28:47.035619"
|
||||
}
|
@ -14,7 +14,7 @@ if [ -z "$RYUJINX_BIN" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
||||
COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1"
|
||||
|
||||
if command -v gamemoderun > /dev/null 2>&1; then
|
||||
COMMAND="$COMMAND gamemoderun"
|
||||
|
@ -13,7 +13,7 @@ mkdir -p AppDir/usr/bin
|
||||
|
||||
cp distribution/linux/Ryujinx.desktop AppDir/Ryujinx.desktop
|
||||
cp distribution/linux/appimage/AppRun AppDir/AppRun
|
||||
cp src/Ryujinx.UI.Common/Resources/Logo_Ryujinx.png AppDir/Ryujinx.svg
|
||||
cp distribution/misc/Logo.svg AppDir/Ryujinx.svg
|
||||
|
||||
|
||||
cp -r "$BUILDDIR"/* AppDir/usr/bin/
|
||||
|
@ -40,11 +40,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.1</string>
|
||||
<string>1.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.1.0</string>
|
||||
<string>1.2.0</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
|
Binary file not shown.
@ -46,5 +46,5 @@ then
|
||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
||||
else
|
||||
echo "Usign codesign for ad-hoc signing"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$APP_BUNDLE_DIRECTORY"
|
||||
fi
|
@ -2,8 +2,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 7 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
||||
if [ "$#" -lt 8 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -18,10 +18,11 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||
VERSION=$5
|
||||
SOURCE_REVISION_ID=$6
|
||||
CONFIGURATION=$7
|
||||
EXTRA_ARGS=$8
|
||||
CANARY=$8
|
||||
|
||||
if [ "$VERSION" == "1.1.0" ];
|
||||
then
|
||||
if [ "$CANARY" == "1" ]; 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
|
||||
else
|
||||
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"
|
||||
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"
|
||||
|
||||
if ! [ -x "$(command -v lipo)" ];
|
||||
@ -99,7 +100,7 @@ then
|
||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
||||
else
|
||||
echo "Using codesign for ad-hoc signing"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$UNIVERSAL_APP_BUNDLE"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$UNIVERSAL_APP_BUNDLE"
|
||||
fi
|
||||
|
||||
echo "Creating archive"
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ "$#" -lt 7 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
||||
if [ "$#" -lt 8 ]; then
|
||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@ -18,13 +18,14 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||
VERSION=$5
|
||||
SOURCE_REVISION_ID=$6
|
||||
CONFIGURATION=$7
|
||||
EXTRA_ARGS=$8
|
||||
CANARY=$8
|
||||
|
||||
if [ "$VERSION" == "1.1.0" ];
|
||||
then
|
||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||
if [ "$CANARY" == "1" ]; then
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||
elif [ "$VERSION" == "1.1.0" ]; then
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||
else
|
||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
|
||||
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
|
||||
fi
|
||||
|
||||
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
||||
@ -56,7 +57,7 @@ mkdir -p "$OUTPUT_DIRECTORY"
|
||||
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
||||
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"
|
||||
|
||||
if ! [ -x "$(command -v lipo)" ];
|
||||
@ -95,7 +96,7 @@ else
|
||||
echo "Using codesign for ad-hoc signing"
|
||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE"
|
||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
@ -17,7 +17,7 @@ error_handler() {
|
||||
set the button_pressed to the button returned of the result
|
||||
|
||||
if the button_pressed is \"Open Download Page\" then
|
||||
open location \"https://ryujinx.org/download\"
|
||||
open location \"https://ryujinx.app/download\"
|
||||
end if
|
||||
"""
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 MiB |
BIN
docs/shell.png
BIN
docs/shell.png
Binary file not shown.
Before Width: | Height: | Size: 905 KiB After Width: | Height: | Size: 1.4 MiB |
@ -2,14 +2,14 @@
|
||||
|
||||
## Contributing Rules
|
||||
|
||||
All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team.
|
||||
All contributions to GreemDev/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team.
|
||||
|
||||
To merge pull requests, you must have write permissions in the repository.
|
||||
|
||||
## Quick Code Review Rules
|
||||
|
||||
* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix.
|
||||
* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md).
|
||||
* All changes should follow the existing code style. You can read more about our code style at [docs/coding-style](../coding-guidelines/coding-style.md).
|
||||
* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge.
|
||||
* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request.
|
||||
* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around.
|
||||
@ -24,7 +24,7 @@ If during the code review process a merge conflict occurs, the PR author is resp
|
||||
|
||||
## Pull Request Builds
|
||||
|
||||
When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion.
|
||||
When submitting a PR to the `GreemDev/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/GreemDev/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion.
|
||||
|
||||
## Review Turnaround Times
|
||||
|
||||
@ -42,7 +42,7 @@ Anyone with write access can merge a pull request manually when the following co
|
||||
|
||||
* The PR has been approved by two reviewers and any other objections are addressed.
|
||||
* You can request follow up reviews from the original reviewers if they requested changes.
|
||||
* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR.
|
||||
* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/GreemDev/Ryujinx/actions) tab of your PR.
|
||||
|
||||
Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"version": "9.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<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
|
||||
{
|
||||
unsafe abstract class Allocator : IDisposable
|
||||
public unsafe abstract class Allocator : IDisposable
|
||||
{
|
||||
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
|
||||
{
|
||||
unsafe sealed class NativeAllocator : Allocator
|
||||
public unsafe sealed class NativeAllocator : Allocator
|
||||
{
|
||||
public static NativeAllocator Instance { get; } = new();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using ARMeilleure.Common;
|
||||
using System;
|
||||
|
||||
namespace ARMeilleure.Decoders
|
||||
{
|
||||
@ -149,7 +150,7 @@ namespace ARMeilleure.Decoders
|
||||
return (((long)opCode << 45) >> 48) & ~3;
|
||||
}
|
||||
|
||||
public static bool VectorArgumentsInvalid(bool q, params int[] args)
|
||||
public static bool VectorArgumentsInvalid(bool q, params ReadOnlySpan<int> args)
|
||||
{
|
||||
if (q)
|
||||
{
|
||||
|
@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
Operand hostAddress;
|
||||
|
||||
var table = context.FunctionTable;
|
||||
|
||||
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
|
||||
// onto the dispatch stub.
|
||||
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
|
||||
@ -203,6 +205,30 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
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
|
||||
{
|
||||
hostAddress = !context.HasPtc ?
|
||||
|
@ -49,6 +49,9 @@ namespace ARMeilleure.Instructions
|
||||
case 0b11_011_1101_0000_011:
|
||||
EmitGetTpidrroEl0(context);
|
||||
return;
|
||||
case 0b11_011_1101_0000_101:
|
||||
EmitGetTpidr2El0(context);
|
||||
return;
|
||||
case 0b11_011_1110_0000_000:
|
||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
|
||||
break;
|
||||
@ -84,6 +87,9 @@ namespace ARMeilleure.Instructions
|
||||
case 0b11_011_1101_0000_010:
|
||||
EmitSetTpidrEl0(context);
|
||||
return;
|
||||
case 0b11_011_1101_0000_101:
|
||||
EmitSetTpidr2El0(context);
|
||||
return;
|
||||
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||
@ -274,5 +291,16 @@ namespace ARMeilleure.Instructions
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ namespace ARMeilleure.Instructions
|
||||
return TblOrTbx(dest, vector, bytes, tb0, tb1, tb2, tb3);
|
||||
}
|
||||
|
||||
private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params V128[] tb)
|
||||
private static V128 TblOrTbx(V128 dest, V128 vector, int bytes, params ReadOnlySpan<V128> tb)
|
||||
{
|
||||
byte[] res = new byte[16];
|
||||
|
||||
|
@ -337,7 +337,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Operation Operation(Intrinsic intrin, Operand dest, params Operand[] srcs)
|
||||
public static Operation Operation(Intrinsic intrin, Operand dest, params ReadOnlySpan<Operand> srcs)
|
||||
{
|
||||
Operation result = Make(Instruction.Extended, 0, srcs.Length);
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace ARMeilleure.Signal
|
||||
{
|
||||
public static class NativeSignalHandlerGenerator
|
||||
{
|
||||
public const int MaxTrackedRanges = 8;
|
||||
public const int MaxTrackedRanges = 16;
|
||||
|
||||
private const int StructAddressOffset = 0;
|
||||
private const int StructWriteOffset = 4;
|
||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.State
|
||||
public ulong ExclusiveValueLow;
|
||||
public ulong ExclusiveValueHigh;
|
||||
public int Running;
|
||||
public long Tpidr2El0;
|
||||
}
|
||||
|
||||
private static NativeCtxStorage _dummyStorage = new();
|
||||
@ -176,6 +177,9 @@ namespace ARMeilleure.State
|
||||
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
||||
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 void SetCounter(int value) => GetStorage().Counter = value;
|
||||
|
||||
@ -232,6 +236,11 @@ namespace ARMeilleure.State
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||
}
|
||||
|
||||
public static int GetTpidr2El0Offset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Tpidr2El0);
|
||||
}
|
||||
|
||||
public static int GetCounterOffset()
|
||||
{
|
||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||
|
@ -46,7 +46,7 @@ namespace ARMeilleure.Translation
|
||||
public IMemoryManager Memory { get; }
|
||||
|
||||
public EntryTable<uint> CountTable { get; }
|
||||
public AddressTable<ulong> FunctionTable { get; }
|
||||
public IAddressTable<ulong> FunctionTable { get; }
|
||||
public TranslatorStubs Stubs { get; }
|
||||
|
||||
public ulong EntryAddress { get; }
|
||||
@ -62,7 +62,7 @@ namespace ARMeilleure.Translation
|
||||
public ArmEmitterContext(
|
||||
IMemoryManager memory,
|
||||
EntryTable<uint> countTable,
|
||||
AddressTable<ulong> funcTable,
|
||||
IAddressTable<ulong> funcTable,
|
||||
TranslatorStubs stubs,
|
||||
ulong entryAddress,
|
||||
bool highCq,
|
||||
|
@ -8,6 +8,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
@ -26,7 +27,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
private static readonly List<CacheEntry> _cacheEntries = new();
|
||||
|
||||
private static readonly object _lock = new();
|
||||
private static readonly Lock _lock = new();
|
||||
private static bool _initialized;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
|
@ -559,27 +559,27 @@ namespace ARMeilleure.Translation
|
||||
return dest;
|
||||
}
|
||||
|
||||
public Operand AddIntrinsic(Intrinsic intrin, params Operand[] args)
|
||||
public Operand AddIntrinsic(Intrinsic intrin, params ReadOnlySpan<Operand> args)
|
||||
{
|
||||
return Add(intrin, Local(OperandType.V128), args);
|
||||
}
|
||||
|
||||
public Operand AddIntrinsicInt(Intrinsic intrin, params Operand[] args)
|
||||
public Operand AddIntrinsicInt(Intrinsic intrin, params ReadOnlySpan<Operand> args)
|
||||
{
|
||||
return Add(intrin, Local(OperandType.I32), args);
|
||||
}
|
||||
|
||||
public Operand AddIntrinsicLong(Intrinsic intrin, params Operand[] args)
|
||||
public Operand AddIntrinsicLong(Intrinsic intrin, params ReadOnlySpan<Operand> args)
|
||||
{
|
||||
return Add(intrin, Local(OperandType.I64), args);
|
||||
}
|
||||
|
||||
public void AddIntrinsicNoRet(Intrinsic intrin, params Operand[] args)
|
||||
public void AddIntrinsicNoRet(Intrinsic intrin, params ReadOnlySpan<Operand> args)
|
||||
{
|
||||
Add(intrin, default, args);
|
||||
}
|
||||
|
||||
private Operand Add(Intrinsic intrin, Operand dest, params Operand[] sources)
|
||||
private Operand Add(Intrinsic intrin, Operand dest, params ReadOnlySpan<Operand> sources)
|
||||
{
|
||||
NewNextBlockIfNeeded();
|
||||
|
||||
|
@ -13,6 +13,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Runtime;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\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 BackupDir = "1";
|
||||
@ -40,6 +41,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
||||
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
||||
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 CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
||||
@ -57,7 +59,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private readonly ManualResetEvent _waitEvent;
|
||||
|
||||
private readonly object _lock;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
@ -87,8 +89,6 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
_waitEvent = new ManualResetEvent(true);
|
||||
|
||||
_lock = new object();
|
||||
|
||||
_disposed = false;
|
||||
|
||||
TitleIdText = TitleIdTextDefault;
|
||||
@ -100,7 +100,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
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();
|
||||
|
||||
@ -126,6 +126,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||
_memoryMode = memoryMode;
|
||||
|
||||
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
|
||||
|
||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
||||
|
||||
@ -139,8 +141,8 @@ namespace ARMeilleure.Translation.PTC
|
||||
Directory.CreateDirectory(workPathBackup);
|
||||
}
|
||||
|
||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion);
|
||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
|
||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
|
||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
|
||||
|
||||
PreLoad();
|
||||
Profiler.PreLoad();
|
||||
@ -705,6 +707,10 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
imm = translator.Stubs.DispatchStub;
|
||||
}
|
||||
else if (symbol == FunctionTableSymbol)
|
||||
{
|
||||
imm = translator.FunctionTable.Base;
|
||||
}
|
||||
|
||||
if (imm == null)
|
||||
{
|
||||
@ -848,17 +854,15 @@ namespace ARMeilleure.Translation.PTC
|
||||
}
|
||||
}
|
||||
|
||||
List<Thread> threads = new();
|
||||
|
||||
for (int i = 0; i < degreeOfParallelism; i++)
|
||||
{
|
||||
Thread thread = new(TranslateFuncs)
|
||||
List<Thread> threads = Enumerable.Range(0, degreeOfParallelism)
|
||||
.Select(idx =>
|
||||
new Thread(TranslateFuncs)
|
||||
{
|
||||
IsBackground = true,
|
||||
};
|
||||
|
||||
threads.Add(thread);
|
||||
Name = "Ptc.TranslateThread." + idx
|
||||
}
|
||||
).ToList();
|
||||
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
|
||||
@ -885,6 +889,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
Thread preSaveThread = new(PreSave)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "Ptc.DiskWriter"
|
||||
};
|
||||
preSaveThread.Start();
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using ARMeilleure.State;
|
||||
using Humanizer;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
@ -41,7 +42,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
|
||||
private readonly ManualResetEvent _waitEvent;
|
||||
|
||||
private readonly object _lock;
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
@ -58,15 +59,13 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
_ptc = ptc;
|
||||
|
||||
_timer = new Timer(SaveInterval * 1000d);
|
||||
_timer.Elapsed += PreSave;
|
||||
_timer = new Timer(SaveInterval.Seconds());
|
||||
_timer.Elapsed += TimerElapsed;
|
||||
|
||||
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
|
||||
|
||||
_waitEvent = new ManualResetEvent(true);
|
||||
|
||||
_lock = new object();
|
||||
|
||||
_disposed = false;
|
||||
|
||||
ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
|
||||
@ -74,6 +73,9 @@ namespace ARMeilleure.Translation.PTC
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
private void TimerElapsed(object _, ElapsedEventArgs __)
|
||||
=> new Thread(PreSave) { Name = "Ptc.DiskWriter" }.Start();
|
||||
|
||||
public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
|
||||
{
|
||||
if (IsAddressInStaticCodeRange(address))
|
||||
@ -264,7 +266,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
compressedStream.SetLength(0L);
|
||||
}
|
||||
|
||||
private void PreSave(object source, ElapsedEventArgs e)
|
||||
private void PreSave()
|
||||
{
|
||||
_waitEvent.Reset();
|
||||
|
||||
@ -430,7 +432,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
_timer.Elapsed -= PreSave;
|
||||
_timer.Elapsed -= TimerElapsed;
|
||||
_timer.Dispose();
|
||||
|
||||
Wait();
|
||||
|
@ -22,33 +22,13 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
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 ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||
|
||||
private readonly Ptc _ptc;
|
||||
|
||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||
internal AddressTable<ulong> FunctionTable { get; }
|
||||
internal IAddressTable<ulong> FunctionTable { get; }
|
||||
internal EntryTable<uint> CountTable { get; }
|
||||
internal TranslatorStubs Stubs { get; }
|
||||
internal TranslatorQueue Queue { get; }
|
||||
@ -57,7 +37,7 @@ namespace ARMeilleure.Translation
|
||||
private Thread[] _backgroundTranslationThreads;
|
||||
private volatile int _threadCount;
|
||||
|
||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable<ulong> functionTable)
|
||||
{
|
||||
_allocator = allocator;
|
||||
Memory = memory;
|
||||
@ -72,15 +52,15 @@ namespace ARMeilleure.Translation
|
||||
|
||||
CountTable = new EntryTable<uint>();
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
||||
FunctionTable = functionTable;
|
||||
Stubs = new TranslatorStubs(FunctionTable);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private readonly AddressTable<ulong> _functionTable;
|
||||
private readonly IAddressTable<ulong> _functionTable;
|
||||
private readonly Lazy<nint> _dispatchStub;
|
||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||
@ -86,7 +86,7 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <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>
|
||||
public TranslatorStubs(AddressTable<ulong> functionTable)
|
||||
public TranslatorStubs(IAddressTable<ulong> functionTable)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(functionTable);
|
||||
|
||||
|
@ -41,7 +41,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
|
||||
public OpenALHardwareDeviceDriver()
|
||||
{
|
||||
_device = ALC.OpenDevice("");
|
||||
_device = ALC.OpenDevice(string.Empty);
|
||||
_context = ALC.CreateContext(_device, new ALContextAttributes());
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
|
@ -5,6 +5,7 @@ using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.OpenAL
|
||||
{
|
||||
@ -18,7 +19,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
private ulong _playedSampleCount;
|
||||
private float _volume;
|
||||
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -11,7 +11,7 @@ namespace Ryujinx.Audio
|
||||
/// <summary>
|
||||
/// Lock used to control the waiters registration.
|
||||
/// </summary>
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Events signaled when the driver played audio buffers.
|
||||
|
@ -2,6 +2,7 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.Common
|
||||
{
|
||||
@ -12,7 +13,7 @@ namespace Ryujinx.Audio.Backends.Common
|
||||
{
|
||||
private const int RingBufferAlignment = 2048;
|
||||
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private MemoryOwner<byte> _bufferOwner;
|
||||
private Memory<byte> _buffer;
|
||||
|
@ -9,20 +9,12 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
{
|
||||
public class DummyHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ManualResetEvent _updateRequiredEvent = new(false);
|
||||
private readonly ManualResetEvent _pauseEvent = new(true);
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
public DummyHardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
public float Volume { get; set; } = 1f;
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||
{
|
||||
@ -60,7 +52,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
@ -14,12 +14,12 @@ namespace Ryujinx.Audio.Input
|
||||
/// </summary>
|
||||
public class AudioInputManager : IDisposable
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lock used for session allocation.
|
||||
/// </summary>
|
||||
private readonly object _sessionLock = new();
|
||||
private readonly Lock _sessionLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// The session ids allocation table.
|
||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Audio.Input
|
||||
/// <summary>
|
||||
/// The lock of the parent.
|
||||
/// </summary>
|
||||
private readonly object _parentLock;
|
||||
private readonly Lock _parentLock;
|
||||
|
||||
/// <summary>
|
||||
/// The dispose state.
|
||||
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Input
|
||||
/// <param name="parentLock">The lock of the manager</param>
|
||||
/// <param name="deviceSession">The hardware device session</param>
|
||||
/// <param name="bufferEvent">The buffer release event of the audio input</param>
|
||||
public AudioInputSystem(AudioInputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent)
|
||||
public AudioInputSystem(AudioInputManager manager, Lock parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent)
|
||||
{
|
||||
_manager = manager;
|
||||
_parentLock = parentLock;
|
||||
|
@ -14,12 +14,12 @@ namespace Ryujinx.Audio.Output
|
||||
/// </summary>
|
||||
public class AudioOutputManager : IDisposable
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lock used for session allocation.
|
||||
/// </summary>
|
||||
private readonly object _sessionLock = new();
|
||||
private readonly Lock _sessionLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// The session ids allocation table.
|
||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Audio.Output
|
||||
/// <summary>
|
||||
/// THe lock of the parent.
|
||||
/// </summary>
|
||||
private readonly object _parentLock;
|
||||
private readonly Lock _parentLock;
|
||||
|
||||
/// <summary>
|
||||
/// The dispose state.
|
||||
@ -62,7 +62,7 @@ namespace Ryujinx.Audio.Output
|
||||
/// <param name="parentLock">The lock of the manager</param>
|
||||
/// <param name="deviceSession">The hardware device session</param>
|
||||
/// <param name="bufferEvent">The buffer release event of the audio output</param>
|
||||
public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent)
|
||||
public AudioOutputSystem(AudioOutputManager manager, Lock parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent)
|
||||
{
|
||||
_manager = manager;
|
||||
_parentLock = parentLock;
|
||||
|
@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
|
||||
{
|
||||
if ((uint)index > (uint)coefficients.Length)
|
||||
if ((uint)index >= (uint)coefficients.Length)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");
|
||||
|
||||
|
@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
public class AudioRenderSystem : IDisposable
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private AudioRendererRenderingDevice _renderingDevice;
|
||||
private AudioRendererExecutionMode _executionMode;
|
||||
|
@ -19,12 +19,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// Lock used for session allocation.
|
||||
/// </summary>
|
||||
private readonly object _sessionLock = new();
|
||||
private readonly Lock _sessionLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Lock used to control the <see cref="AudioProcessor"/> running state.
|
||||
/// </summary>
|
||||
private readonly object _audioProcessorLock = new();
|
||||
private readonly Lock _audioProcessorLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// The session ids allocation table.
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
{
|
||||
@ -16,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler
|
||||
/// <summary>
|
||||
/// Global lock of the object.
|
||||
/// </summary>
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
/// <summary>
|
||||
/// The upsamplers instances.
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
7
src/Ryujinx.BuildValidationTasks/IValidationTask.cs
Normal file
7
src/Ryujinx.BuildValidationTasks/IValidationTask.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
public interface IValidationTask
|
||||
{
|
||||
public bool Execute(string projectPath, bool isGitRunner);
|
||||
}
|
||||
}
|
117
src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs
Normal file
117
src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs
Normal file
@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
public class LocalesValidationTask : IValidationTask
|
||||
{
|
||||
public LocalesValidationTask() { }
|
||||
|
||||
public bool Execute(string projectPath, bool isGitRunner)
|
||||
{
|
||||
Console.WriteLine("Running Locale Validation Task...");
|
||||
|
||||
string path = projectPath + "src/Ryujinx/Assets/locales.json";
|
||||
string data;
|
||||
|
||||
using (StreamReader sr = new(path))
|
||||
{
|
||||
data = sr.ReadToEnd();
|
||||
}
|
||||
|
||||
LocalesJson json;
|
||||
|
||||
if (isGitRunner && data.Contains("\r\n"))
|
||||
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, build locally to fix...");
|
||||
|
||||
try
|
||||
{
|
||||
json = JsonSerializer.Deserialize<LocalesJson>(data);
|
||||
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
throw new JsonException(e.Message); //shorter and easier stacktrace
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool encounteredIssue = false;
|
||||
|
||||
for (int i = 0; i < json.Locales.Count; i++)
|
||||
{
|
||||
LocalesEntry locale = json.Locales[i];
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang)))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations.Add(langCode, string.Empty);
|
||||
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"]))
|
||||
{
|
||||
encounteredIssue = true;
|
||||
|
||||
if (!isGitRunner)
|
||||
{
|
||||
locale.Translations[langCode] = string.Empty;
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
|
||||
}
|
||||
}
|
||||
|
||||
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
|
||||
json.Locales[i] = locale;
|
||||
}
|
||||
|
||||
if (isGitRunner && encounteredIssue)
|
||||
throw new JsonException("1 or more locales are invalid!");
|
||||
|
||||
JsonSerializerOptions jsonOptions = new JsonSerializerOptions()
|
||||
{
|
||||
WriteIndented = true,
|
||||
NewLine = "\n",
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(json, jsonOptions);
|
||||
|
||||
using (StreamWriter sw = new(path))
|
||||
{
|
||||
sw.Write(jsonString);
|
||||
}
|
||||
|
||||
Console.WriteLine("Finished Locale Validation Task!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LocalesJson
|
||||
{
|
||||
public List<string> Languages { get; set; }
|
||||
public List<LocalesEntry> Locales { get; set; }
|
||||
}
|
||||
|
||||
struct LocalesEntry
|
||||
{
|
||||
public string ID { get; set; }
|
||||
public Dictionary<string, string> Translations { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
37
src/Ryujinx.BuildValidationTasks/Program.cs
Normal file
37
src/Ryujinx.BuildValidationTasks/Program.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.BuildValidationTasks
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
// Display the number of command line arguments.
|
||||
if (args.Length == 0)
|
||||
throw new ArgumentException("Error: too few arguments!");
|
||||
|
||||
string path = args[0];
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new ArgumentException("Error: path is null or empty!");
|
||||
|
||||
if (!Path.Exists(path))
|
||||
throw new FileLoadException($"path {{{path}}} does not exist!");
|
||||
|
||||
path = Path.GetFullPath(path);
|
||||
|
||||
if (!Directory.GetDirectories(path).Contains($"{path}src"))
|
||||
throw new FileLoadException($"path {{{path}}} is not a valid ryujinx project!");
|
||||
|
||||
bool isGitRunner = path.Contains("runner") || path.Contains("D:\\a\\Ryujinx\\Ryujinx");
|
||||
if (isGitRunner)
|
||||
Console.WriteLine("Is Git Runner!");
|
||||
|
||||
// Run tasks
|
||||
// Pass extra info needed in the task constructors
|
||||
new LocalesValidationTask().Execute(path, isGitRunner);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PostBuildTarget" AfterTargets="AfterBuild">
|
||||
<Message Text="Running Validation Project" Importance="high" />
|
||||
|
||||
<Exec WorkingDirectory="$(ProjectDir)bin\Debug\$(TargetFramework)\"
|
||||
Command="dotnet Ryujinx.BuildValidationTasks.dll "$(ProjectDir)..\..\\""
|
||||
ConsoleToMsBuild="true"
|
||||
Condition="'$(RuntimeIdentifier)' == ''"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
@ -119,7 +119,7 @@ namespace Ryujinx.Common.Configuration
|
||||
|
||||
private static string SetUpLogsDir()
|
||||
{
|
||||
string logDir = "";
|
||||
string logDir = string.Empty;
|
||||
|
||||
if (Mode == LaunchMode.Portable)
|
||||
{
|
||||
@ -148,7 +148,7 @@ namespace Ryujinx.Common.Configuration
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
logDir = string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
@ -179,7 +179,7 @@ namespace Ryujinx.Common.Configuration
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
|
||||
logDir = "";
|
||||
logDir = string.Empty;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(logDir))
|
||||
|
@ -36,6 +36,8 @@ namespace Ryujinx.Common.Configuration
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static float ToFloatY(this AspectRatio aspectRatio)
|
||||
{
|
||||
return aspectRatio switch
|
||||
|
60
src/Ryujinx.Common/Configuration/DirtyHack.cs
Normal file
60
src/Ryujinx.Common/Configuration/DirtyHack.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using Gommon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[Flags]
|
||||
public enum DirtyHack : byte
|
||||
{
|
||||
Xc2MenuSoftlockFix = 1,
|
||||
ShaderTranslationDelay = 2
|
||||
}
|
||||
|
||||
public readonly struct EnabledDirtyHack(DirtyHack hack, int value)
|
||||
{
|
||||
public DirtyHack Hack => hack;
|
||||
public int Value => value;
|
||||
|
||||
|
||||
|
||||
public ulong Pack() => Raw.PackBitFields(PackedFormat);
|
||||
|
||||
public static EnabledDirtyHack Unpack(ulong packedHack)
|
||||
{
|
||||
var unpackedFields = packedHack.UnpackBitFields(PackedFormat);
|
||||
if (unpackedFields is not [var hack, var value])
|
||||
throw new ArgumentException(nameof(packedHack));
|
||||
|
||||
return new EnabledDirtyHack((DirtyHack)hack, (int)value);
|
||||
}
|
||||
|
||||
private uint[] Raw => [(uint)Hack, (uint)Value.CoerceAtLeast(0)];
|
||||
|
||||
public static readonly byte[] PackedFormat = [8, 32];
|
||||
}
|
||||
|
||||
public class DirtyHacks : Dictionary<DirtyHack, int>
|
||||
{
|
||||
public DirtyHacks(IEnumerable<EnabledDirtyHack> hacks)
|
||||
=> hacks.ForEach(edh => Add(edh.Hack, edh.Value));
|
||||
|
||||
public DirtyHacks(ulong[] packedHacks) : this(packedHacks.Select(EnabledDirtyHack.Unpack)) {}
|
||||
|
||||
public ulong[] PackEntries()
|
||||
=> Entries.Select(it => it.Pack()).ToArray();
|
||||
|
||||
public EnabledDirtyHack[] Entries
|
||||
=> this
|
||||
.Select(it => new EnabledDirtyHack(it.Key, it.Value))
|
||||
.ToArray();
|
||||
|
||||
public static implicit operator DirtyHacks(EnabledDirtyHack[] hacks) => new(hacks);
|
||||
public static implicit operator DirtyHacks(ulong[] packedHacks) => new(packedHacks);
|
||||
|
||||
public new int this[DirtyHack hack] => TryGetValue(hack, out var value) ? value : -1;
|
||||
|
||||
public bool IsEnabled(DirtyHack hack) => ContainsKey(hack);
|
||||
}
|
||||
}
|
@ -6,7 +6,9 @@ namespace Ryujinx.Common.Configuration
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Auto,
|
||||
Vulkan,
|
||||
OpenGl,
|
||||
Metal
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
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 bool InvertStickX { get; set; }
|
||||
|
@ -2,7 +2,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class KeyboardHotkeys
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
public Key ToggleVSyncMode { get; set; }
|
||||
public Key Screenshot { get; set; }
|
||||
public Key ShowUI { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
@ -11,5 +11,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
||||
public Key ResScaleDown { get; set; }
|
||||
public Key VolumeUp { get; set; }
|
||||
public Key VolumeDown { get; set; }
|
||||
public Key CustomVSyncIntervalIncrement { get; set; }
|
||||
public Key CustomVSyncIntervalDecrement { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ namespace Ryujinx.Common.Configuration.Multiplayer
|
||||
public enum MultiplayerMode
|
||||
{
|
||||
Disabled,
|
||||
LdnRyu,
|
||||
LdnMitm,
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ namespace Ryujinx.Common.Configuration
|
||||
Bilinear,
|
||||
Nearest,
|
||||
Fsr,
|
||||
Area,
|
||||
}
|
||||
}
|
||||
|
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
|
||||
}
|
||||
}
|
@ -8,10 +8,10 @@ namespace Ryujinx.Common
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <see cref="ReadOnlySpan{int}" /> to this stream.
|
||||
/// Writes an int span to this stream.
|
||||
///
|
||||
/// This default implementation converts each buffer value to a stack-allocated
|
||||
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||
/// byte array, then writes it to the Stream using <see cref="Stream.Write(ReadOnlySpan{byte})" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="buffer">The buffer of values to be written</param>
|
||||
|
@ -121,8 +121,8 @@ namespace Ryujinx.Common.GraphicsDriver
|
||||
};
|
||||
application.AppName.Set("Ryujinx.exe");
|
||||
application.UserFriendlyName.Set("Ryujinx");
|
||||
application.Launcher.Set("");
|
||||
application.FileInFolder.Set("");
|
||||
application.Launcher.Set(string.Empty);
|
||||
application.FileInFolder.Set(string.Empty);
|
||||
|
||||
Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application));
|
||||
}
|
||||
|
@ -3,10 +3,28 @@ using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
public static partial class ConsoleHelper
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32")]
|
||||
private static partial nint GetConsoleWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
private static partial nint GetForegroundWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetForegroundWindow(nint hWnd);
|
||||
|
||||
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
|
||||
|
||||
public static void SetConsoleWindowState(bool show)
|
||||
@ -35,16 +53,11 @@ namespace Ryujinx.UI.Common.Helper
|
||||
return;
|
||||
}
|
||||
|
||||
SetForegroundWindow(hWnd);
|
||||
|
||||
hWnd = GetForegroundWindow();
|
||||
|
||||
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32")]
|
||||
private static partial nint GetConsoleWindow();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("user32")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
|
||||
}
|
||||
}
|
@ -4,10 +4,11 @@ using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
public static partial class FileAssociationHelper
|
||||
{
|
||||
@ -22,7 +23,27 @@ namespace Ryujinx.UI.Common.Helper
|
||||
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||
public static partial void SHChangeNotify(uint wEventId, uint uFlags, nint dwItem1, nint dwItem2);
|
||||
|
||||
public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild;
|
||||
public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows());
|
||||
|
||||
public static bool AreMimeTypesRegistered
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return AreMimeTypesRegisteredLinux();
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return AreMimeTypesRegisteredWindows();
|
||||
}
|
||||
|
||||
// TODO: Add macOS support.
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
|
||||
@ -35,7 +56,7 @@ namespace Ryujinx.UI.Common.Helper
|
||||
if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
|
||||
{
|
||||
string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml");
|
||||
string additionalArgs = !uninstall ? "--novendor" : "";
|
||||
string additionalArgs = !uninstall ? "--novendor" : string.Empty;
|
||||
|
||||
using Process mimeProcess = new();
|
||||
|
||||
@ -72,42 +93,46 @@ namespace Ryujinx.UI.Common.Helper
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static bool AreMimeTypesRegisteredWindows()
|
||||
{
|
||||
return _fileExtensions.Aggregate(false,
|
||||
(current, ext) => current | CheckRegistering(ext)
|
||||
);
|
||||
|
||||
static bool CheckRegistering(string ext)
|
||||
{
|
||||
RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");
|
||||
|
||||
if (key is null)
|
||||
var openCmd = key?.OpenSubKey(@"shell\open\command");
|
||||
|
||||
if (openCmd is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var openCmd = key.OpenSubKey(@"shell\open\command");
|
||||
|
||||
string keyValue = (string)openCmd.GetValue("");
|
||||
string keyValue = (string)openCmd.GetValue(string.Empty);
|
||||
|
||||
return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
|
||||
}
|
||||
|
||||
bool registered = false;
|
||||
|
||||
foreach (string ext in _fileExtensions)
|
||||
{
|
||||
registered |= CheckRegistering(ext);
|
||||
}
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static bool InstallWindowsMimeTypes(bool uninstall = false)
|
||||
{
|
||||
bool registered = _fileExtensions.Aggregate(false,
|
||||
(current, ext) => current | RegisterExtension(ext, uninstall)
|
||||
);
|
||||
|
||||
// Notify Explorer the file association has been changed.
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nint.Zero, nint.Zero);
|
||||
|
||||
return registered;
|
||||
|
||||
static bool RegisterExtension(string ext, bool uninstall = false)
|
||||
{
|
||||
string keyString = @$"Software\Classes\{ext}";
|
||||
|
||||
if (uninstall)
|
||||
{
|
||||
// If the types don't already exist, there's nothing to do and we can call this operation successful.
|
||||
// If the types don't already exist, there's nothing to do, and we can call this operation successful.
|
||||
if (!AreMimeTypesRegisteredWindows())
|
||||
{
|
||||
return true;
|
||||
@ -127,42 +152,13 @@ namespace Ryujinx.UI.Common.Helper
|
||||
|
||||
Logger.Debug?.Print(LogClass.Application, $"Adding type association {ext}");
|
||||
using var openCmd = key.CreateSubKey(@"shell\open\command");
|
||||
openCmd.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
|
||||
openCmd.SetValue(string.Empty, $"\"{Environment.ProcessPath}\" \"%1\"");
|
||||
Logger.Debug?.Print(LogClass.Application, $"Added type association {ext}");
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool registered = false;
|
||||
|
||||
foreach (string ext in _fileExtensions)
|
||||
{
|
||||
registered |= RegisterExtension(ext, uninstall);
|
||||
}
|
||||
|
||||
// Notify Explorer the file association has been changed.
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, nint.Zero, nint.Zero);
|
||||
|
||||
return registered;
|
||||
}
|
||||
|
||||
public static bool AreMimeTypesRegistered()
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return AreMimeTypesRegisteredLinux();
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return AreMimeTypesRegisteredWindows();
|
||||
}
|
||||
|
||||
// TODO: Add macOS support.
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Install()
|
@ -3,7 +3,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static class LinuxHelper
|
@ -2,7 +2,7 @@ using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static partial class ObjectiveC
|
@ -1,10 +1,11 @@
|
||||
using Gommon;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.UI.Common.Helper
|
||||
namespace Ryujinx.Common.Helper
|
||||
{
|
||||
public static partial class OpenHelper
|
||||
{
|
||||
@ -34,6 +35,8 @@ namespace Ryujinx.UI.Common.Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static void OpenFolder(FilePath path) => OpenFolder(path.Path);
|
||||
|
||||
public static void LocateFile(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
@ -72,5 +72,6 @@ namespace Ryujinx.Common.Logging
|
||||
TamperMachine,
|
||||
UI,
|
||||
Vic,
|
||||
XCIFileTrimmer
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@ -38,7 +39,7 @@ namespace Ryujinx.Common.Logging
|
||||
{
|
||||
if (_enabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message)));
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, string.Empty, message)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,20 +159,15 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
|
||||
private static ILogTarget GetTarget(string targetName)
|
||||
{
|
||||
foreach (var target in _logTargets)
|
||||
{
|
||||
if (target.Name.Equals(targetName))
|
||||
{
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
=> _logTargets.FirstOrDefault(target => target.Name.Equals(targetName));
|
||||
|
||||
public static void AddTarget(ILogTarget target)
|
||||
{
|
||||
if (_logTargets.Any(t => t.Name == target.Name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logTargets.Add(target);
|
||||
|
||||
Updated += target.Log;
|
||||
|
@ -27,13 +27,9 @@ namespace Ryujinx.Common.Logging.Targets
|
||||
|
||||
private readonly int _overflowTimeout;
|
||||
|
||||
string ILogTarget.Name { get => _target.Name; }
|
||||
string ILogTarget.Name => _target.Name;
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target)
|
||||
: this(target, -1, AsyncLogTargetOverflowAction.Block)
|
||||
{ }
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
|
||||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit = -1, AsyncLogTargetOverflowAction overflowAction = AsyncLogTargetOverflowAction.Block)
|
||||
{
|
||||
_target = target;
|
||||
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
||||
|
@ -47,7 +47,7 @@ namespace Ryujinx.Common.Logging.Targets
|
||||
}
|
||||
|
||||
// Clean up old logs, should only keep 3
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy(info => info.CreationTime).ToArray();
|
||||
for (int i = 0; i < files.Length - 2; i++)
|
||||
{
|
||||
try
|
||||
@ -69,9 +69,10 @@ namespace Ryujinx.Common.Logging.Targets
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.Version;
|
||||
string appName = ReleaseInformation.IsCanaryBuild ? "Ryujinx_Canary" : "Ryujinx";
|
||||
|
||||
// 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
|
||||
{
|
||||
|
30
src/Ryujinx.Common/Logging/XCIFileTrimmerLog.cs
Normal file
30
src/Ryujinx.Common/Logging/XCIFileTrimmerLog.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class XCIFileTrimmerLog : XCIFileTrimmer.ILog
|
||||
{
|
||||
public virtual void Progress(long current, long total, string text, bool complete)
|
||||
{
|
||||
}
|
||||
|
||||
public void Write(XCIFileTrimmer.LogType logType, string text)
|
||||
{
|
||||
switch (logType)
|
||||
{
|
||||
case XCIFileTrimmer.LogType.Info:
|
||||
Logger.Notice.Print(LogClass.XCIFileTrimmer, text);
|
||||
break;
|
||||
case XCIFileTrimmer.LogType.Warn:
|
||||
Logger.Warning?.Print(LogClass.XCIFileTrimmer, text);
|
||||
break;
|
||||
case XCIFileTrimmer.LogType.Error:
|
||||
Logger.Error?.Print(LogClass.XCIFileTrimmer, text);
|
||||
break;
|
||||
case XCIFileTrimmer.LogType.Progress:
|
||||
Logger.Info?.Print(LogClass.XCIFileTrimmer, text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -803,18 +803,6 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array256<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array128<T> _other;
|
||||
Array127<T> _other2;
|
||||
public readonly int Length => 256;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array140<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
@ -828,6 +816,18 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array256<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array128<T> _other;
|
||||
Array127<T> _other2;
|
||||
public readonly int Length => 256;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array384<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
|
@ -124,7 +124,7 @@ namespace Ryujinx.Common.PreciseSleep
|
||||
}
|
||||
}
|
||||
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
private readonly List<NanosleepThread> _threads = new();
|
||||
private readonly List<NanosleepThread> _active = new();
|
||||
private readonly Stack<NanosleepThread> _free = new();
|
||||
|
@ -50,7 +50,7 @@ namespace Ryujinx.Common.SystemInterop
|
||||
private long _lastTicks = PerformanceCounter.ElapsedTicks;
|
||||
private long _lastId;
|
||||
|
||||
private readonly object _lock = new();
|
||||
private readonly Lock _lock = new();
|
||||
private readonly List<WaitingObject> _waitingObjects = new();
|
||||
|
||||
private WindowsGranularTimer()
|
||||
|
@ -1,11 +1,13 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class ReactiveObject<T>
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _readerWriterLock = new();
|
||||
private readonly ReaderWriterLockSlim _rwLock = new();
|
||||
private bool _isInitialized;
|
||||
private T _value;
|
||||
|
||||
@ -15,15 +17,15 @@ namespace Ryujinx.Common
|
||||
{
|
||||
get
|
||||
{
|
||||
_readerWriterLock.EnterReadLock();
|
||||
_rwLock.EnterReadLock();
|
||||
T value = _value;
|
||||
_readerWriterLock.ExitReadLock();
|
||||
_rwLock.ExitReadLock();
|
||||
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_readerWriterLock.EnterWriteLock();
|
||||
_rwLock.EnterWriteLock();
|
||||
|
||||
T oldValue = _value;
|
||||
|
||||
@ -32,7 +34,7 @@ namespace Ryujinx.Common
|
||||
_isInitialized = true;
|
||||
_value = value;
|
||||
|
||||
_readerWriterLock.ExitWriteLock();
|
||||
_rwLock.ExitWriteLock();
|
||||
|
||||
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
||||
{
|
||||
@ -41,11 +43,24 @@ namespace Ryujinx.Common
|
||||
}
|
||||
}
|
||||
|
||||
public void LogChangesToValue(string valueName, LogClass logClass = LogClass.Configuration)
|
||||
=> Event += (_, e) => ReactiveObjectHelper.LogValueChange(logClass, e, valueName);
|
||||
|
||||
public static implicit operator T(ReactiveObject<T> obj) => obj.Value;
|
||||
}
|
||||
|
||||
public static class ReactiveObjectHelper
|
||||
{
|
||||
public static void LogValueChange<T>(LogClass logClass, ReactiveEventArgs<T> eventArgs, string valueName)
|
||||
{
|
||||
if (eventArgs.AreValuesEqual)
|
||||
return;
|
||||
|
||||
string message = string.Create(CultureInfo.InvariantCulture, $"{valueName} set to: {eventArgs.NewValue}");
|
||||
|
||||
Logger.Info?.Print(logClass, message);
|
||||
}
|
||||
|
||||
public static void Toggle(this ReactiveObject<bool> rBoolean) => rBoolean.Value = !rBoolean.Value;
|
||||
}
|
||||
|
||||
@ -53,5 +68,22 @@ namespace Ryujinx.Common
|
||||
{
|
||||
public T OldValue { get; } = oldValue;
|
||||
public T NewValue { get; } = newValue;
|
||||
|
||||
public bool AreValuesEqual
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OldValue == null && NewValue == null)
|
||||
return true;
|
||||
|
||||
if (OldValue == null && NewValue != null)
|
||||
return false;
|
||||
|
||||
if (OldValue != null && NewValue == null)
|
||||
return false;
|
||||
|
||||
return OldValue!.Equals(NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
@ -5,7 +6,8 @@ namespace Ryujinx.Common
|
||||
// DO NOT EDIT, filled by CI
|
||||
public static class ReleaseInformation
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
private const string CanaryChannel = "canary";
|
||||
private const string ReleaseChannel = "release";
|
||||
|
||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
@ -13,6 +15,7 @@ namespace Ryujinx.Common
|
||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||
|
||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||
public const string ReleaseChannelSourceRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO%%";
|
||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||
|
||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||
@ -21,11 +24,22 @@ namespace Ryujinx.Common
|
||||
!BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelSourceRepo.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||
!ConfigFileName.StartsWith("%%");
|
||||
|
||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
public static bool IsCanaryBuild => IsValid && ReleaseChannelName.Equals(CanaryChannel);
|
||||
|
||||
public static bool IsReleaseBuild => IsValid && ReleaseChannelName.Equals(ReleaseChannel);
|
||||
|
||||
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}";
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
<PackageReference Include="Humanizer" />
|
||||
<PackageReference Include="Gommon" />
|
||||
</ItemGroup>
|
||||
|
||||
|
194
src/Ryujinx.Common/TitleIDs.cs
Normal file
194
src/Ryujinx.Common/TitleIDs.cs
Normal file
@ -0,0 +1,194 @@
|
||||
using Gommon;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class TitleIDs
|
||||
{
|
||||
public static ReactiveObject<Optional<string>> CurrentApplication { get; set; } = new();
|
||||
|
||||
public static GraphicsBackend SelectGraphicsBackend(string titleId, GraphicsBackend currentBackend)
|
||||
{
|
||||
switch (currentBackend)
|
||||
{
|
||||
case GraphicsBackend.OpenGl when OperatingSystem.IsMacOS():
|
||||
return GraphicsBackend.Vulkan;
|
||||
case GraphicsBackend.Vulkan or GraphicsBackend.OpenGl or GraphicsBackend.Metal:
|
||||
return currentBackend;
|
||||
}
|
||||
|
||||
if (!(OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture is Architecture.Arm64))
|
||||
return GraphicsBackend.Vulkan;
|
||||
|
||||
return GreatMetalTitles.ContainsIgnoreCase(titleId) ? GraphicsBackend.Metal : GraphicsBackend.Vulkan;
|
||||
}
|
||||
|
||||
public static readonly string[] GreatMetalTitles =
|
||||
[
|
||||
"01006f8002326000", // Animal Crossings: New Horizons
|
||||
"01009bf0072d4000", // Captain Toad: Treasure Tracker
|
||||
"0100a5c00d162000", // Cuphead
|
||||
"010023800d64a000", // Deltarune
|
||||
"010028600EBDA000", // Mario 3D World
|
||||
"0100152000022000", // Mario Kart 8 Deluxe
|
||||
"01005CA01580E000", // Persona 5
|
||||
"0100187003A36000", // Pokémon: Let's Go, Evoli!
|
||||
"010003f003a34000", // Pokémon: Let's Go, Pikachu!
|
||||
"01008C0016544000", // Sea of Stars
|
||||
"01006A800016E000", // Smash Ultimate
|
||||
"0100000000010000", // Super Mario Odyessy
|
||||
];
|
||||
|
||||
public static string GetDiscordGameAsset(string titleId)
|
||||
=> DiscordGameAssetKeys.Contains(titleId) ? titleId : "game";
|
||||
|
||||
public static readonly string[] DiscordGameAssetKeys =
|
||||
[
|
||||
"010055d009f78000", // Fire Emblem: Three Houses
|
||||
"0100a12011cc8000", // Fire Emblem: Shadow Dragon
|
||||
"0100a6301214e000", // Fire Emblem Engage
|
||||
"0100f15003e64000", // Fire Emblem Warriors
|
||||
"010071f0143ea000", // Fire Emblem Warriors: Three Hopes
|
||||
|
||||
"01007e3006dda000", // Kirby Star Allies
|
||||
"01004d300c5ae000", // Kirby and the Forgotten Land
|
||||
"01006b601380e000", // Kirby's Return to Dream Land Deluxe
|
||||
"01003fb00c5a8000", // Super Kirby Clash
|
||||
"0100227010460000", // Kirby Fighters 2
|
||||
"0100a8e016236000", // Kirby's Dream Buffet
|
||||
|
||||
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
|
||||
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
|
||||
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
||||
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
||||
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
||||
"01000b900d8b0000", // Cadence of Hyrule
|
||||
"0100ae00096ea000", // Hyrule Warriors: Definitive Edition
|
||||
"01002b00111a2000", // Hyrule Warriors: Age of Calamity
|
||||
|
||||
"010048701995e000", // Luigi's Mansion 2 HD
|
||||
"0100dca0064a6000", // Luigi's Mansion 3
|
||||
|
||||
"010093801237c000", // Metroid Dread
|
||||
"010012101468c000", // Metroid Prime Remastered
|
||||
|
||||
"0100000000010000", // SUPER MARIO ODYSSEY
|
||||
"0100ea80032ea000", // Super Mario Bros. U Deluxe
|
||||
"01009b90006dc000", // Super Mario Maker 2
|
||||
"010049900f546000", // Super Mario 3D All-Stars
|
||||
"010049900F546001", // ^ 64
|
||||
"010049900F546002", // ^ Sunshine
|
||||
"010049900F546003", // ^ Galaxy
|
||||
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
||||
"010015100b514000", // Super Mario Bros. Wonder
|
||||
"0100152000022000", // Mario Kart 8 Deluxe
|
||||
"010036b0034e4000", // Super Mario Party
|
||||
"01006fe013472000", // Mario Party Superstars
|
||||
"0100965017338000", // Super Mario Party Jamboree
|
||||
"01006d0017f7a000", // Mario & Luigi: Brothership
|
||||
"010067300059a000", // Mario + Rabbids: Kingdom Battle
|
||||
"0100317013770000", // Mario + Rabbids: Sparks of Hope
|
||||
"0100a3900c3e2000", // Paper Mario: The Origami King
|
||||
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
||||
"0100bc0018138000", // Super Mario RPG
|
||||
"0100bde00862a000", // Mario Tennis Aces
|
||||
"0100c9c00e25c000", // Mario Golf: Super Rush
|
||||
"010019401051c000", // Mario Strikers: Battle League
|
||||
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
|
||||
"0100b99019412000", // Mario vs. Donkey Kong
|
||||
|
||||
"0100aa80194b0000", // Pikmin 1
|
||||
"0100d680194b2000", // Pikmin 2
|
||||
"0100f4c009322000", // Pikmin 3 Deluxe
|
||||
"0100b7c00933a000", // Pikmin 4
|
||||
|
||||
"010003f003a34000", // Pokémon: Let's Go Pikachu!
|
||||
"0100187003a36000", // Pokémon: Let's Go Eevee!
|
||||
"0100abf008968000", // Pokémon Sword
|
||||
"01008db008c2c000", // Pokémon Shield
|
||||
"0100000011d90000", // Pokémon Brilliant Diamond
|
||||
"010018e011d92000", // Pokémon Shining Pearl
|
||||
"01001f5010dfa000", // Pokémon Legends: Arceus
|
||||
"0100a3d008c5c000", // Pokémon Scarlet
|
||||
"01008f6008c5e000", // Pokémon Violet
|
||||
"0100b3f000be2000", // Pokkén Tournament DX
|
||||
"0100f4300bf2c000", // New Pokémon Snap
|
||||
|
||||
"01003bc0000a0000", // Splatoon 2 (US)
|
||||
"0100f8f0000a2000", // Splatoon 2 (EU)
|
||||
"01003c700009c000", // Splatoon 2 (JP)
|
||||
"0100c2500fc20000", // Splatoon 3
|
||||
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
||||
|
||||
"010040600c5ce000", // Tetris 99
|
||||
"0100277011f1a000", // Super Mario Bros. 35
|
||||
"0100ad9012510000", // PAC-MAN 99
|
||||
"0100ccf019c8c000", // F-ZERO 99
|
||||
"0100d870045b6000", // NES - Nintendo Switch Online
|
||||
"01008d300c50c000", // SNES - Nintendo Switch Online
|
||||
"0100c9a00ece6000", // N64 - Nintendo Switch Online
|
||||
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
|
||||
"0100c62011050000", // GB - Nintendo Switch Online
|
||||
"010012f017576000", // GBA - Nintendo Switch Online
|
||||
|
||||
"01000320000cc000", // 1-2 Switch
|
||||
"0100300012f2a000", // Advance Wars 1+2: Re-Boot Camp
|
||||
"01006f8002326000", // Animal Crossing: New Horizons
|
||||
"0100620012d6e000", // Big Brain Academy: Brain vs. Brain
|
||||
"010018300d006000", // BOXBOY! + BOXGIRL!
|
||||
"0100c1f0051b6000", // Donkey Kong Country: Tropical Freeze
|
||||
"0100ed000d390000", // Dr. Kawashima's Brain Training
|
||||
"010067b017588000", // Endless Ocean Luminous
|
||||
"0100d2f00d5c0000", // Nintendo Switch Sports
|
||||
"01006b5012b32000", // Part Time UFO
|
||||
"0100704000B3A000", // Snipperclips
|
||||
"01006a800016e000", // Super Smash Bros. Ultimate
|
||||
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
|
||||
|
||||
"010076f0049a2000", // Bayonetta
|
||||
"01007960049a0000", // Bayonetta 2
|
||||
"01004a4010fea000", // Bayonetta 3
|
||||
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
|
||||
|
||||
"0100dcd01525a000", // Persona 3 Portable
|
||||
"010062b01525c000", // Persona 4 Golden
|
||||
"010075a016a3a000", // Persona 4 Arena Ultimax
|
||||
"01005ca01580e000", // Persona 5 Royal
|
||||
"0100801011c3e000", // Persona 5 Strikers
|
||||
"010087701b092000", // Persona 5 Tactica
|
||||
|
||||
"01009aa000faa000", // Sonic Mania
|
||||
"01004ad014bf0000", // Sonic Frontiers
|
||||
"01005ea01c0fc000", // SONIC X SHADOW GENERATIONS
|
||||
"01005ea01c0fc001", // ^
|
||||
|
||||
"010056e00853a000", // A Hat in Time
|
||||
"0100dbf01000a000", // Burnout Paradise Remastered
|
||||
"0100744001588000", // Cars 3: Driven to Win
|
||||
"0100b41013c82000", // Cruis'n Blast
|
||||
"01001b300b9be000", // Diablo III: Eternal Collection
|
||||
"01008c8012920000", // Dying Light Platinum Edition
|
||||
"010073c01af34000", // LEGO Horizon Adventures
|
||||
"0100770008dd8000", // Monster Hunter Generations Ultimate
|
||||
"0100b04011742000", // Monster Hunter Rise
|
||||
"0100853015e86000", // No Man's Sky
|
||||
"01007bb017812000", // Portal
|
||||
"0100abd01785c000", // Portal 2
|
||||
"01008e200c5c2000", // Muse Dash
|
||||
"01007820196a6000", // Red Dead Redemption
|
||||
"01002f7013224000", // Rune Factory 5
|
||||
"01008d100d43e000", // Saints Row IV
|
||||
"0100de600beee000", // Saints Row: The Third - The Full Package
|
||||
"01001180021fa000", // Shovel Knight: Specter of Torment
|
||||
"0100d7a01b7a2000", // Star Wars: Bounty Hunter
|
||||
"0100800015926000", // Suika Game
|
||||
"0100e46006708000", // Terraria
|
||||
"01000a10041ea000", // The Elder Scrolls V: Skyrim
|
||||
"010057a01e4d4000", // TSUKIHIME -A piece of blue glass moon-
|
||||
"010080b00ad66000", // Undertale
|
||||
];
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Ryujinx.UI.Common
|
||||
namespace Ryujinx.Common.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a common error that could be reported to the user by the emulator.
|
@ -40,5 +40,35 @@ namespace Ryujinx.Common
|
||||
|
||||
return (value >> 32) | (value << 32);
|
||||
}
|
||||
|
||||
// Never actually written bit packing logic before, so I looked it up.
|
||||
// This code is from https://gist.github.com/Alan-FGR/04938e93e2bffdf5802ceb218a37c195
|
||||
|
||||
public static ulong PackBitFields(this uint[] values, byte[] bitFields)
|
||||
{
|
||||
ulong retVal = values[0]; //we set the first value right away
|
||||
for (int f = 1; f < values.Length; f++)
|
||||
{
|
||||
retVal <<= bitFields[f]; // we shift the previous value
|
||||
retVal += values[f];// and add our current value
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static uint[] UnpackBitFields(this ulong packed, byte[] bitFields)
|
||||
{
|
||||
int fields = bitFields.Length - 1; // number of fields to unpack
|
||||
uint[] retArr = new uint[fields + 1]; // init return array
|
||||
int curPos = 0; // current field bit position (start)
|
||||
int lastEnd; // position where last field ended
|
||||
for (int f = fields; f >= 0; f--) // loop from last
|
||||
{
|
||||
lastEnd = curPos; // we store where the last value ended
|
||||
curPos += bitFields[f]; // we get where the current value starts
|
||||
int leftShift = 64 - curPos; // we figure how much left shift we gotta apply for the other numbers to overflow into oblivion
|
||||
retArr[f] = (uint)((packed << leftShift) >> leftShift + lastEnd); // we do magic
|
||||
}
|
||||
return retArr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@ -17,29 +18,24 @@ namespace Ryujinx.Common.Utilities
|
||||
/// It is REQUIRED for you to save returned options statically or as a part of static serializer context
|
||||
/// in order to avoid performance issues. You can safely modify returned options for your case before storing.
|
||||
/// </remarks>
|
||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true)
|
||||
{
|
||||
JsonSerializerOptions options = new()
|
||||
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) =>
|
||||
new()
|
||||
{
|
||||
DictionaryKeyPolicy = _snakeCasePolicy,
|
||||
PropertyNamingPolicy = _snakeCasePolicy,
|
||||
WriteIndented = indented,
|
||||
AllowTrailingCommas = true,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return JsonSerializer.Serialize(value, typeInfo);
|
||||
}
|
||||
=> JsonSerializer.Serialize(value, typeInfo);
|
||||
|
||||
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
return JsonSerializer.Deserialize(value, typeInfo);
|
||||
}
|
||||
=> JsonSerializer.Deserialize(value, typeInfo);
|
||||
|
||||
public static T Deserialize<T>(ReadOnlySpan<byte> utf8Value, JsonTypeInfo<T> typeInfo)
|
||||
=> JsonSerializer.Deserialize<T>(utf8Value, typeInfo);
|
||||
|
||||
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
@ -53,10 +49,7 @@ namespace Ryujinx.Common.Utilities
|
||||
return JsonSerializer.Deserialize(file, typeInfo);
|
||||
}
|
||||
|
||||
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo)
|
||||
{
|
||||
JsonSerializer.Serialize(stream, value, typeInfo);
|
||||
}
|
||||
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo) => JsonSerializer.Serialize(stream, value, typeInfo);
|
||||
|
||||
private class SnakeCaseNamingPolicy : JsonNamingPolicy
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@ -65,6 +66,11 @@ namespace Ryujinx.Common.Utilities
|
||||
return (targetProperties, targetAddressInfo);
|
||||
}
|
||||
|
||||
public static bool SupportsDynamicDns()
|
||||
{
|
||||
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
}
|
||||
|
||||
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
||||
{
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
|
||||
|
524
src/Ryujinx.Common/Utilities/XCIFileTrimmer.cs
Normal file
524
src/Ryujinx.Common/Utilities/XCIFileTrimmer.cs
Normal file
@ -0,0 +1,524 @@
|
||||
// Uncomment the line below to ensure XCIFileTrimmer does not modify files
|
||||
//#define XCI_TRIMMER_READ_ONLY_MODE
|
||||
|
||||
using Gommon;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public sealed class XCIFileTrimmer
|
||||
{
|
||||
private const long BytesInAMegabyte = 1024 * 1024;
|
||||
private const int BufferSize = 8 * (int)BytesInAMegabyte;
|
||||
|
||||
private const long CartSizeMBinFormattedGB = 952;
|
||||
private const int CartKeyAreaSize = 0x1000;
|
||||
private const byte PaddingByte = 0xFF;
|
||||
private const int HeaderFilePos = 0x100;
|
||||
private const int CartSizeFilePos = 0x10D;
|
||||
private const int DataSizeFilePos = 0x118;
|
||||
private const string HeaderMagicValue = "HEAD";
|
||||
|
||||
/// <summary>
|
||||
/// Cartridge Sizes (ByteIdentifier, SizeInGB)
|
||||
/// </summary>
|
||||
private static readonly Dictionary<byte, long> _cartSizesGB = new()
|
||||
{
|
||||
{ 0xFA, 1 },
|
||||
{ 0xF8, 2 },
|
||||
{ 0xF0, 4 },
|
||||
{ 0xE0, 8 },
|
||||
{ 0xE1, 16 },
|
||||
{ 0xE2, 32 }
|
||||
};
|
||||
|
||||
private static long RecordsToByte(long records)
|
||||
{
|
||||
return 512 + (records * 512);
|
||||
}
|
||||
|
||||
public static bool CanTrim(string filename, ILog log = null)
|
||||
{
|
||||
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var trimmer = new XCIFileTrimmer(filename, log);
|
||||
return trimmer.CanBeTrimmed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CanUntrim(string filename, ILog log = null)
|
||||
{
|
||||
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
var trimmer = new XCIFileTrimmer(filename, log);
|
||||
return trimmer.CanBeUntrimmed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ILog _log;
|
||||
private string _filename;
|
||||
private FileStream _fileStream;
|
||||
private BinaryReader _binaryReader;
|
||||
private long _offsetB, _dataSizeB, _cartSizeB, _fileSizeB;
|
||||
private bool _fileOK = true;
|
||||
private bool _freeSpaceChecked = false;
|
||||
private bool _freeSpaceValid = false;
|
||||
|
||||
public enum OperationOutcome
|
||||
{
|
||||
Undetermined,
|
||||
InvalidXCIFile,
|
||||
NoTrimNecessary,
|
||||
NoUntrimPossible,
|
||||
FreeSpaceCheckFailed,
|
||||
FileIOWriteError,
|
||||
ReadOnlyFileCannotFix,
|
||||
FileSizeChanged,
|
||||
Successful,
|
||||
Cancelled
|
||||
}
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Progress
|
||||
}
|
||||
|
||||
public interface ILog
|
||||
{
|
||||
public void Write(LogType logType, string text);
|
||||
public void Progress(long current, long total, string text, bool complete);
|
||||
}
|
||||
|
||||
public bool FileOK => _fileOK;
|
||||
public bool Trimmed => _fileOK && FileSizeB < UntrimmedFileSizeB;
|
||||
public bool ContainsKeyArea => _offsetB != 0;
|
||||
public bool CanBeTrimmed => _fileOK && FileSizeB > TrimmedFileSizeB;
|
||||
public bool CanBeUntrimmed => _fileOK && FileSizeB < UntrimmedFileSizeB;
|
||||
public bool FreeSpaceChecked => _fileOK && _freeSpaceChecked;
|
||||
public bool FreeSpaceValid => _fileOK && _freeSpaceValid;
|
||||
public long DataSizeB => _dataSizeB;
|
||||
public long CartSizeB => _cartSizeB;
|
||||
public long FileSizeB => _fileSizeB;
|
||||
public long DiskSpaceSavedB => CartSizeB - FileSizeB;
|
||||
public long DiskSpaceSavingsB => CartSizeB - DataSizeB;
|
||||
public long TrimmedFileSizeB => _offsetB + _dataSizeB;
|
||||
public long UntrimmedFileSizeB => _offsetB + _cartSizeB;
|
||||
|
||||
public ILog Log
|
||||
{
|
||||
get => _log;
|
||||
set => _log = value;
|
||||
}
|
||||
|
||||
public String Filename
|
||||
{
|
||||
get => _filename;
|
||||
set
|
||||
{
|
||||
_filename = value;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public long Pos
|
||||
{
|
||||
get => _fileStream.Position;
|
||||
set => _fileStream.Position = value;
|
||||
}
|
||||
|
||||
public XCIFileTrimmer(string path, ILog log = null)
|
||||
{
|
||||
Log = log;
|
||||
Filename = path;
|
||||
ReadHeader();
|
||||
}
|
||||
|
||||
public void CheckFreeSpace(CancellationToken? cancelToken = null)
|
||||
{
|
||||
if (FreeSpaceChecked)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (CanBeTrimmed)
|
||||
{
|
||||
_freeSpaceValid = false;
|
||||
|
||||
OpenReaders();
|
||||
|
||||
try
|
||||
{
|
||||
Pos = TrimmedFileSizeB;
|
||||
bool freeSpaceValid = true;
|
||||
long readSizeB = FileSizeB - TrimmedFileSizeB;
|
||||
|
||||
Stopwatch timedSw = Lambda.Timed(() =>
|
||||
{
|
||||
freeSpaceValid = CheckPadding(readSizeB, cancelToken);
|
||||
});
|
||||
|
||||
if (timedSw.Elapsed.TotalSeconds > 0)
|
||||
{
|
||||
Log?.Write(LogType.Info, $"Checked at {readSizeB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
|
||||
}
|
||||
|
||||
if (freeSpaceValid)
|
||||
Log?.Write(LogType.Info, "Free space is valid");
|
||||
|
||||
_freeSpaceValid = freeSpaceValid;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseReaders();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Log?.Write(LogType.Warn, "There is no free space to check.");
|
||||
_freeSpaceValid = false;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_freeSpaceChecked = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckPadding(long readSizeB, CancellationToken? cancelToken = null)
|
||||
{
|
||||
long maxReads = readSizeB / XCIFileTrimmer.BufferSize;
|
||||
long read = 0;
|
||||
var buffer = new byte[BufferSize];
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int bytes = _fileStream.Read(buffer, 0, XCIFileTrimmer.BufferSize);
|
||||
if (bytes == 0)
|
||||
break;
|
||||
|
||||
Log?.Progress(read, maxReads, "Verifying file can be trimmed", false);
|
||||
if (buffer.Take(bytes).AsParallel().Any(b => b != XCIFileTrimmer.PaddingByte))
|
||||
{
|
||||
Log?.Write(LogType.Warn, "Free space is NOT valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
read++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_freeSpaceChecked = false;
|
||||
_freeSpaceValid = false;
|
||||
ReadHeader();
|
||||
}
|
||||
|
||||
public OperationOutcome Trim(CancellationToken? cancelToken = null)
|
||||
{
|
||||
if (!FileOK)
|
||||
{
|
||||
return OperationOutcome.InvalidXCIFile;
|
||||
}
|
||||
|
||||
if (!CanBeTrimmed)
|
||||
{
|
||||
return OperationOutcome.NoTrimNecessary;
|
||||
}
|
||||
|
||||
if (!FreeSpaceChecked)
|
||||
{
|
||||
CheckFreeSpace(cancelToken);
|
||||
}
|
||||
|
||||
if (!FreeSpaceValid)
|
||||
{
|
||||
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
|
||||
{
|
||||
return OperationOutcome.Cancelled;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperationOutcome.FreeSpaceCheckFailed;
|
||||
}
|
||||
}
|
||||
|
||||
Log?.Write(LogType.Info, "Trimming...");
|
||||
|
||||
try
|
||||
{
|
||||
var info = new FileInfo(Filename);
|
||||
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log?.Write(LogType.Info, "Attempting to remove ReadOnly attribute");
|
||||
File.SetAttributes(Filename, info.Attributes & ~FileAttributes.ReadOnly);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log?.Write(LogType.Error, e.ToString());
|
||||
return OperationOutcome.ReadOnlyFileCannotFix;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.Length != FileSizeB)
|
||||
{
|
||||
Log?.Write(LogType.Error, "File size has changed, cannot safely trim.");
|
||||
return OperationOutcome.FileSizeChanged;
|
||||
}
|
||||
|
||||
var outfileStream = new FileStream(_filename, FileMode.Open, FileAccess.Write, FileShare.Write);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
#if !XCI_TRIMMER_READ_ONLY_MODE
|
||||
outfileStream.SetLength(TrimmedFileSizeB);
|
||||
#endif
|
||||
return OperationOutcome.Successful;
|
||||
}
|
||||
finally
|
||||
{
|
||||
outfileStream.Close();
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log?.Write(LogType.Error, e.ToString());
|
||||
return OperationOutcome.FileIOWriteError;
|
||||
}
|
||||
}
|
||||
|
||||
public OperationOutcome Untrim(CancellationToken? cancelToken = null)
|
||||
{
|
||||
if (!FileOK)
|
||||
{
|
||||
return OperationOutcome.InvalidXCIFile;
|
||||
}
|
||||
|
||||
if (!CanBeUntrimmed)
|
||||
{
|
||||
return OperationOutcome.NoUntrimPossible;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Log?.Write(LogType.Info, "Untrimming...");
|
||||
|
||||
var info = new FileInfo(Filename);
|
||||
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log?.Write(LogType.Info, "Attempting to remove ReadOnly attribute");
|
||||
File.SetAttributes(Filename, info.Attributes & ~FileAttributes.ReadOnly);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log?.Write(LogType.Error, e.ToString());
|
||||
return OperationOutcome.ReadOnlyFileCannotFix;
|
||||
}
|
||||
}
|
||||
|
||||
if (info.Length != FileSizeB)
|
||||
{
|
||||
Log?.Write(LogType.Error, "File size has changed, cannot safely untrim.");
|
||||
return OperationOutcome.FileSizeChanged;
|
||||
}
|
||||
|
||||
var outfileStream = new FileStream(_filename, FileMode.Append, FileAccess.Write, FileShare.Write);
|
||||
long bytesToWriteB = UntrimmedFileSizeB - FileSizeB;
|
||||
|
||||
try
|
||||
{
|
||||
Stopwatch timedSw = Lambda.Timed(() =>
|
||||
{
|
||||
WritePadding(outfileStream, bytesToWriteB, cancelToken);
|
||||
});
|
||||
|
||||
if (timedSw.Elapsed.TotalSeconds > 0)
|
||||
{
|
||||
Log?.Write(LogType.Info, $"Wrote at {bytesToWriteB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
|
||||
}
|
||||
|
||||
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
|
||||
{
|
||||
return OperationOutcome.Cancelled;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OperationOutcome.Successful;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
outfileStream.Close();
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log?.Write(LogType.Error, e.ToString());
|
||||
return OperationOutcome.FileIOWriteError;
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePadding(FileStream outfileStream, long bytesToWriteB, CancellationToken? cancelToken = null)
|
||||
{
|
||||
long bytesLeftToWriteB = bytesToWriteB;
|
||||
long writes = bytesLeftToWriteB / XCIFileTrimmer.BufferSize;
|
||||
int write = 0;
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new byte[BufferSize];
|
||||
Array.Fill<byte>(buffer, XCIFileTrimmer.PaddingByte);
|
||||
|
||||
while (bytesLeftToWriteB > 0)
|
||||
{
|
||||
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long bytesToWrite = Math.Min(XCIFileTrimmer.BufferSize, bytesLeftToWriteB);
|
||||
|
||||
#if !XCI_TRIMMER_READ_ONLY_MODE
|
||||
outfileStream.Write(buffer, 0, (int)bytesToWrite);
|
||||
#endif
|
||||
|
||||
bytesLeftToWriteB -= bytesToWrite;
|
||||
Log?.Progress(write, writes, "Writing padding data...", false);
|
||||
write++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log?.Progress(write, writes, "Writing padding data...", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenReaders()
|
||||
{
|
||||
if (_binaryReader == null)
|
||||
{
|
||||
_fileStream = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
_binaryReader = new BinaryReader(_fileStream);
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseReaders()
|
||||
{
|
||||
if (_binaryReader != null && _binaryReader.BaseStream != null)
|
||||
_binaryReader.Close();
|
||||
_binaryReader = null;
|
||||
_fileStream = null;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void ReadHeader()
|
||||
{
|
||||
try
|
||||
{
|
||||
OpenReaders();
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt without key area
|
||||
bool success = CheckAndReadHeader(false);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
// Attempt with key area
|
||||
success = CheckAndReadHeader(true);
|
||||
}
|
||||
|
||||
_fileOK = success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseReaders();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log?.Write(LogType.Error, ex.Message);
|
||||
_fileOK = false;
|
||||
_dataSizeB = 0;
|
||||
_cartSizeB = 0;
|
||||
_fileSizeB = 0;
|
||||
_offsetB = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckAndReadHeader(bool assumeKeyArea)
|
||||
{
|
||||
// Read file size
|
||||
_fileSizeB = _fileStream.Length;
|
||||
if (_fileSizeB < 32 * 1024)
|
||||
{
|
||||
Log?.Write(LogType.Error, "The source file doesn't look like an XCI file as the data size is too small");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup offset
|
||||
_offsetB = (long)(assumeKeyArea ? XCIFileTrimmer.CartKeyAreaSize : 0);
|
||||
|
||||
// Check header
|
||||
Pos = _offsetB + XCIFileTrimmer.HeaderFilePos;
|
||||
string head = System.Text.Encoding.ASCII.GetString(_binaryReader.ReadBytes(4));
|
||||
if (head != XCIFileTrimmer.HeaderMagicValue)
|
||||
{
|
||||
if (!assumeKeyArea)
|
||||
{
|
||||
Log?.Write(LogType.Warn, $"Incorrect header found, file mat contain a key area...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log?.Write(LogType.Error, "The source file doesn't look like an XCI file as the header is corrupted");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read Cart Size
|
||||
Pos = _offsetB + XCIFileTrimmer.CartSizeFilePos;
|
||||
byte cartSizeId = _binaryReader.ReadByte();
|
||||
if (!_cartSizesGB.TryGetValue(cartSizeId, out long cartSizeNGB))
|
||||
{
|
||||
Log?.Write(LogType.Error, $"The source file doesn't look like an XCI file as the Cartridge Size is incorrect (0x{cartSizeId:X2})");
|
||||
return false;
|
||||
}
|
||||
_cartSizeB = cartSizeNGB * XCIFileTrimmer.CartSizeMBinFormattedGB * XCIFileTrimmer.BytesInAMegabyte;
|
||||
|
||||
// Read data size
|
||||
Pos = _offsetB + XCIFileTrimmer.DataSizeFilePos;
|
||||
long records = (long)BitConverter.ToUInt32(_binaryReader.ReadBytes(4), 0);
|
||||
_dataSizeB = RecordsToByte(records);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user