commit
02d2f2c8af
8
.github/labeler.yml
vendored
8
.github/labeler.yml
vendored
@ -33,3 +33,11 @@ kernel:
|
|||||||
infra:
|
infra:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
|
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'docs/**'
|
||||||
|
|
||||||
|
ldn:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'
|
||||||
|
64
.github/workflows/build.yml
vendored
64
.github/workflows/build.yml
vendored
@ -74,36 +74,36 @@ jobs:
|
|||||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
|
|
||||||
#- name: Build AppImage
|
- name: Build AppImage
|
||||||
# if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
# run: |
|
run: |
|
||||||
# PLATFORM_NAME="${{ matrix.platform.name }}"
|
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||||
|
|
||||||
# sudo apt install -y zsync desktop-file-utils appstream
|
sudo apt install -y zsync desktop-file-utils appstream
|
||||||
|
|
||||||
# mkdir -p tools
|
mkdir -p tools
|
||||||
# export PATH="$PATH:$(readlink -f tools)"
|
export PATH="$PATH:$(readlink -f tools)"
|
||||||
|
|
||||||
# # Setup appimagetool
|
# Setup appimagetool
|
||||||
# wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
# chmod +x tools/appimagetool
|
chmod +x tools/appimagetool
|
||||||
# chmod +x distribution/linux/appimage/build-appimage.sh
|
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||||
# if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||||
# ARCH_NAME=x64
|
ARCH_NAME=x64
|
||||||
# export ARCH=x86_64
|
export ARCH=x86_64
|
||||||
# elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||||
# ARCH_NAME=arm64
|
ARCH_NAME=arm64
|
||||||
# export ARCH=aarch64
|
export ARCH=aarch64
|
||||||
# else
|
else
|
||||||
# echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||||
# exit 1
|
exit 1
|
||||||
# fi
|
fi
|
||||||
|
|
||||||
# export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||||
# BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||||
# shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@ -112,17 +112,17 @@ jobs:
|
|||||||
path: publish
|
path: publish
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
#- name: Upload Ryujinx (AppImage) artifact
|
- name: Upload Ryujinx (AppImage) artifact
|
||||||
# uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
# if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
# with:
|
with:
|
||||||
# name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
|
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
|
||||||
# path: publish_appimage
|
path: publish_appimage
|
||||||
|
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_sdl2_headless
|
path: publish_sdl2_headless
|
||||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
@ -185,6 +185,6 @@ jobs:
|
|||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
name: nogui-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||||
path: "publish_headless/*.tar.gz"
|
path: "publish_headless/*.tar.gz"
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
|
257
.github/workflows/canary.yml
vendored
Normal file
257
.github/workflows/canary.yml
vendored
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
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: "GreemDev"
|
||||||
|
RYUJINX_TARGET_RELEASE_CHANNEL_SOURCE_REPO: "Ryujinx"
|
||||||
|
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
|
||||||
|
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: context.repo.owner,
|
||||||
|
repo: context.repo.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: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
||||||
|
omitBodyDuringUpdate: true
|
||||||
|
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
|
||||||
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
|
||||||
|
|
||||||
|
- name: Packing Windows builds
|
||||||
|
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
|
||||||
|
|
||||||
|
pushd publish_sdl2_headless
|
||||||
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
|
7z a ../release_output/nogui-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
|
||||||
|
|
||||||
|
pushd publish_sdl2_headless
|
||||||
|
rm publish/libarmeilleure-jitsupport.dylib
|
||||||
|
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
||||||
|
tar -czvf ../release_output/nogui-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/*AppImage*"
|
||||||
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
||||||
|
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: 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 1
|
||||||
|
|
||||||
|
- name: Pushing new release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
name: "Canary ${{ steps.version_info.outputs.build_version }}"
|
||||||
|
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
||||||
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
|
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
|
||||||
|
omitBodyDuringUpdate: true
|
||||||
|
allowUpdates: true
|
||||||
|
replacesArtifacts: true
|
||||||
|
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||||
|
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||||
|
token: ${{ secrets.RELEASE_TOKEN }}
|
4
.github/workflows/nightly_pr_comment.yml
vendored
4
.github/workflows/nightly_pr_comment.yml
vendored
@ -38,12 +38,12 @@ jobs:
|
|||||||
return core.error(`No artifacts found`);
|
return core.error(`No artifacts found`);
|
||||||
}
|
}
|
||||||
let body = `Download the artifacts for this pull request:\n`;
|
let body = `Download the artifacts for this pull request:\n`;
|
||||||
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less</summary>\n`;
|
||||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||||
for (const art of artifacts) {
|
for (const art of artifacts) {
|
||||||
if(art.name.includes('Debug')) {
|
if(art.name.includes('Debug')) {
|
||||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
} else if(art.name.includes('nogui-ryujinx')) {
|
||||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else {
|
} else {
|
||||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
|
116
.github/workflows/release.yml
vendored
116
.github/workflows/release.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs: {}
|
inputs: {}
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ release ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
@ -20,7 +20,7 @@ env:
|
|||||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
RYUJINX_BASE_VERSION: "1.2"
|
RYUJINX_BASE_VERSION: "1.2"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
|
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
|
||||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
|
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
|
||||||
RELEASE: 1
|
RELEASE: 1
|
||||||
@ -93,6 +93,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_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_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_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
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -101,83 +102,79 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained -p:IncludeNativeLibrariesForSelfExtract=true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained
|
||||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained -p:IncludeNativeLibrariesForSelfExtract=true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.platform.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_ava
|
pushd publish
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
rm libarmeilleure-jitsupport.dylib
|
||||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
7z a ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build AppImage (Linux)
|
||||||
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
|
run: |
|
||||||
|
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
|
||||||
|
PLATFORM_NAME="${{ matrix.platform.name }}"
|
||||||
|
|
||||||
|
sudo apt install -y zsync desktop-file-utils appstream
|
||||||
|
|
||||||
|
mkdir -p tools
|
||||||
|
export PATH="$PATH:$(readlink -f tools)"
|
||||||
|
|
||||||
|
# Setup appimagetool
|
||||||
|
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||||
|
chmod +x tools/appimagetool
|
||||||
|
chmod +x distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
|
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
|
||||||
|
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
|
||||||
|
ARCH_NAME=x64
|
||||||
|
export ARCH=x86_64
|
||||||
|
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
|
||||||
|
ARCH_NAME=arm64
|
||||||
|
export ARCH=aarch64
|
||||||
|
else
|
||||||
|
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
|
||||||
|
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
|
||||||
|
|
||||||
|
pushd publish_appimage
|
||||||
|
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
|
||||||
|
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
|
||||||
|
popd
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_ava
|
pushd publish
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
chmod +x Ryujinx.sh Ryujinx
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
rm publish/libarmeilleure-jitsupport.dylib
|
chmod +x Ryujinx.sh Ryujinx.Headless.SDL2
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
tar -czvf ../release_output/nogui-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
|
||||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
|
||||||
popd
|
popd
|
||||||
shell: bash
|
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
|
- name: Pushing new release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
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*"
|
||||||
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
|
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ 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 }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
@ -228,22 +225,23 @@ 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_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_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_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
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
|
||||||
|
|
||||||
- name: Pushing new release
|
- name: Pushing new release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
name: ${{ steps.version_info.outputs.build_version }}
|
name: ${{ steps.version_info.outputs.build_version }}
|
||||||
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
|
artifacts: "publish/*.tar.gz, publish_headless/*.tar.gz"
|
||||||
tag: ${{ steps.version_info.outputs.build_version }}
|
tag: ${{ steps.version_info.outputs.build_version }}
|
||||||
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
|
||||||
omitBodyDuringUpdate: true
|
omitBodyDuringUpdate: true
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -175,3 +175,6 @@ PublishProfiles/
|
|||||||
|
|
||||||
# Glade backup files
|
# Glade backup files
|
||||||
*.glade~
|
*.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 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.
|
@ -74,7 +74,7 @@ We use and recommend the following workflow:
|
|||||||
3. In your fork, create a branch off of main (`git checkout -b mybranch`).
|
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.
|
- 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.
|
4. Make and commit your changes to your branch.
|
||||||
- [Build Instructions](https://github.com/GreemDev/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.
|
- Commit messages should be clear statements of action and intent.
|
||||||
6. Build the repository with your changes.
|
6. Build the repository with your changes.
|
||||||
- Make sure that the builds are clean.
|
- Make sure that the builds are clean.
|
||||||
@ -83,7 +83,7 @@ We use and recommend the following workflow:
|
|||||||
- State in the description what issue or improvement your change is addressing.
|
- 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/GreemDev/Ryujinx/actions) to check for outstanding errors.
|
- 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
|
8. Wait for feedback or approval of your changes from the core development team
|
||||||
- Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md).
|
- 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.
|
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.
|
- The next official build will automatically include your change.
|
||||||
- You can delete the branch you used for making the change.
|
- You can delete the branch you used for making the change.
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
<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.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
@ -33,11 +33,12 @@
|
|||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" 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.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
<PackageVersion Include="Gommon" Version="2.6.5" />
|
<PackageVersion Include="Gommon" Version="2.6.6" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
@ -51,4 +52,4 @@
|
|||||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
55
README.md
55
README.md
@ -14,6 +14,15 @@
|
|||||||
<img src="https://img.shields.io/github/v/release/GreemDev/Ryujinx"
|
<img src="https://img.shields.io/github/v/release/GreemDev/Ryujinx"
|
||||||
alt="Latest Release">
|
alt="Latest Release">
|
||||||
</a>
|
</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml">
|
||||||
|
<img src="https://github.com/GreemDev/Ryujinx/actions/workflows/canary.yml/badge.svg"
|
||||||
|
alt="">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/GreemDev/Ryujinx-Canary/releases/latest">
|
||||||
|
<img src="https://img.shields.io/github/v/release/GreemDev/Ryujinx-Canary?label=canary"
|
||||||
|
alt="Latest Canary Release">
|
||||||
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -33,7 +42,7 @@
|
|||||||
Guides and documentation can be found on the <a href="https://github.com/GreemDev/Ryujinx/wiki">Wiki tab</a>.
|
Guides and documentation can be found on the <a href="https://github.com/GreemDev/Ryujinx/wiki">Wiki tab</a>.
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
If you would like a version more preservative fork of Ryujinx, check out <a href="https://github.com/ryujinx-mirror/ryujinx">ryujinx-mirror</a>.
|
If you would like a more preservative fork of Ryujinx, check out <a href="https://github.com/ryujinx-mirror/ryujinx">ryujinx-mirror</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
@ -47,15 +56,6 @@
|
|||||||
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
||||||
</p>
|
</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
|
## Usage
|
||||||
|
|
||||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||||
@ -63,38 +63,21 @@ failing to meet this requirement may result in a poor gameplay experience or une
|
|||||||
|
|
||||||
## Latest build
|
## Latest build
|
||||||
|
|
||||||
These builds are compiled automatically for each commit on the master branch.
|
Stable builds are made every so often onto a separate "release" branch that then gets put into the releases you know and love.
|
||||||
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
|
||||||
|
|
||||||
You can find the latest release [here](https://github.com/GreemDev/Ryujinx/releases/latest).
|
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
|
## Documentation
|
||||||
|
|
||||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
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
|
## Features
|
||||||
|
|
||||||
- **Audio**
|
- **Audio**
|
||||||
|
17
Ryujinx.sln
17
Ryujinx.sln
@ -29,14 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "s
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}"
|
||||||
EndProject
|
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
|
|
||||||
Release Script = .github/workflows/release.yml
|
|
||||||
Build Script = .github/workflows/build.yml
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}"
|
||||||
@ -89,6 +81,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
|
||||||
EndProject
|
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
|
||||||
|
.github/workflows/release.yml = .github/workflows/release.yml
|
||||||
|
.github/workflows/canary.yml = .github/workflows/canary.yml
|
||||||
|
.github/workflows/build.yml = .github/workflows/build.yml
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -707,6 +707,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010300",
|
||||||
@ -3526,6 +3542,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01400000",
|
||||||
@ -4160,6 +4192,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -5848,6 +5896,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -6126,6 +6190,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -8341,6 +8421,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -9020,6 +9116,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000100",
|
||||||
@ -9496,6 +9608,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010000",
|
||||||
@ -9833,6 +9961,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -14667,6 +14811,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01030000",
|
||||||
@ -16119,6 +16279,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01050000",
|
||||||
@ -16717,6 +16893,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01070000",
|
||||||
@ -19745,6 +19937,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01080000",
|
||||||
@ -20503,6 +20711,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010000",
|
||||||
@ -21805,6 +22029,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -22340,6 +22580,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01020100",
|
||||||
@ -22990,6 +23246,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -23440,6 +23712,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -24660,6 +24948,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01410000",
|
||||||
@ -24954,6 +25258,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01060000",
|
||||||
@ -25286,6 +25606,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -29114,6 +29450,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010000",
|
||||||
@ -32512,6 +32864,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -32928,6 +33296,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000100",
|
||||||
@ -34800,6 +35184,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01000000",
|
||||||
@ -37569,6 +37969,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010100",
|
||||||
@ -41293,6 +41709,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01020100",
|
||||||
@ -45153,6 +45585,22 @@
|
|||||||
"0100F2C0115B6000"
|
"0100F2C0115B6000"
|
||||||
],
|
],
|
||||||
"gameName": "The Legend of Zelda: Tears of the Kingdom"
|
"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",
|
"head": "01010000",
|
||||||
@ -47896,5 +48344,5 @@
|
|||||||
"type": "Figure"
|
"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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
COMMAND="env LANG=C.UTF-8 DOTNET_EnableAlternateStackCheck=1"
|
||||||
|
|
||||||
if command -v gamemoderun > /dev/null 2>&1; then
|
if command -v gamemoderun > /dev/null 2>&1; then
|
||||||
COMMAND="$COMMAND gamemoderun"
|
COMMAND="$COMMAND gamemoderun"
|
||||||
|
@ -46,5 +46,5 @@ then
|
|||||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
||||||
else
|
else
|
||||||
echo "Usign codesign for ad-hoc signing"
|
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
|
fi
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 7 ]; then
|
if [ "$#" -lt 8 ]; then
|
||||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -18,10 +18,11 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
|||||||
VERSION=$5
|
VERSION=$5
|
||||||
SOURCE_REVISION_ID=$6
|
SOURCE_REVISION_ID=$6
|
||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
EXTRA_ARGS=$8
|
CANARY=$8
|
||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$CANARY" == "1" ]; then
|
||||||
then
|
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
|
||||||
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
|
||||||
else
|
else
|
||||||
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||||
@ -61,7 +62,7 @@ mkdir -p "$OUTPUT_DIRECTORY"
|
|||||||
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
||||||
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
# Make it libraries universal
|
# Make its libraries universal
|
||||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
||||||
|
|
||||||
if ! [ -x "$(command -v lipo)" ];
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
@ -99,7 +100,7 @@ then
|
|||||||
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
||||||
else
|
else
|
||||||
echo "Using codesign for ad-hoc signing"
|
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
|
fi
|
||||||
|
|
||||||
echo "Creating archive"
|
echo "Creating archive"
|
||||||
@ -111,4 +112,4 @@ rm "$RELEASE_TAR_FILE_NAME"
|
|||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
if [ "$#" -lt 7 ]; then
|
if [ "$#" -lt 8 ]; then
|
||||||
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <EXTRA_ARGS>"
|
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION> <CANARY>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -18,13 +18,14 @@ ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
|||||||
VERSION=$5
|
VERSION=$5
|
||||||
SOURCE_REVISION_ID=$6
|
SOURCE_REVISION_ID=$6
|
||||||
CONFIGURATION=$7
|
CONFIGURATION=$7
|
||||||
EXTRA_ARGS=$8
|
CANARY=$8
|
||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$CANARY" == "1" ]; then
|
||||||
then
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-canary-$VERSION-macos_universal.tar
|
||||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
elif [ "$VERSION" == "1.1.0" ]; then
|
||||||
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar
|
||||||
else
|
else
|
||||||
RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar
|
RELEASE_TAR_FILE_NAME=nogui-ryujinx-$VERSION-macos_universal.tar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64"
|
||||||
@ -56,7 +57,7 @@ mkdir -p "$OUTPUT_DIRECTORY"
|
|||||||
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT"
|
||||||
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
# Make it libraries universal
|
# Make its libraries universal
|
||||||
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib"
|
||||||
|
|
||||||
if ! [ -x "$(command -v lipo)" ];
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
@ -95,7 +96,7 @@ else
|
|||||||
echo "Using codesign for ad-hoc signing"
|
echo "Using codesign for ad-hoc signing"
|
||||||
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
for FILE in "$UNIVERSAL_OUTPUT"/*; do
|
||||||
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
if [[ $(file "$FILE") == *"Mach-O"* ]]; then
|
||||||
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE"
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$FILE"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
@ -108,4 +109,4 @@ gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
|||||||
rm "$RELEASE_TAR_FILE_NAME"
|
rm "$RELEASE_TAR_FILE_NAME"
|
||||||
popd
|
popd
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
||||||
|
@ -9,7 +9,7 @@ To merge pull requests, you must have write permissions in the repository.
|
|||||||
## Quick Code Review Rules
|
## 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.
|
* 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.
|
* 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.
|
* 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.
|
* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around.
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,252 +0,0 @@
|
|||||||
using ARMeilleure.Diagnostics;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace ARMeilleure.Common
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a table of guest address to a value.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEntry">Type of the value</typeparam>
|
|
||||||
public unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents a level in an <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly struct Level
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the index of the <see cref="Level"/> in the guest address.
|
|
||||||
/// </summary>
|
|
||||||
public int Index { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the length of the <see cref="Level"/> in the guest address.
|
|
||||||
/// </summary>
|
|
||||||
public int Length { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the mask which masks the bits used by the <see cref="Level"/>.
|
|
||||||
/// </summary>
|
|
||||||
public ulong Mask => ((1ul << Length) - 1) << Index;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="Level"/> structure with the specified
|
|
||||||
/// <paramref name="index"/> and <paramref name="length"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index of the <see cref="Level"/></param>
|
|
||||||
/// <param name="length">Length of the <see cref="Level"/></param>
|
|
||||||
public Level(int index, int length)
|
|
||||||
{
|
|
||||||
(Index, Length) = (index, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the value of the <see cref="Level"/> from the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Value of the <see cref="Level"/> from the specified guest <paramref name="address"/></returns>
|
|
||||||
public int GetValue(ulong address)
|
|
||||||
{
|
|
||||||
return (int)((address & Mask) >> Index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
private TEntry** _table;
|
|
||||||
private readonly List<nint> _pages;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public ulong Mask { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="Level"/>s used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public Level[] Levels { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the default fill value of newly created leaf pages.
|
|
||||||
/// </summary>
|
|
||||||
public TEntry Fill { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
|
||||||
public nint Base
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
lock (_pages)
|
|
||||||
{
|
|
||||||
return (nint)GetRootPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
|
||||||
/// <see cref="Level"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
|
|
||||||
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
|
|
||||||
public AddressTable(Level[] levels)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(levels);
|
|
||||||
|
|
||||||
if (levels.Length < 2)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Table must be at least 2 levels deep.", nameof(levels));
|
|
||||||
}
|
|
||||||
|
|
||||||
_pages = new List<nint>(capacity: 16);
|
|
||||||
|
|
||||||
Levels = levels;
|
|
||||||
Mask = 0;
|
|
||||||
|
|
||||||
foreach (var level in Levels)
|
|
||||||
{
|
|
||||||
Mask |= level.Mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Determines if the specified <paramref name="address"/> is in the range of the
|
|
||||||
/// <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
|
|
||||||
public bool IsValid(ulong address)
|
|
||||||
{
|
|
||||||
return (address & ~Mask) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
|
|
||||||
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
|
||||||
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
|
|
||||||
public ref TEntry GetValue(ulong address)
|
|
||||||
{
|
|
||||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
|
||||||
|
|
||||||
if (!IsValid(address))
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_pages)
|
|
||||||
{
|
|
||||||
return ref GetPage(address)[Levels[^1].GetValue(address)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the leaf page for the specified guest <paramref name="address"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="address">Guest address</param>
|
|
||||||
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
|
|
||||||
private TEntry* GetPage(ulong address)
|
|
||||||
{
|
|
||||||
TEntry** page = GetRootPage();
|
|
||||||
|
|
||||||
for (int i = 0; i < Levels.Length - 1; i++)
|
|
||||||
{
|
|
||||||
ref Level level = ref Levels[i];
|
|
||||||
ref TEntry* nextPage = ref page[level.GetValue(address)];
|
|
||||||
|
|
||||||
if (nextPage == null)
|
|
||||||
{
|
|
||||||
ref Level nextLevel = ref Levels[i + 1];
|
|
||||||
|
|
||||||
nextPage = i == Levels.Length - 2 ?
|
|
||||||
(TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true) :
|
|
||||||
(TEntry*)Allocate(1 << nextLevel.Length, nint.Zero, leaf: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
page = (TEntry**)nextPage;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (TEntry*)page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
|
|
||||||
private TEntry** GetRootPage()
|
|
||||||
{
|
|
||||||
if (_table == null)
|
|
||||||
{
|
|
||||||
_table = (TEntry**)Allocate(1 << Levels[0].Length, fill: nint.Zero, leaf: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _table;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Allocates a block of memory of the specified type and length.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of elements</typeparam>
|
|
||||||
/// <param name="length">Number of elements</param>
|
|
||||||
/// <param name="fill">Fill value</param>
|
|
||||||
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
|
|
||||||
/// <returns>Allocated block</returns>
|
|
||||||
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
|
||||||
{
|
|
||||||
var size = sizeof(T) * length;
|
|
||||||
var page = (nint)NativeAllocator.Instance.Allocate((uint)size);
|
|
||||||
var span = new Span<T>((void*)page, length);
|
|
||||||
|
|
||||||
span.Fill(fill);
|
|
||||||
|
|
||||||
_pages.Add(page);
|
|
||||||
|
|
||||||
TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
|
|
||||||
|
|
||||||
return page;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Dispose(true);
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
|
|
||||||
/// instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (!_disposed)
|
|
||||||
{
|
|
||||||
foreach (var page in _pages)
|
|
||||||
{
|
|
||||||
Marshal.FreeHGlobal(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
~AddressTable()
|
|
||||||
{
|
|
||||||
Dispose(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
44
src/ARMeilleure/Common/AddressTableLevel.cs
Normal file
44
src/ARMeilleure/Common/AddressTableLevel.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a level in an <see cref="IAddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly struct AddressTableLevel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the index of the <see cref="Level"/> in the guest address.
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the length of the <see cref="AddressTableLevel"/> in the guest address.
|
||||||
|
/// </summary>
|
||||||
|
public int Length { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the mask which masks the bits used by the <see cref="AddressTableLevel"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Mask => ((1ul << Length) - 1) << Index;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AddressTableLevel"/> structure with the specified
|
||||||
|
/// <paramref name="index"/> and <paramref name="length"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index of the <see cref="AddressTableLevel"/></param>
|
||||||
|
/// <param name="length">Length of the <see cref="AddressTableLevel"/></param>
|
||||||
|
public AddressTableLevel(int index, int length)
|
||||||
|
{
|
||||||
|
(Index, Length) = (index, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Value of the <see cref="AddressTableLevel"/> from the specified guest <paramref name="address"/></returns>
|
||||||
|
public long GetValue(ulong address)
|
||||||
|
{
|
||||||
|
return (long)((address & Mask) >> Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/ARMeilleure/Common/AddressTablePresets.cs
Normal file
75
src/ARMeilleure/Common/AddressTablePresets.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
public static class AddressTablePresets
|
||||||
|
{
|
||||||
|
private static readonly AddressTableLevel[] _levels64Bit =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new(31, 17),
|
||||||
|
new(23, 8),
|
||||||
|
new(15, 8),
|
||||||
|
new( 7, 8),
|
||||||
|
new( 2, 5),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32Bit =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new(31, 17),
|
||||||
|
new(23, 8),
|
||||||
|
new(15, 8),
|
||||||
|
new( 7, 8),
|
||||||
|
new( 1, 6),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels64BitSparseTiny =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 11, 28),
|
||||||
|
new( 2, 9),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32BitSparseTiny =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 10, 22),
|
||||||
|
new( 1, 9),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels64BitSparseGiant =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 38, 1),
|
||||||
|
new( 2, 36),
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly AddressTableLevel[] _levels32BitSparseGiant =
|
||||||
|
new AddressTableLevel[]
|
||||||
|
{
|
||||||
|
new( 31, 1),
|
||||||
|
new( 1, 30),
|
||||||
|
};
|
||||||
|
|
||||||
|
//high power will run worse on DDR3 systems and some DDR4 systems due to the higher ram utilization
|
||||||
|
//low power will never run worse than non-sparse, but for most systems it won't be necessary
|
||||||
|
//high power is always used, but I've left low power in here for future reference
|
||||||
|
public static AddressTableLevel[] GetArmPreset(bool for64Bits, bool sparse, bool lowPower = false)
|
||||||
|
{
|
||||||
|
if (sparse)
|
||||||
|
{
|
||||||
|
if (lowPower)
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64BitSparseTiny : _levels32BitSparseTiny;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64BitSparseGiant : _levels32BitSparseGiant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return for64Bits ? _levels64Bit : _levels32Bit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace ARMeilleure.Common
|
namespace ARMeilleure.Common
|
||||||
{
|
{
|
||||||
unsafe abstract class Allocator : IDisposable
|
public unsafe abstract class Allocator : IDisposable
|
||||||
{
|
{
|
||||||
public T* Allocate<T>(ulong count = 1) where T : unmanaged
|
public T* Allocate<T>(ulong count = 1) where T : unmanaged
|
||||||
{
|
{
|
||||||
|
51
src/ARMeilleure/Common/IAddressTable.cs
Normal file
51
src/ARMeilleure/Common/IAddressTable.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
public interface IAddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True if the address table's bottom level is sparsely mapped.
|
||||||
|
/// This also ensures the second bottom level is filled with a dummy page rather than 0.
|
||||||
|
/// </summary>
|
||||||
|
bool Sparse { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the bits used by the <see cref="Levels"/> of the <see cref="IAddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
ulong Mask { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="AddressTableLevel"/>s used by the <see cref="IAddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
AddressTableLevel[] Levels { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default fill value of newly created leaf pages.
|
||||||
|
/// </summary>
|
||||||
|
TEntry Fill { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the base address of the <see cref="EntryTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||||
|
nint Base { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the specified <paramref name="address"/> is in the range of the
|
||||||
|
/// <see cref="IAddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns><see langword="true"/> if is valid; otherwise <see langword="false"/></returns>
|
||||||
|
bool IsValid(ulong address);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the value at the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Reference to the value at the specified guest <paramref name="address"/></returns>
|
||||||
|
/// <exception cref="ObjectDisposedException"><see cref="EntryTable{TEntry}"/> instance was disposed</exception>
|
||||||
|
/// <exception cref="ArgumentException"><paramref name="address"/> is not mapped</exception>
|
||||||
|
ref TEntry GetValue(ulong address);
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
namespace ARMeilleure.Common
|
namespace ARMeilleure.Common
|
||||||
{
|
{
|
||||||
unsafe sealed class NativeAllocator : Allocator
|
public unsafe sealed class NativeAllocator : Allocator
|
||||||
{
|
{
|
||||||
public static NativeAllocator Instance { get; } = new();
|
public static NativeAllocator Instance { get; } = new();
|
||||||
|
|
||||||
|
@ -193,6 +193,8 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
Operand hostAddress;
|
Operand hostAddress;
|
||||||
|
|
||||||
|
var table = context.FunctionTable;
|
||||||
|
|
||||||
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
|
// If address is mapped onto the function table, we can skip the table walk. Otherwise we fallback
|
||||||
// onto the dispatch stub.
|
// onto the dispatch stub.
|
||||||
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
|
if (guestAddress.Kind == OperandKind.Constant && context.FunctionTable.IsValid(guestAddress.Value))
|
||||||
@ -203,6 +205,30 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
|
hostAddress = context.Load(OperandType.I64, hostAddressAddr);
|
||||||
}
|
}
|
||||||
|
else if (table.Sparse)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
// Deliberately attempts to avoid branches.
|
||||||
|
|
||||||
|
Operand tableBase = !context.HasPtc ?
|
||||||
|
Const(table.Base) :
|
||||||
|
Const(table.Base, Ptc.FunctionTableSymbol);
|
||||||
|
|
||||||
|
hostAddress = tableBase;
|
||||||
|
|
||||||
|
for (int i = 0; i < table.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = table.Levels[i];
|
||||||
|
int clearBits = 64 - (level.Index + level.Length);
|
||||||
|
|
||||||
|
Operand index = context.ShiftLeft(
|
||||||
|
context.ShiftRightUI(context.ShiftLeft(guestAddress, Const(clearBits)), Const(clearBits + level.Index)),
|
||||||
|
Const(3)
|
||||||
|
);
|
||||||
|
|
||||||
|
hostAddress = context.Load(OperandType.I64, context.Add(hostAddress, index));
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hostAddress = !context.HasPtc ?
|
hostAddress = !context.HasPtc ?
|
||||||
|
@ -49,6 +49,9 @@ namespace ARMeilleure.Instructions
|
|||||||
case 0b11_011_1101_0000_011:
|
case 0b11_011_1101_0000_011:
|
||||||
EmitGetTpidrroEl0(context);
|
EmitGetTpidrroEl0(context);
|
||||||
return;
|
return;
|
||||||
|
case 0b11_011_1101_0000_101:
|
||||||
|
EmitGetTpidr2El0(context);
|
||||||
|
return;
|
||||||
case 0b11_011_1110_0000_000:
|
case 0b11_011_1110_0000_000:
|
||||||
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
|
info = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetCntfrqEl0));
|
||||||
break;
|
break;
|
||||||
@ -84,6 +87,9 @@ namespace ARMeilleure.Instructions
|
|||||||
case 0b11_011_1101_0000_010:
|
case 0b11_011_1101_0000_010:
|
||||||
EmitSetTpidrEl0(context);
|
EmitSetTpidrEl0(context);
|
||||||
return;
|
return;
|
||||||
|
case 0b11_011_1101_0000_101:
|
||||||
|
EmitSetTpidr2El0(context);
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
throw new NotImplementedException($"Unknown MSR 0x{op.RawOpCode:X8} at 0x{op.Address:X16}.");
|
||||||
@ -213,6 +219,17 @@ namespace ARMeilleure.Instructions
|
|||||||
SetIntOrZR(context, op.Rt, result);
|
SetIntOrZR(context, op.Rt, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitGetTpidr2El0(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
|
|
||||||
|
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||||
|
|
||||||
|
Operand result = context.Load(OperandType.I64, context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())));
|
||||||
|
|
||||||
|
SetIntOrZR(context, op.Rt, result);
|
||||||
|
}
|
||||||
|
|
||||||
private static void EmitSetNzcv(ArmEmitterContext context)
|
private static void EmitSetNzcv(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
@ -274,5 +291,16 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidrEl0Offset())), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EmitSetTpidr2El0(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCodeSystem op = (OpCodeSystem)context.CurrOp;
|
||||||
|
|
||||||
|
Operand value = GetIntOrZR(context, op.Rt);
|
||||||
|
|
||||||
|
Operand nativeContext = context.LoadArgument(OperandType.I64, 0);
|
||||||
|
|
||||||
|
context.Store(context.Add(nativeContext, Const((ulong)NativeContext.GetTpidr2El0Offset())), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace ARMeilleure.Signal
|
|||||||
{
|
{
|
||||||
public static class NativeSignalHandlerGenerator
|
public static class NativeSignalHandlerGenerator
|
||||||
{
|
{
|
||||||
public const int MaxTrackedRanges = 8;
|
public const int MaxTrackedRanges = 16;
|
||||||
|
|
||||||
private const int StructAddressOffset = 0;
|
private const int StructAddressOffset = 0;
|
||||||
private const int StructWriteOffset = 4;
|
private const int StructWriteOffset = 4;
|
||||||
|
@ -21,6 +21,7 @@ namespace ARMeilleure.State
|
|||||||
public ulong ExclusiveValueLow;
|
public ulong ExclusiveValueLow;
|
||||||
public ulong ExclusiveValueHigh;
|
public ulong ExclusiveValueHigh;
|
||||||
public int Running;
|
public int Running;
|
||||||
|
public long Tpidr2El0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NativeCtxStorage _dummyStorage = new();
|
private static NativeCtxStorage _dummyStorage = new();
|
||||||
@ -176,6 +177,9 @@ namespace ARMeilleure.State
|
|||||||
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
public long GetTpidrroEl0() => GetStorage().TpidrroEl0;
|
||||||
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
public void SetTpidrroEl0(long value) => GetStorage().TpidrroEl0 = value;
|
||||||
|
|
||||||
|
public long GetTpidr2El0() => GetStorage().Tpidr2El0;
|
||||||
|
public void SetTpidr2El0(long value) => GetStorage().Tpidr2El0 = value;
|
||||||
|
|
||||||
public int GetCounter() => GetStorage().Counter;
|
public int GetCounter() => GetStorage().Counter;
|
||||||
public void SetCounter(int value) => GetStorage().Counter = value;
|
public void SetCounter(int value) => GetStorage().Counter = value;
|
||||||
|
|
||||||
@ -232,6 +236,11 @@ namespace ARMeilleure.State
|
|||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.TpidrroEl0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int GetTpidr2El0Offset()
|
||||||
|
{
|
||||||
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Tpidr2El0);
|
||||||
|
}
|
||||||
|
|
||||||
public static int GetCounterOffset()
|
public static int GetCounterOffset()
|
||||||
{
|
{
|
||||||
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
return StorageOffset(ref _dummyStorage, ref _dummyStorage.Counter);
|
||||||
|
@ -46,7 +46,7 @@ namespace ARMeilleure.Translation
|
|||||||
public IMemoryManager Memory { get; }
|
public IMemoryManager Memory { get; }
|
||||||
|
|
||||||
public EntryTable<uint> CountTable { get; }
|
public EntryTable<uint> CountTable { get; }
|
||||||
public AddressTable<ulong> FunctionTable { get; }
|
public IAddressTable<ulong> FunctionTable { get; }
|
||||||
public TranslatorStubs Stubs { get; }
|
public TranslatorStubs Stubs { get; }
|
||||||
|
|
||||||
public ulong EntryAddress { get; }
|
public ulong EntryAddress { get; }
|
||||||
@ -62,7 +62,7 @@ namespace ARMeilleure.Translation
|
|||||||
public ArmEmitterContext(
|
public ArmEmitterContext(
|
||||||
IMemoryManager memory,
|
IMemoryManager memory,
|
||||||
EntryTable<uint> countTable,
|
EntryTable<uint> countTable,
|
||||||
AddressTable<ulong> funcTable,
|
IAddressTable<ulong> funcTable,
|
||||||
TranslatorStubs stubs,
|
TranslatorStubs stubs,
|
||||||
ulong entryAddress,
|
ulong entryAddress,
|
||||||
bool highCq,
|
bool highCq,
|
||||||
|
@ -77,7 +77,7 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++)
|
for (int pBlkIndex = 0; pBlkIndex < block.Predecessors.Count; pBlkIndex++)
|
||||||
{
|
{
|
||||||
BasicBlock current = block.Predecessors[pBlkIndex];
|
BasicBlock current = block.Predecessors[pBlkIndex];
|
||||||
|
@ -13,6 +13,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime;
|
using System.Runtime;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 6997; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -40,6 +41,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
|
||||||
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
|
||||||
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
|
public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
|
||||||
|
public static readonly Symbol FunctionTableSymbol = new(SymbolType.Special, 4);
|
||||||
|
|
||||||
private const byte FillingByte = 0x00;
|
private const byte FillingByte = 0x00;
|
||||||
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
|
||||||
@ -100,7 +102,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Disable();
|
Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode)
|
public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode, string cacheSelector)
|
||||||
{
|
{
|
||||||
Wait();
|
Wait();
|
||||||
|
|
||||||
@ -126,6 +128,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
DisplayVersion = !string.IsNullOrEmpty(displayVersion) ? displayVersion : DisplayVersionDefault;
|
||||||
_memoryMode = memoryMode;
|
_memoryMode = memoryMode;
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Ptc, $"PPTC (v{InternalVersion}) Profile: {DisplayVersion}-{cacheSelector}");
|
||||||
|
|
||||||
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
string workPathActual = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", ActualDir);
|
||||||
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
string workPathBackup = Path.Combine(AppDataManager.GamesDirPath, TitleIdText, "cache", "cpu", BackupDir);
|
||||||
|
|
||||||
@ -139,8 +143,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Directory.CreateDirectory(workPathBackup);
|
Directory.CreateDirectory(workPathBackup);
|
||||||
}
|
}
|
||||||
|
|
||||||
CachePathActual = Path.Combine(workPathActual, DisplayVersion);
|
CachePathActual = Path.Combine(workPathActual, DisplayVersion) + "-" + cacheSelector;
|
||||||
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
|
CachePathBackup = Path.Combine(workPathBackup, DisplayVersion) + "-" + cacheSelector;
|
||||||
|
|
||||||
PreLoad();
|
PreLoad();
|
||||||
Profiler.PreLoad();
|
Profiler.PreLoad();
|
||||||
@ -705,6 +709,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
imm = translator.Stubs.DispatchStub;
|
imm = translator.Stubs.DispatchStub;
|
||||||
}
|
}
|
||||||
|
else if (symbol == FunctionTableSymbol)
|
||||||
|
{
|
||||||
|
imm = translator.FunctionTable.Base;
|
||||||
|
}
|
||||||
|
|
||||||
if (imm == null)
|
if (imm == null)
|
||||||
{
|
{
|
||||||
@ -848,18 +856,15 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Thread> threads = new();
|
|
||||||
|
|
||||||
for (int i = 0; i < degreeOfParallelism; i++)
|
List<Thread> threads = Enumerable.Range(0, degreeOfParallelism)
|
||||||
{
|
.Select(idx =>
|
||||||
Thread thread = new(TranslateFuncs)
|
new Thread(TranslateFuncs)
|
||||||
{
|
{
|
||||||
IsBackground = true,
|
IsBackground = true,
|
||||||
Name = "Ptc.TranslateThread." + i
|
Name = "Ptc.TranslateThread." + idx
|
||||||
};
|
}
|
||||||
|
).ToList();
|
||||||
threads.Add(thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
@ -22,33 +22,13 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
public class Translator
|
public class Translator
|
||||||
{
|
{
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 2, 5),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 1, 6),
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly IJitMemoryAllocator _allocator;
|
private readonly IJitMemoryAllocator _allocator;
|
||||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||||
|
|
||||||
private readonly Ptc _ptc;
|
private readonly Ptc _ptc;
|
||||||
|
|
||||||
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
internal TranslatorCache<TranslatedFunction> Functions { get; }
|
||||||
internal AddressTable<ulong> FunctionTable { get; }
|
internal IAddressTable<ulong> FunctionTable { get; }
|
||||||
internal EntryTable<uint> CountTable { get; }
|
internal EntryTable<uint> CountTable { get; }
|
||||||
internal TranslatorStubs Stubs { get; }
|
internal TranslatorStubs Stubs { get; }
|
||||||
internal TranslatorQueue Queue { get; }
|
internal TranslatorQueue Queue { get; }
|
||||||
@ -57,7 +37,7 @@ namespace ARMeilleure.Translation
|
|||||||
private Thread[] _backgroundTranslationThreads;
|
private Thread[] _backgroundTranslationThreads;
|
||||||
private volatile int _threadCount;
|
private volatile int _threadCount;
|
||||||
|
|
||||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, IAddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
@ -72,15 +52,15 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
CountTable = new EntryTable<uint>();
|
CountTable = new EntryTable<uint>();
|
||||||
Functions = new TranslatorCache<TranslatedFunction>();
|
Functions = new TranslatorCache<TranslatedFunction>();
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
FunctionTable = functionTable;
|
||||||
Stubs = new TranslatorStubs(FunctionTable);
|
Stubs = new TranslatorStubs(FunctionTable);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type);
|
_ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type, cacheSelector);
|
||||||
return _ptc;
|
return _ptc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly AddressTable<ulong> _functionTable;
|
private readonly IAddressTable<ulong> _functionTable;
|
||||||
private readonly Lazy<nint> _dispatchStub;
|
private readonly Lazy<nint> _dispatchStub;
|
||||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||||
@ -86,7 +86,7 @@ namespace ARMeilleure.Translation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||||
public TranslatorStubs(AddressTable<ulong> functionTable)
|
public TranslatorStubs(IAddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(functionTable);
|
ArgumentNullException.ThrowIfNull(functionTable);
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,7 +2,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
{
|
{
|
||||||
public class KeyboardHotkeys
|
public class KeyboardHotkeys
|
||||||
{
|
{
|
||||||
public Key ToggleVsync { get; set; }
|
public Key ToggleVSyncMode { get; set; }
|
||||||
public Key Screenshot { get; set; }
|
public Key Screenshot { get; set; }
|
||||||
public Key ShowUI { get; set; }
|
public Key ShowUI { get; set; }
|
||||||
public Key Pause { get; set; }
|
public Key Pause { get; set; }
|
||||||
@ -11,5 +11,7 @@ namespace Ryujinx.Common.Configuration.Hid
|
|||||||
public Key ResScaleDown { get; set; }
|
public Key ResScaleDown { get; set; }
|
||||||
public Key VolumeUp { get; set; }
|
public Key VolumeUp { get; set; }
|
||||||
public Key VolumeDown { get; set; }
|
public Key VolumeDown { get; set; }
|
||||||
|
public Key CustomVSyncIntervalIncrement { get; set; }
|
||||||
|
public Key CustomVSyncIntervalDecrement { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ namespace Ryujinx.Common.Configuration.Multiplayer
|
|||||||
public enum MultiplayerMode
|
public enum MultiplayerMode
|
||||||
{
|
{
|
||||||
Disabled,
|
Disabled,
|
||||||
|
LdnRyu,
|
||||||
LdnMitm,
|
LdnMitm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -72,5 +72,6 @@ namespace Ryujinx.Common.Logging
|
|||||||
TamperMachine,
|
TamperMachine,
|
||||||
UI,
|
UI,
|
||||||
Vic,
|
Vic,
|
||||||
|
XCIFileTrimmer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,10 +30,10 @@ namespace Ryujinx.Common.Logging.Targets
|
|||||||
string ILogTarget.Name { get => _target.Name; }
|
string ILogTarget.Name { get => _target.Name; }
|
||||||
|
|
||||||
public AsyncLogTargetWrapper(ILogTarget target)
|
public AsyncLogTargetWrapper(ILogTarget target)
|
||||||
: this(target, -1, AsyncLogTargetOverflowAction.Block)
|
: this(target, -1)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
|
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit = -1, AsyncLogTargetOverflowAction overflowAction = AsyncLogTargetOverflowAction.Block)
|
||||||
{
|
{
|
||||||
_target = target;
|
_target = target;
|
||||||
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
||||||
|
@ -47,7 +47,7 @@ namespace Ryujinx.Common.Logging.Targets
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clean up old logs, should only keep 3
|
// 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++)
|
for (int i = 0; i < files.Length - 2; i++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -69,9 +69,10 @@ namespace Ryujinx.Common.Logging.Targets
|
|||||||
}
|
}
|
||||||
|
|
||||||
string version = ReleaseInformation.Version;
|
string version = ReleaseInformation.Version;
|
||||||
|
string appName = ReleaseInformation.IsCanaryBuild ? "Ryujinx_Canary" : "Ryujinx";
|
||||||
|
|
||||||
// Get path for the current time
|
// Get path for the current time
|
||||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
path = Path.Combine(logDir.FullName, $"{appName}_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
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 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
|
public struct Array140<T> : IArray<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
T _e0;
|
T _e0;
|
||||||
@ -828,6 +816,18 @@ namespace Ryujinx.Common.Memory
|
|||||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
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
|
public struct Array384<T> : IArray<T> where T : unmanaged
|
||||||
{
|
{
|
||||||
T _e0;
|
T _e0;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
{
|
{
|
||||||
public class ReactiveObject<T>
|
public class ReactiveObject<T>
|
||||||
{
|
{
|
||||||
private readonly ReaderWriterLockSlim _readerWriterLock = new();
|
private readonly ReaderWriterLockSlim _rwLock = new();
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
private T _value;
|
private T _value;
|
||||||
|
|
||||||
@ -15,15 +17,15 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterReadLock();
|
_rwLock.EnterReadLock();
|
||||||
T value = _value;
|
T value = _value;
|
||||||
_readerWriterLock.ExitReadLock();
|
_rwLock.ExitReadLock();
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterWriteLock();
|
_rwLock.EnterWriteLock();
|
||||||
|
|
||||||
T oldValue = _value;
|
T oldValue = _value;
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ namespace Ryujinx.Common
|
|||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
_value = value;
|
_value = value;
|
||||||
|
|
||||||
_readerWriterLock.ExitWriteLock();
|
_rwLock.ExitWriteLock();
|
||||||
|
|
||||||
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
||||||
{
|
{
|
||||||
@ -40,12 +42,22 @@ 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 implicit operator T(ReactiveObject<T> obj) => obj.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ReactiveObjectHelper
|
public static class ReactiveObjectHelper
|
||||||
{
|
{
|
||||||
|
public static void LogValueChange<T>(LogClass logClass, ReactiveEventArgs<T> eventArgs, string valueName)
|
||||||
|
{
|
||||||
|
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;
|
public static void Toggle(this ReactiveObject<bool> rBoolean) => rBoolean.Value = !rBoolean.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
@ -5,7 +6,9 @@ namespace Ryujinx.Common
|
|||||||
// DO NOT EDIT, filled by CI
|
// DO NOT EDIT, filled by CI
|
||||||
public static class ReleaseInformation
|
public static class ReleaseInformation
|
||||||
{
|
{
|
||||||
private const string FlatHubChannelOwner = "flathub";
|
private const string FlatHubChannel = "flathub";
|
||||||
|
private const string CanaryChannel = "canary";
|
||||||
|
private const string ReleaseChannel = "release";
|
||||||
|
|
||||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||||
@ -13,6 +16,7 @@ namespace Ryujinx.Common
|
|||||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||||
|
|
||||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
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 const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||||
|
|
||||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||||
@ -21,11 +25,24 @@ namespace Ryujinx.Common
|
|||||||
!BuildGitHash.StartsWith("%%") &&
|
!BuildGitHash.StartsWith("%%") &&
|
||||||
!ReleaseChannelName.StartsWith("%%") &&
|
!ReleaseChannelName.StartsWith("%%") &&
|
||||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||||
|
!ReleaseChannelSourceRepo.StartsWith("%%") &&
|
||||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||||
!ConfigFileName.StartsWith("%%");
|
!ConfigFileName.StartsWith("%%");
|
||||||
|
|
||||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannel);
|
||||||
|
|
||||||
|
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 Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
|
|
||||||
|
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
|
||||||
|
IsCanaryBuild
|
||||||
|
? $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/compare/Canary-{currentVersion}...Canary-{newVersion}"
|
||||||
|
: $"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelSourceRepo}/releases/tag/{newVersion}";
|
||||||
|
|
||||||
|
public static string GetChangelogForVersion(Version version) =>
|
||||||
|
$"https://github.com/{ReleaseChannelOwner}/{ReleaseChannelRepo}/releases/tag/{version}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
@ -65,6 +66,11 @@ namespace Ryujinx.Common.Utilities
|
|||||||
return (targetProperties, targetAddressInfo);
|
return (targetProperties, targetAddressInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool SupportsDynamicDns()
|
||||||
|
{
|
||||||
|
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
}
|
||||||
|
|
||||||
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
public static uint ConvertIpv4Address(IPAddress ipAddress)
|
||||||
{
|
{
|
||||||
return BinaryPrimitives.ReadUInt32BigEndian(ipAddress.GetAddressBytes());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
482
src/Ryujinx.Cpu/AddressTable.cs
Normal file
482
src/Ryujinx.Cpu/AddressTable.cs
Normal file
@ -0,0 +1,482 @@
|
|||||||
|
using ARMeilleure.Memory;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Cpu.Signal;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using static Ryujinx.Cpu.MemoryEhMeilleure;
|
||||||
|
|
||||||
|
namespace ARMeilleure.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a table of guest address to a value.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntry">Type of the value</typeparam>
|
||||||
|
public unsafe class AddressTable<TEntry> : IAddressTable<TEntry> where TEntry : unmanaged
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a page of the address table.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct AddressTablePage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// True if the allocation belongs to a sparse block, false otherwise.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool IsSparse;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Base address for the page.
|
||||||
|
/// </summary>
|
||||||
|
public readonly IntPtr Address;
|
||||||
|
|
||||||
|
public AddressTablePage(bool isSparse, IntPtr address)
|
||||||
|
{
|
||||||
|
IsSparse = isSparse;
|
||||||
|
Address = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A sparsely mapped block of memory with a signal handler to map pages as they're accessed.
|
||||||
|
/// </summary>
|
||||||
|
private readonly struct TableSparseBlock : IDisposable
|
||||||
|
{
|
||||||
|
public readonly SparseMemoryBlock Block;
|
||||||
|
private readonly TrackingEventDelegate _trackingEvent;
|
||||||
|
|
||||||
|
public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit)
|
||||||
|
{
|
||||||
|
var block = new SparseMemoryBlock(size, pageInit, null);
|
||||||
|
|
||||||
|
_trackingEvent = (ulong address, ulong size, bool write) =>
|
||||||
|
{
|
||||||
|
ulong pointer = (ulong)block.Block.Pointer + address;
|
||||||
|
ensureMapped((IntPtr)pointer);
|
||||||
|
return pointer;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool added = NativeSignalHandler.AddTrackedRegion(
|
||||||
|
(nuint)block.Block.Pointer,
|
||||||
|
(nuint)(block.Block.Pointer + (IntPtr)block.Block.Size),
|
||||||
|
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
|
||||||
|
|
||||||
|
if (!added)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Block = block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
NativeSignalHandler.RemoveTrackedRegion((nuint)Block.Block.Pointer);
|
||||||
|
|
||||||
|
Block.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
private TEntry** _table;
|
||||||
|
private readonly List<AddressTablePage> _pages;
|
||||||
|
private TEntry _fill;
|
||||||
|
|
||||||
|
private readonly MemoryBlock _sparseFill;
|
||||||
|
private readonly SparseMemoryBlock _fillBottomLevel;
|
||||||
|
private readonly TEntry* _fillBottomLevelPtr;
|
||||||
|
|
||||||
|
private readonly List<TableSparseBlock> _sparseReserved;
|
||||||
|
private readonly ReaderWriterLockSlim _sparseLock;
|
||||||
|
|
||||||
|
private ulong _sparseBlockSize;
|
||||||
|
private ulong _sparseReservedOffset;
|
||||||
|
|
||||||
|
public bool Sparse { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ulong Mask { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public AddressTableLevel[] Levels { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TEntry Fill
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _fill;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
UpdateFill(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr Base
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
lock (_pages)
|
||||||
|
{
|
||||||
|
return (IntPtr)GetRootPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a new instance of the <see cref="AddressTable{TEntry}"/> class with the specified list of
|
||||||
|
/// <see cref="Level"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="levels">Levels for the address table</param>
|
||||||
|
/// <param name="sparse">True if the bottom page should be sparsely mapped</param>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="levels"/> is null</exception>
|
||||||
|
/// <exception cref="ArgumentException">Length of <paramref name="levels"/> is less than 2</exception>
|
||||||
|
public AddressTable(AddressTableLevel[] levels, bool sparse)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(levels);
|
||||||
|
|
||||||
|
_pages = new List<AddressTablePage>(capacity: 16);
|
||||||
|
|
||||||
|
Levels = levels;
|
||||||
|
Mask = 0;
|
||||||
|
|
||||||
|
foreach (var level in Levels)
|
||||||
|
{
|
||||||
|
Mask |= level.Mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sparse = sparse;
|
||||||
|
|
||||||
|
if (sparse)
|
||||||
|
{
|
||||||
|
// If the address table is sparse, allocate a fill block
|
||||||
|
|
||||||
|
_sparseFill = new MemoryBlock(268435456ul, MemoryAllocationFlags.Mirrorable); //low Power TC uses size: 65536ul
|
||||||
|
|
||||||
|
ulong bottomLevelSize = (1ul << levels.Last().Length) * (ulong)sizeof(TEntry);
|
||||||
|
|
||||||
|
_fillBottomLevel = new SparseMemoryBlock(bottomLevelSize, null, _sparseFill);
|
||||||
|
_fillBottomLevelPtr = (TEntry*)_fillBottomLevel.Block.Pointer;
|
||||||
|
|
||||||
|
_sparseReserved = new List<TableSparseBlock>();
|
||||||
|
_sparseLock = new ReaderWriterLockSlim();
|
||||||
|
|
||||||
|
_sparseBlockSize = bottomLevelSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create an <see cref="AddressTable{TEntry}"/> instance for an ARM function table.
|
||||||
|
/// Selects the best table structure for A32/A64, taking into account the selected memory manager type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="for64Bits">True if the guest is A64, false otherwise</param>
|
||||||
|
/// <param name="type">Memory manager type</param>
|
||||||
|
/// <returns>An <see cref="AddressTable{TEntry}"/> for ARM function lookup</returns>
|
||||||
|
public static AddressTable<TEntry> CreateForArm(bool for64Bits, MemoryManagerType type)
|
||||||
|
{
|
||||||
|
// Assume software memory means that we don't want to use any signal handlers.
|
||||||
|
bool sparse = type != MemoryManagerType.SoftwareMmu && type != MemoryManagerType.SoftwarePageTable;
|
||||||
|
|
||||||
|
return new AddressTable<TEntry>(AddressTablePresets.GetArmPreset(for64Bits, sparse), sparse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update the fill value for the bottom level of the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fillValue">New fill value</param>
|
||||||
|
private void UpdateFill(TEntry fillValue)
|
||||||
|
{
|
||||||
|
if (_sparseFill != null)
|
||||||
|
{
|
||||||
|
Span<byte> span = _sparseFill.GetSpan(0, (int)_sparseFill.Size);
|
||||||
|
MemoryMarshal.Cast<byte, TEntry>(span).Fill(fillValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
_fill = fillValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Signal that the given code range exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address"></param>
|
||||||
|
/// <param name="size"></param>
|
||||||
|
public void SignalCodeRange(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
AddressTableLevel bottom = Levels.Last();
|
||||||
|
ulong bottomLevelEntries = 1ul << bottom.Length;
|
||||||
|
|
||||||
|
ulong entryIndex = address >> bottom.Index;
|
||||||
|
ulong entries = size >> bottom.Index;
|
||||||
|
entries += entryIndex - BitUtils.AlignDown(entryIndex, bottomLevelEntries);
|
||||||
|
|
||||||
|
_sparseBlockSize = Math.Max(_sparseBlockSize, BitUtils.AlignUp(entries, bottomLevelEntries) * (ulong)sizeof(TEntry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsValid(ulong address)
|
||||||
|
{
|
||||||
|
return (address & ~Mask) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public ref TEntry GetValue(ulong address)
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||||
|
|
||||||
|
if (!IsValid(address))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Address 0x{address:X} is not mapped onto the table.", nameof(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_pages)
|
||||||
|
{
|
||||||
|
TEntry* page = GetPage(address);
|
||||||
|
|
||||||
|
long index = Levels[^1].GetValue(address);
|
||||||
|
|
||||||
|
EnsureMapped((IntPtr)(page + index));
|
||||||
|
|
||||||
|
return ref page[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the leaf page for the specified guest <paramref name="address"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Guest address</param>
|
||||||
|
/// <returns>Leaf page for the specified guest <paramref name="address"/></returns>
|
||||||
|
private TEntry* GetPage(ulong address)
|
||||||
|
{
|
||||||
|
TEntry** page = GetRootPage();
|
||||||
|
|
||||||
|
for (int i = 0; i < Levels.Length - 1; i++)
|
||||||
|
{
|
||||||
|
ref AddressTableLevel level = ref Levels[i];
|
||||||
|
ref TEntry* nextPage = ref page[level.GetValue(address)];
|
||||||
|
|
||||||
|
if (nextPage == null || nextPage == _fillBottomLevelPtr)
|
||||||
|
{
|
||||||
|
ref AddressTableLevel nextLevel = ref Levels[i + 1];
|
||||||
|
|
||||||
|
if (i == Levels.Length - 2)
|
||||||
|
{
|
||||||
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, Fill, leaf: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextPage = (TEntry*)Allocate(1 << nextLevel.Length, GetFillValue(i), leaf: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page = (TEntry**)nextPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (TEntry*)page;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">Pointer to be mapped</param>
|
||||||
|
private void EnsureMapped(IntPtr ptr)
|
||||||
|
{
|
||||||
|
if (Sparse)
|
||||||
|
{
|
||||||
|
// Check sparse allocations to see if the pointer is in any of them.
|
||||||
|
// Ensure the page is committed if there's a match.
|
||||||
|
|
||||||
|
_sparseLock.EnterReadLock();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (TableSparseBlock reserved in _sparseReserved)
|
||||||
|
{
|
||||||
|
SparseMemoryBlock sparse = reserved.Block;
|
||||||
|
|
||||||
|
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
|
||||||
|
{
|
||||||
|
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_sparseLock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the fill value for a non-leaf level of the table.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="level">Level to get the fill value for</param>
|
||||||
|
/// <returns>The fill value</returns>
|
||||||
|
private IntPtr GetFillValue(int level)
|
||||||
|
{
|
||||||
|
if (_fillBottomLevel != null && level == Levels.Length - 2)
|
||||||
|
{
|
||||||
|
return (IntPtr)_fillBottomLevelPtr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lazily initialize and get the root page of the <see cref="AddressTable{TEntry}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Root page of the <see cref="AddressTable{TEntry}"/></returns>
|
||||||
|
private TEntry** GetRootPage()
|
||||||
|
{
|
||||||
|
if (_table == null)
|
||||||
|
{
|
||||||
|
if (Levels.Length == 1)
|
||||||
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, Fill, leaf: true);
|
||||||
|
else
|
||||||
|
_table = (TEntry**)Allocate(1 << Levels[0].Length, GetFillValue(0), leaf: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize a leaf page with the fill value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="page">Page to initialize</param>
|
||||||
|
private void InitLeafPage(Span<byte> page)
|
||||||
|
{
|
||||||
|
MemoryMarshal.Cast<byte, TEntry>(page).Fill(_fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reserve a new sparse block, and add it to the list.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The new sparse block that was added</returns>
|
||||||
|
private TableSparseBlock ReserveNewSparseBlock()
|
||||||
|
{
|
||||||
|
var block = new TableSparseBlock(_sparseBlockSize, EnsureMapped, InitLeafPage);
|
||||||
|
|
||||||
|
_sparseReserved.Add(block);
|
||||||
|
_sparseReservedOffset = 0;
|
||||||
|
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allocates a block of memory of the specified type and length.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Type of elements</typeparam>
|
||||||
|
/// <param name="length">Number of elements</param>
|
||||||
|
/// <param name="fill">Fill value</param>
|
||||||
|
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
|
||||||
|
/// <returns>Allocated block</returns>
|
||||||
|
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
|
||||||
|
{
|
||||||
|
var size = sizeof(T) * length;
|
||||||
|
|
||||||
|
AddressTablePage page;
|
||||||
|
|
||||||
|
if (Sparse && leaf)
|
||||||
|
{
|
||||||
|
_sparseLock.EnterWriteLock();
|
||||||
|
|
||||||
|
SparseMemoryBlock block;
|
||||||
|
|
||||||
|
if (_sparseReserved.Count == 0)
|
||||||
|
{
|
||||||
|
block = ReserveNewSparseBlock().Block;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
block = _sparseReserved.Last().Block;
|
||||||
|
|
||||||
|
if (_sparseReservedOffset == block.Block.Size)
|
||||||
|
{
|
||||||
|
block = ReserveNewSparseBlock().Block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset);
|
||||||
|
|
||||||
|
_sparseReservedOffset += (ulong)size;
|
||||||
|
|
||||||
|
_sparseLock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
|
||||||
|
page = new AddressTablePage(false, address);
|
||||||
|
|
||||||
|
var span = new Span<T>((void*)page.Address, length);
|
||||||
|
span.Fill(fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pages.Add(page);
|
||||||
|
|
||||||
|
//TranslatorEventSource.Log.AddressTableAllocated(size, leaf);
|
||||||
|
|
||||||
|
return page.Address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Releases all unmanaged and optionally managed resources used by the <see cref="AddressTable{TEntry}"/>
|
||||||
|
/// instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing"><see langword="true"/> to dispose managed resources also; otherwise just unmanaged resouces</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
foreach (var page in _pages)
|
||||||
|
{
|
||||||
|
if (!page.IsSparse)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(page.Address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sparse)
|
||||||
|
{
|
||||||
|
foreach (TableSparseBlock block in _sparseReserved)
|
||||||
|
{
|
||||||
|
block.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_sparseReserved.Clear();
|
||||||
|
|
||||||
|
_fillBottomLevel.Dispose();
|
||||||
|
_sparseFill.Dispose();
|
||||||
|
_sparseLock.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Frees resources used by the <see cref="AddressTable{TEntry}"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
~AddressTable()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new DummyDiskCacheLoadState();
|
return new DummyDiskCacheLoadState();
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Cpu
|
|||||||
/// <param name="displayVersion">Version of the application</param>
|
/// <param name="displayVersion">Version of the application</param>
|
||||||
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
/// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
|
||||||
/// <returns>Disk cache load progress reporter and manager</returns>
|
/// <returns>Disk cache load progress reporter and manager</returns>
|
||||||
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled);
|
IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
/// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using Ryujinx.Cpu.Signal;
|
using Ryujinx.Cpu.Signal;
|
||||||
@ -9,11 +10,13 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
private readonly ITickSource _tickSource;
|
private readonly ITickSource _tickSource;
|
||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
|
private readonly AddressTable<ulong> _functionTable;
|
||||||
|
|
||||||
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||||
{
|
{
|
||||||
_tickSource = tickSource;
|
_tickSource = tickSource;
|
||||||
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit);
|
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
|
||||||
|
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, _functionTable);
|
||||||
|
|
||||||
if (memory.Type.IsHostMappedOrTracked())
|
if (memory.Type.IsHostMappedOrTracked())
|
||||||
{
|
{
|
||||||
@ -47,14 +50,15 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled));
|
return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled, cacheSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void PrepareCodeRange(ulong address, ulong size)
|
public void PrepareCodeRange(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
_functionTable.SignalCodeRange(address, size);
|
||||||
_translator.PrepareCodeRange(address, size);
|
_translator.PrepareCodeRange(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +140,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
bool isTail = false)
|
bool isTail = false)
|
||||||
{
|
{
|
||||||
int tempRegister;
|
int tempRegister;
|
||||||
|
int tempGuestAddress = -1;
|
||||||
|
|
||||||
|
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
|
||||||
|
funcTable is { Sparse: true };
|
||||||
|
|
||||||
if (guestAddress.Kind == OperandKind.Constant)
|
if (guestAddress.Kind == OperandKind.Constant)
|
||||||
{
|
{
|
||||||
@ -153,9 +157,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
||||||
|
|
||||||
|
if (inlineLookup && guestAddress.Value == 0)
|
||||||
|
{
|
||||||
|
// X0 will be overwritten. Move the address to a temp register.
|
||||||
|
tempGuestAddress = regAlloc.AllocateTempGprRegister();
|
||||||
|
asm.Mov(Register(tempGuestAddress), guestAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1;
|
tempRegister = NextFreeRegister(1, tempGuestAddress);
|
||||||
|
|
||||||
if (!isTail)
|
if (!isTail)
|
||||||
{
|
{
|
||||||
@ -176,6 +187,40 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
||||||
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
||||||
}
|
}
|
||||||
|
else if (inlineLookup)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
|
||||||
|
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
guestAddress = Register(tempGuestAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong tableBase = (ulong)funcTable.Base;
|
||||||
|
|
||||||
|
// Index into the table.
|
||||||
|
asm.Mov(rn, tableBase);
|
||||||
|
|
||||||
|
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = funcTable.Levels[i];
|
||||||
|
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||||
|
asm.Lsl(indexReg, indexReg, Const(3));
|
||||||
|
|
||||||
|
// Index into the page.
|
||||||
|
asm.Add(rn, rn, indexReg);
|
||||||
|
|
||||||
|
// Load the page address.
|
||||||
|
asm.LdrRiUn(rn, rn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
regAlloc.FreeTempGprRegister(tempGuestAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.Mov(rn, (ulong)funcPtr);
|
asm.Mov(rn, (ulong)funcPtr);
|
||||||
@ -252,5 +297,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
{
|
{
|
||||||
return new Operand(register, RegisterType.Integer, type);
|
return new Operand(register, RegisterType.Integer, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand Const(long value, OperandType type = OperandType.I64)
|
||||||
|
{
|
||||||
|
return new Operand(type, (ulong)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int NextFreeRegister(int start, int avoid)
|
||||||
|
{
|
||||||
|
if (start == avoid)
|
||||||
|
{
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -305,6 +305,10 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
bool isTail = false)
|
bool isTail = false)
|
||||||
{
|
{
|
||||||
int tempRegister;
|
int tempRegister;
|
||||||
|
int tempGuestAddress = -1;
|
||||||
|
|
||||||
|
bool inlineLookup = guestAddress.Kind != OperandKind.Constant &&
|
||||||
|
funcTable is { Sparse: true };
|
||||||
|
|
||||||
if (guestAddress.Kind == OperandKind.Constant)
|
if (guestAddress.Kind == OperandKind.Constant)
|
||||||
{
|
{
|
||||||
@ -318,9 +322,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
asm.StrRiUn(guestAddress, Register(regAlloc.FixedContextRegister), NativeContextOffsets.DispatchAddressOffset);
|
||||||
|
|
||||||
|
if (inlineLookup && guestAddress.Value == 0)
|
||||||
|
{
|
||||||
|
// X0 will be overwritten. Move the address to a temp register.
|
||||||
|
tempGuestAddress = regAlloc.AllocateTempGprRegister();
|
||||||
|
asm.Mov(Register(tempGuestAddress), guestAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempRegister = regAlloc.FixedContextRegister == 1 ? 2 : 1;
|
tempRegister = NextFreeRegister(1, tempGuestAddress);
|
||||||
|
|
||||||
if (!isTail)
|
if (!isTail)
|
||||||
{
|
{
|
||||||
@ -341,6 +352,40 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
asm.Mov(rn, funcPtrLoc & ~0xfffUL);
|
||||||
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
asm.LdrRiUn(rn, rn, (int)(funcPtrLoc & 0xfffUL));
|
||||||
}
|
}
|
||||||
|
else if (inlineLookup)
|
||||||
|
{
|
||||||
|
// Inline table lookup. Only enabled when the sparse function table is enabled with 2 levels.
|
||||||
|
|
||||||
|
Operand indexReg = Register(NextFreeRegister(tempRegister + 1, tempGuestAddress));
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
guestAddress = Register(tempGuestAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong tableBase = (ulong)funcTable.Base;
|
||||||
|
|
||||||
|
// Index into the table.
|
||||||
|
asm.Mov(rn, tableBase);
|
||||||
|
|
||||||
|
for (int i = 0; i < funcTable.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var level = funcTable.Levels[i];
|
||||||
|
asm.Ubfx(indexReg, guestAddress, level.Index, level.Length);
|
||||||
|
asm.Lsl(indexReg, indexReg, Const(3));
|
||||||
|
|
||||||
|
// Index into the page.
|
||||||
|
asm.Add(rn, rn, indexReg);
|
||||||
|
|
||||||
|
// Load the page address.
|
||||||
|
asm.LdrRiUn(rn, rn, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempGuestAddress != -1)
|
||||||
|
{
|
||||||
|
regAlloc.FreeTempGprRegister(tempGuestAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
asm.Mov(rn, (ulong)funcPtr);
|
asm.Mov(rn, (ulong)funcPtr);
|
||||||
@ -613,5 +658,20 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
|||||||
{
|
{
|
||||||
return new Operand(register, RegisterType.Integer, type);
|
return new Operand(register, RegisterType.Integer, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Operand Const(long value, OperandType type = OperandType.I64)
|
||||||
|
{
|
||||||
|
return new Operand(type, (ulong)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int NextFreeRegister(int start, int avoid)
|
||||||
|
{
|
||||||
|
if (start == avoid)
|
||||||
|
{
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using ARMeilleure.Common;
|
||||||
using ARMeilleure.Memory;
|
using ARMeilleure.Memory;
|
||||||
using Ryujinx.Cpu.Jit;
|
using Ryujinx.Cpu.Jit;
|
||||||
using Ryujinx.Cpu.LightningJit.State;
|
using Ryujinx.Cpu.LightningJit.State;
|
||||||
@ -8,11 +9,16 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
{
|
{
|
||||||
private readonly ITickSource _tickSource;
|
private readonly ITickSource _tickSource;
|
||||||
private readonly Translator _translator;
|
private readonly Translator _translator;
|
||||||
|
private readonly AddressTable<ulong> _functionTable;
|
||||||
|
|
||||||
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
public LightningJitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||||
{
|
{
|
||||||
_tickSource = tickSource;
|
_tickSource = tickSource;
|
||||||
_translator = new Translator(memory, for64Bit);
|
|
||||||
|
_functionTable = AddressTable<ulong>.CreateForArm(for64Bit, memory.Type);
|
||||||
|
|
||||||
|
_translator = new Translator(memory, _functionTable);
|
||||||
|
|
||||||
memory.UnmapEvent += UnmapHandler;
|
memory.UnmapEvent += UnmapHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +46,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled, string cacheSelector)
|
||||||
{
|
{
|
||||||
return new DummyDiskCacheLoadState();
|
return new DummyDiskCacheLoadState();
|
||||||
}
|
}
|
||||||
@ -48,6 +54,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void PrepareCodeRange(ulong address, ulong size)
|
public void PrepareCodeRange(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
_functionTable.SignalCodeRange(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -19,25 +19,6 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
// Should be enabled on platforms that enforce W^X.
|
// Should be enabled on platforms that enforce W^X.
|
||||||
private static bool IsNoWxPlatform => false;
|
private static bool IsNoWxPlatform => false;
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels64Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(31, 17),
|
|
||||||
new(23, 8),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 2, 5),
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly AddressTable<ulong>.Level[] _levels32Bit =
|
|
||||||
new AddressTable<ulong>.Level[]
|
|
||||||
{
|
|
||||||
new(23, 9),
|
|
||||||
new(15, 8),
|
|
||||||
new( 7, 8),
|
|
||||||
new( 1, 6),
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
|
||||||
private readonly NoWxCache _noWxCache;
|
private readonly NoWxCache _noWxCache;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
@ -47,7 +28,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
internal TranslatorStubs Stubs { get; }
|
internal TranslatorStubs Stubs { get; }
|
||||||
internal IMemoryManager Memory { get; }
|
internal IMemoryManager Memory { get; }
|
||||||
|
|
||||||
public Translator(IMemoryManager memory, bool for64Bits)
|
public Translator(IMemoryManager memory, AddressTable<ulong> functionTable)
|
||||||
{
|
{
|
||||||
Memory = memory;
|
Memory = memory;
|
||||||
|
|
||||||
@ -63,7 +44,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
}
|
}
|
||||||
|
|
||||||
Functions = new TranslatorCache<TranslatedFunction>();
|
Functions = new TranslatorCache<TranslatedFunction>();
|
||||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
FunctionTable = functionTable;
|
||||||
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
|
Stubs = new TranslatorStubs(FunctionTable, _noWxCache);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
|
@ -23,7 +23,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
private readonly AddressTable<ulong> _functionTable;
|
private readonly IAddressTable<ulong> _functionTable;
|
||||||
private readonly NoWxCache _noWxCache;
|
private readonly NoWxCache _noWxCache;
|
||||||
private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
|
private readonly GetFunctionAddressDelegate _getFunctionAddressRef;
|
||||||
private readonly nint _getFunctionAddress;
|
private readonly nint _getFunctionAddress;
|
||||||
@ -79,7 +79,7 @@ namespace Ryujinx.Cpu.LightningJit
|
|||||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||||
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
|
/// <param name="noWxCache">Cache used on platforms that enforce W^X, otherwise should be null</param>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||||
public TranslatorStubs(AddressTable<ulong> functionTable, NoWxCache noWxCache)
|
public TranslatorStubs(IAddressTable<ulong> functionTable, NoWxCache noWxCache)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(functionTable);
|
ArgumentNullException.ThrowIfNull(functionTable);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
IPipeline Pipeline { get; }
|
IPipeline Pipeline { get; }
|
||||||
|
|
||||||
IWindow Window { get; }
|
IWindow Window { get; }
|
||||||
|
|
||||||
uint ProgramCount { get; }
|
uint ProgramCount { get; }
|
||||||
|
|
||||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||||
|
@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
void SetSize(int width, int height);
|
void SetSize(int width, int height);
|
||||||
|
|
||||||
void ChangeVSyncMode(bool vsyncEnabled);
|
void ChangeVSyncMode(VSyncMode vSyncMode);
|
||||||
|
|
||||||
void SetAntiAliasing(AntiAliasing antialiasing);
|
void SetAntiAliasing(AntiAliasing antialiasing);
|
||||||
void SetScalingFilter(ScalingFilter type);
|
void SetScalingFilter(ScalingFilter type);
|
||||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
_impl.Window.SetSize(width, height);
|
_impl.Window.SetSize(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
public void ChangeVSyncMode(VSyncMode vSyncMode) { }
|
||||||
|
|
||||||
public void SetAntiAliasing(AntiAliasing effect) { }
|
public void SetAntiAliasing(AntiAliasing effect) { }
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
9
src/Ryujinx.Graphics.GAL/VSyncMode.cs
Normal file
9
src/Ryujinx.Graphics.GAL/VSyncMode.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public enum VSyncMode
|
||||||
|
{
|
||||||
|
Switch,
|
||||||
|
Unbounded,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -97,7 +97,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info)
|
||||||
{
|
{
|
||||||
ProgramCount++;
|
ProgramCount++;
|
||||||
|
|
||||||
return new Program(shaders, info.FragmentOutputMap);
|
return new Program(shaders, info.FragmentOutputMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
public void ChangeVSyncMode(VSyncMode vSyncMode) { }
|
||||||
|
|
||||||
public void SetSize(int width, int height)
|
public void SetSize(int width, int height)
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
@ -182,6 +182,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Prevent the sum of descriptors from exceeding MaxPushDescriptors
|
||||||
|
int totalDescriptors = 0;
|
||||||
|
foreach (ResourceDescriptor desc in layout.Sets.First().Descriptors)
|
||||||
|
{
|
||||||
|
if (!reserved.Contains(desc.Binding))
|
||||||
|
totalDescriptors += desc.Count;
|
||||||
|
}
|
||||||
|
if (totalDescriptors > gd.Capabilities.MaxPushDescriptors)
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
if (_handle != BufferHandle.Null)
|
if (_handle != BufferHandle.Null)
|
||||||
{
|
{
|
||||||
// May need to restride the vertex buffer.
|
// May need to restride the vertex buffer.
|
||||||
|
//
|
||||||
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0)
|
// Fix divide by zero when recovering from missed draw (Oct. 16 2024)
|
||||||
|
// (fixes crash in 'Baldo: The Guardian Owls' opening cutscene)
|
||||||
|
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && alignment != 0 && (_stride % alignment) != 0)
|
||||||
{
|
{
|
||||||
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);
|
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
public IProgram CreateProgram(ShaderSource[] sources, ShaderInfo info)
|
||||||
{
|
{
|
||||||
ProgramCount++;
|
ProgramCount++;
|
||||||
|
|
||||||
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
bool isCompute = sources.Length == 1 && sources[0].Stage == ShaderStage.Compute;
|
||||||
|
|
||||||
if (info.State.HasValue || isCompute)
|
if (info.State.HasValue || isCompute)
|
||||||
|
@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private int _width;
|
private int _width;
|
||||||
private int _height;
|
private int _height;
|
||||||
private bool _vsyncEnabled;
|
private VSyncMode _vSyncMode;
|
||||||
private bool _swapchainIsDirty;
|
private bool _swapchainIsDirty;
|
||||||
private VkFormat _format;
|
private VkFormat _format;
|
||||||
private AntiAliasing _currentAntiAliasing;
|
private AntiAliasing _currentAntiAliasing;
|
||||||
@ -139,7 +139,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ImageArrayLayers = 1,
|
ImageArrayLayers = 1,
|
||||||
PreTransform = capabilities.CurrentTransform,
|
PreTransform = capabilities.CurrentTransform,
|
||||||
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
CompositeAlpha = ChooseCompositeAlpha(capabilities.SupportedCompositeAlpha),
|
||||||
PresentMode = ChooseSwapPresentMode(presentModes, _vsyncEnabled),
|
PresentMode = ChooseSwapPresentMode(presentModes, _vSyncMode),
|
||||||
Clipped = true,
|
Clipped = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -279,9 +279,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, bool vsyncEnabled)
|
private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
if (!vsyncEnabled && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
|
if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr))
|
||||||
{
|
{
|
||||||
return PresentModeKHR.ImmediateKhr;
|
return PresentModeKHR.ImmediateKhr;
|
||||||
}
|
}
|
||||||
@ -634,9 +634,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ChangeVSyncMode(bool vsyncEnabled)
|
public override void ChangeVSyncMode(VSyncMode vSyncMode)
|
||||||
{
|
{
|
||||||
_vsyncEnabled = vsyncEnabled;
|
_vSyncMode = vSyncMode;
|
||||||
|
//present mode may change, so mark the swapchain for recreation
|
||||||
_swapchainIsDirty = true;
|
_swapchainIsDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public abstract void Dispose();
|
public abstract void Dispose();
|
||||||
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
public abstract void Present(ITexture texture, ImageCrop crop, Action swapBuffersCallback);
|
||||||
public abstract void SetSize(int width, int height);
|
public abstract void SetSize(int width, int height);
|
||||||
public abstract void ChangeVSyncMode(bool vsyncEnabled);
|
public abstract void ChangeVSyncMode(VSyncMode vSyncMode);
|
||||||
public abstract void SetAntiAliasing(AntiAliasing effect);
|
public abstract void SetAntiAliasing(AntiAliasing effect);
|
||||||
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
public abstract void SetScalingFilter(ScalingFilter scalerType);
|
||||||
public abstract void SetScalingFilterLevel(float scale);
|
public abstract void SetScalingFilterLevel(float scale);
|
||||||
|
@ -13,6 +13,7 @@ namespace Ryujinx.HLE.Generators
|
|||||||
var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver;
|
var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver;
|
||||||
CodeGenerator generator = new CodeGenerator();
|
CodeGenerator generator = new CodeGenerator();
|
||||||
|
|
||||||
|
generator.AppendLine("#nullable enable");
|
||||||
generator.AppendLine("using System;");
|
generator.AppendLine("using System;");
|
||||||
generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm");
|
generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm");
|
||||||
generator.EnterScope($"partial class IUserInterface");
|
generator.EnterScope($"partial class IUserInterface");
|
||||||
@ -58,6 +59,7 @@ namespace Ryujinx.HLE.Generators
|
|||||||
|
|
||||||
generator.LeaveScope();
|
generator.LeaveScope();
|
||||||
generator.LeaveScope();
|
generator.LeaveScope();
|
||||||
|
generator.AppendLine("#nullable disable");
|
||||||
context.AddSource($"IUserInterface.g.cs", generator.ToString());
|
context.AddSource($"IUserInterface.g.cs", generator.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||||
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
|
||||||
<IsRoslynComponent>true</IsRoslynComponent>
|
<IsRoslynComponent>true</IsRoslynComponent>
|
||||||
|
<DefaultItemExcludes>$(DefaultItemExcludes);._*</DefaultItemExcludes>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -21,6 +21,7 @@ using System.IO;
|
|||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.FileSystem
|
namespace Ryujinx.HLE.FileSystem
|
||||||
@ -474,6 +475,74 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
FinishInstallation(temporaryDirectory, registeredDirectory);
|
FinishInstallation(temporaryDirectory, registeredDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InstallKeys(string keysSource, string installDirectory)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(keysSource))
|
||||||
|
{
|
||||||
|
foreach (var filePath in Directory.EnumerateFiles(keysSource, "*.keys"))
|
||||||
|
{
|
||||||
|
VerifyKeysFile(filePath);
|
||||||
|
File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(keysSource))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Keys file does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInfo info = new(keysSource);
|
||||||
|
|
||||||
|
using FileStream file = File.OpenRead(keysSource);
|
||||||
|
|
||||||
|
switch (info.Extension)
|
||||||
|
{
|
||||||
|
case ".zip":
|
||||||
|
using (ZipArchive archive = ZipFile.OpenRead(keysSource))
|
||||||
|
{
|
||||||
|
InstallKeysFromZip(archive, installDirectory);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ".keys":
|
||||||
|
VerifyKeysFile(keysSource);
|
||||||
|
File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new InvalidFirmwarePackageException("Input file is not a valid key package");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InstallKeysFromZip(ZipArchive archive, string installDirectory)
|
||||||
|
{
|
||||||
|
string temporaryDirectory = Path.Combine(installDirectory, "temp");
|
||||||
|
if (Directory.Exists(temporaryDirectory))
|
||||||
|
{
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
}
|
||||||
|
Directory.CreateDirectory(temporaryDirectory);
|
||||||
|
foreach (var entry in archive.Entries)
|
||||||
|
{
|
||||||
|
if (Path.GetExtension(entry.FullName).Equals(".keys", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
string extractDestination = Path.Combine(temporaryDirectory, entry.Name);
|
||||||
|
entry.ExtractToFile(extractDestination, overwrite: true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
VerifyKeysFile(extractDestination);
|
||||||
|
File.Move(extractDestination, Path.Combine(installDirectory, entry.Name), true);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Directory.Delete(temporaryDirectory, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
private void FinishInstallation(string temporaryDirectory, string registeredDirectory)
|
||||||
{
|
{
|
||||||
if (Directory.Exists(registeredDirectory))
|
if (Directory.Exists(registeredDirectory))
|
||||||
@ -947,5 +1016,70 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void VerifyKeysFile(string filePath)
|
||||||
|
{
|
||||||
|
// Verify the keys file format refers to https://github.com/Thealexbarney/LibHac/blob/master/KEYS.md
|
||||||
|
string genericPattern = @"^[a-z0-9_]+ = [a-z0-9]+$";
|
||||||
|
string titlePattern = @"^[a-z0-9]{32} = [a-z0-9]{32}$";
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
// Read all lines from the file
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
string[] lines = File.ReadAllLines(filePath);
|
||||||
|
|
||||||
|
bool verified = false;
|
||||||
|
switch (fileName)
|
||||||
|
{
|
||||||
|
case "prod.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
case "title.keys":
|
||||||
|
verified = verifyKeys(lines, titlePattern);
|
||||||
|
break;
|
||||||
|
case "console.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
case "dev.keys":
|
||||||
|
verified = verifyKeys(lines, genericPattern);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new FormatException($"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.");
|
||||||
|
}
|
||||||
|
if (!verified)
|
||||||
|
{
|
||||||
|
throw new FormatException($"Invalid \"{filePath}\" file format.");
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"Keys file not found at \"{filePath}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool verifyKeys(string[] lines, string regex)
|
||||||
|
{
|
||||||
|
foreach (string line in lines)
|
||||||
|
{
|
||||||
|
if (!Regex.IsMatch(line, regex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AreKeysAlredyPresent(string pathToCheck)
|
||||||
|
{
|
||||||
|
string[] fileNames = { "prod.keys", "title.keys", "console.keys", "dev.keys" };
|
||||||
|
foreach (var file in fileNames)
|
||||||
|
{
|
||||||
|
if (File.Exists(Path.Combine(pathToCheck, file)))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,9 +223,10 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
KeySet ??= KeySet.CreateDefaultKeySet();
|
KeySet ??= KeySet.CreateDefaultKeySet();
|
||||||
|
|
||||||
string keyFile = null;
|
string prodKeyFile = null;
|
||||||
string titleKeyFile = null;
|
string titleKeyFile = null;
|
||||||
string consoleKeyFile = null;
|
string consoleKeyFile = null;
|
||||||
|
string devKeyFile = null;
|
||||||
|
|
||||||
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile)
|
if (AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile)
|
||||||
{
|
{
|
||||||
@ -236,13 +237,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
void LoadSetAtPath(string basePath)
|
void LoadSetAtPath(string basePath)
|
||||||
{
|
{
|
||||||
string localKeyFile = Path.Combine(basePath, "prod.keys");
|
string localProdKeyFile = Path.Combine(basePath, "prod.keys");
|
||||||
string localTitleKeyFile = Path.Combine(basePath, "title.keys");
|
string localTitleKeyFile = Path.Combine(basePath, "title.keys");
|
||||||
string localConsoleKeyFile = Path.Combine(basePath, "console.keys");
|
string localConsoleKeyFile = Path.Combine(basePath, "console.keys");
|
||||||
|
string localDevKeyFile = Path.Combine(basePath, "dev.keys");
|
||||||
|
|
||||||
if (File.Exists(localKeyFile))
|
if (File.Exists(localProdKeyFile))
|
||||||
{
|
{
|
||||||
keyFile = localKeyFile;
|
prodKeyFile = localProdKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(localTitleKeyFile))
|
if (File.Exists(localTitleKeyFile))
|
||||||
@ -254,9 +256,14 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
consoleKeyFile = localConsoleKeyFile;
|
consoleKeyFile = localConsoleKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (File.Exists(localDevKeyFile))
|
||||||
|
{
|
||||||
|
devKeyFile = localDevKeyFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalKeyReader.ReadKeyFile(KeySet, keyFile, titleKeyFile, consoleKeyFile, null);
|
ExternalKeyReader.ReadKeyFile(KeySet, prodKeyFile, devKeyFile, titleKeyFile, consoleKeyFile, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ImportTickets(IFileSystem fs)
|
public void ImportTickets(IFileSystem fs)
|
||||||
|
@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using System;
|
using System;
|
||||||
|
using VSyncMode = Ryujinx.Common.Configuration.VSyncMode;
|
||||||
|
|
||||||
namespace Ryujinx.HLE
|
namespace Ryujinx.HLE
|
||||||
{
|
{
|
||||||
@ -84,9 +85,14 @@ namespace Ryujinx.HLE
|
|||||||
internal readonly RegionCode Region;
|
internal readonly RegionCode Region;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the vertical sync in the SurfaceFlinger service.
|
/// Control the initial state of the present interval in the SurfaceFlinger service (previously Vsync).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal readonly bool EnableVsync;
|
internal readonly VSyncMode VSyncMode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Control the custom VSync interval, if enabled and active.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly int CustomVSyncInterval;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Control the initial state of the docked mode.
|
/// Control the initial state of the docked mode.
|
||||||
@ -164,6 +170,21 @@ namespace Ryujinx.HLE
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiplayerMode MultiplayerMode { internal get; set; }
|
public MultiplayerMode MultiplayerMode { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disable P2P mode
|
||||||
|
/// </summary>
|
||||||
|
public bool MultiplayerDisableP2p { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplayer Passphrase
|
||||||
|
/// </summary>
|
||||||
|
public string MultiplayerLdnPassphrase { internal get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LDN Server
|
||||||
|
/// </summary>
|
||||||
|
public string MultiplayerLdnServer { internal get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An action called when HLE force a refresh of output after docked mode changed.
|
/// An action called when HLE force a refresh of output after docked mode changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -180,7 +201,7 @@ namespace Ryujinx.HLE
|
|||||||
IHostUIHandler hostUIHandler,
|
IHostUIHandler hostUIHandler,
|
||||||
SystemLanguage systemLanguage,
|
SystemLanguage systemLanguage,
|
||||||
RegionCode region,
|
RegionCode region,
|
||||||
bool enableVsync,
|
VSyncMode vSyncMode,
|
||||||
bool enableDockedMode,
|
bool enableDockedMode,
|
||||||
bool enablePtc,
|
bool enablePtc,
|
||||||
bool enableInternetAccess,
|
bool enableInternetAccess,
|
||||||
@ -194,7 +215,11 @@ namespace Ryujinx.HLE
|
|||||||
float audioVolume,
|
float audioVolume,
|
||||||
bool useHypervisor,
|
bool useHypervisor,
|
||||||
string multiplayerLanInterfaceId,
|
string multiplayerLanInterfaceId,
|
||||||
MultiplayerMode multiplayerMode)
|
MultiplayerMode multiplayerMode,
|
||||||
|
bool multiplayerDisableP2p,
|
||||||
|
string multiplayerLdnPassphrase,
|
||||||
|
string multiplayerLdnServer,
|
||||||
|
int customVSyncInterval)
|
||||||
{
|
{
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
LibHacHorizonManager = libHacHorizonManager;
|
LibHacHorizonManager = libHacHorizonManager;
|
||||||
@ -207,7 +232,8 @@ namespace Ryujinx.HLE
|
|||||||
HostUIHandler = hostUIHandler;
|
HostUIHandler = hostUIHandler;
|
||||||
SystemLanguage = systemLanguage;
|
SystemLanguage = systemLanguage;
|
||||||
Region = region;
|
Region = region;
|
||||||
EnableVsync = enableVsync;
|
VSyncMode = vSyncMode;
|
||||||
|
CustomVSyncInterval = customVSyncInterval;
|
||||||
EnableDockedMode = enableDockedMode;
|
EnableDockedMode = enableDockedMode;
|
||||||
EnablePtc = enablePtc;
|
EnablePtc = enablePtc;
|
||||||
EnableInternetAccess = enableInternetAccess;
|
EnableInternetAccess = enableInternetAccess;
|
||||||
@ -222,6 +248,9 @@ namespace Ryujinx.HLE
|
|||||||
UseHypervisor = useHypervisor;
|
UseHypervisor = useHypervisor;
|
||||||
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
||||||
MultiplayerMode = multiplayerMode;
|
MultiplayerMode = multiplayerMode;
|
||||||
|
MultiplayerDisableP2p = multiplayerDisableP2p;
|
||||||
|
MultiplayerLdnPassphrase = multiplayerLdnPassphrase;
|
||||||
|
MultiplayerLdnServer = multiplayerLdnServer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.HOS.Applets.Browser;
|
using Ryujinx.HLE.HOS.Applets.Browser;
|
||||||
|
using Ryujinx.HLE.HOS.Applets.Cabinet;
|
||||||
|
using Ryujinx.HLE.HOS.Applets.Dummy;
|
||||||
using Ryujinx.HLE.HOS.Applets.Error;
|
using Ryujinx.HLE.HOS.Applets.Error;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
using System;
|
using System;
|
||||||
@ -26,9 +29,15 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
return new BrowserApplet(system);
|
return new BrowserApplet(system);
|
||||||
case AppletId.LibAppletOff:
|
case AppletId.LibAppletOff:
|
||||||
return new BrowserApplet(system);
|
return new BrowserApplet(system);
|
||||||
|
case AppletId.MiiEdit:
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Please use the MiiEdit inside File/Open Applet");
|
||||||
|
return new DummyApplet(system);
|
||||||
|
case AppletId.Cabinet:
|
||||||
|
return new CabinetApplet(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NotImplementedException($"{applet} applet is not implemented.");
|
Logger.Warning?.Print(LogClass.Application, $"Applet {applet} not implemented!");
|
||||||
|
return new DummyApplet(system);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
195
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
195
src/Ryujinx.HLE/HOS/Applets/Cabinet/CabinetApplet.cs
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.Cabinet
|
||||||
|
{
|
||||||
|
internal unsafe class CabinetApplet : IApplet
|
||||||
|
{
|
||||||
|
private readonly Horizon _system;
|
||||||
|
private AppletSession _normalSession;
|
||||||
|
|
||||||
|
public event EventHandler AppletStateChanged;
|
||||||
|
|
||||||
|
public CabinetApplet(Horizon system)
|
||||||
|
{
|
||||||
|
_system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||||
|
{
|
||||||
|
_normalSession = normalSession;
|
||||||
|
|
||||||
|
byte[] launchParams = _normalSession.Pop();
|
||||||
|
byte[] startParamBytes = _normalSession.Pop();
|
||||||
|
|
||||||
|
StartParamForAmiiboSettings startParam = IApplet.ReadStruct<StartParamForAmiiboSettings>(startParamBytes);
|
||||||
|
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceAm, $"CabinetApplet Start Type: {startParam.Type}");
|
||||||
|
|
||||||
|
switch (startParam.Type)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
StartNicknameAndOwnerSettings(ref startParam);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 3:
|
||||||
|
StartFormatter(ref startParam);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logger.Error?.Print(LogClass.ServiceAm, $"Unknown AmiiboSettings type: {startParam.Type}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the response
|
||||||
|
ReturnValueForAmiiboSettings returnValue = new()
|
||||||
|
{
|
||||||
|
AmiiboSettingsReturnFlag = (byte)AmiiboSettingsReturnFlag.HasRegisterInfo,
|
||||||
|
DeviceHandle = new DeviceHandle
|
||||||
|
{
|
||||||
|
Handle = 0 // Dummy device handle
|
||||||
|
},
|
||||||
|
RegisterInfo = startParam.RegisterInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push the response
|
||||||
|
_normalSession.Push(BuildResponse(returnValue));
|
||||||
|
AppletStateChanged?.Invoke(this, null);
|
||||||
|
|
||||||
|
_system.ReturnFocus();
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultCode GetResult()
|
||||||
|
{
|
||||||
|
_system.Device.System.NfpDevices.RemoveAt(0);
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartFormatter(ref StartParamForAmiiboSettings startParam)
|
||||||
|
{
|
||||||
|
// Initialize RegisterInfo
|
||||||
|
startParam.RegisterInfo = new RegisterInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartNicknameAndOwnerSettings(ref StartParamForAmiiboSettings startParam)
|
||||||
|
{
|
||||||
|
_system.Device.UIHandler.DisplayCabinetDialog(out string newName);
|
||||||
|
byte[] nameBytes = Encoding.UTF8.GetBytes(newName);
|
||||||
|
Array41<byte> nickName = new Array41<byte>();
|
||||||
|
nameBytes.CopyTo(nickName.AsSpan());
|
||||||
|
startParam.RegisterInfo.Nickname = nickName;
|
||||||
|
NfpDevice devicePlayer1 = new()
|
||||||
|
{
|
||||||
|
NpadIdType = NpadIdType.Player1,
|
||||||
|
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
|
||||||
|
State = NfpDeviceState.SearchingForTag,
|
||||||
|
};
|
||||||
|
_system.Device.System.NfpDevices.Add(devicePlayer1);
|
||||||
|
_system.Device.UIHandler.DisplayCabinetMessageDialog();
|
||||||
|
string amiiboId = string.Empty;
|
||||||
|
bool scanned = false;
|
||||||
|
while (!scanned)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _system.Device.System.NfpDevices.Count; i++)
|
||||||
|
{
|
||||||
|
if (_system.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
|
||||||
|
{
|
||||||
|
amiiboId = _system.Device.System.NfpDevices[i].AmiiboId;
|
||||||
|
scanned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VirtualAmiibo.UpdateNickName(amiiboId, newName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] BuildResponse(ReturnValueForAmiiboSettings returnValue)
|
||||||
|
{
|
||||||
|
int size = Unsafe.SizeOf<ReturnValueForAmiiboSettings>();
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
|
||||||
|
fixed (byte* bytesPtr = bytes)
|
||||||
|
{
|
||||||
|
Unsafe.Write(bytesPtr, returnValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ReadStruct<T>(byte[] data) where T : unmanaged
|
||||||
|
{
|
||||||
|
if (data.Length < Unsafe.SizeOf<T>())
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Not enough data to read the struct");
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed (byte* dataPtr = data)
|
||||||
|
{
|
||||||
|
return Unsafe.Read<T>(dataPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Structs
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct TagInfo
|
||||||
|
{
|
||||||
|
public fixed byte Data[0x58];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct StartParamForAmiiboSettings
|
||||||
|
{
|
||||||
|
public byte ZeroValue; // Left at zero by sdknso
|
||||||
|
public byte Type;
|
||||||
|
public byte Flags;
|
||||||
|
public byte AmiiboSettingsStartParamOffset28;
|
||||||
|
public ulong AmiiboSettingsStartParam0;
|
||||||
|
|
||||||
|
public TagInfo TagInfo; // Only enabled when flags bit 1 is set
|
||||||
|
public RegisterInfo RegisterInfo; // Only enabled when flags bit 2 is set
|
||||||
|
|
||||||
|
public fixed byte StartParamExtraData[0x20];
|
||||||
|
|
||||||
|
public fixed byte Reserved[0x24];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public unsafe struct ReturnValueForAmiiboSettings
|
||||||
|
{
|
||||||
|
public byte AmiiboSettingsReturnFlag;
|
||||||
|
private byte Padding1;
|
||||||
|
private byte Padding2;
|
||||||
|
private byte Padding3;
|
||||||
|
public DeviceHandle DeviceHandle;
|
||||||
|
public TagInfo TagInfo;
|
||||||
|
public RegisterInfo RegisterInfo;
|
||||||
|
public fixed byte IgnoredBySdknso[0x24];
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public struct DeviceHandle
|
||||||
|
{
|
||||||
|
public ulong Handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AmiiboSettingsReturnFlag : byte
|
||||||
|
{
|
||||||
|
Cancel = 0,
|
||||||
|
HasTagInfo = 2,
|
||||||
|
HasRegisterInfo = 4,
|
||||||
|
HasTagInfoAndRegisterInfo = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
43
src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs
Normal file
43
src/Ryujinx.HLE/HOS/Applets/Dummy/DummyApplet.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
namespace Ryujinx.HLE.HOS.Applets.Dummy
|
||||||
|
{
|
||||||
|
internal class DummyApplet : IApplet
|
||||||
|
{
|
||||||
|
private readonly Horizon _system;
|
||||||
|
private AppletSession _normalSession;
|
||||||
|
public event EventHandler AppletStateChanged;
|
||||||
|
public DummyApplet(Horizon system)
|
||||||
|
{
|
||||||
|
_system = system;
|
||||||
|
}
|
||||||
|
public ResultCode Start(AppletSession normalSession, AppletSession interactiveSession)
|
||||||
|
{
|
||||||
|
_normalSession = normalSession;
|
||||||
|
_normalSession.Push(BuildResponse());
|
||||||
|
AppletStateChanged?.Invoke(this, null);
|
||||||
|
_system.ReturnFocus();
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
private static T ReadStruct<T>(byte[] data) where T : struct
|
||||||
|
{
|
||||||
|
return MemoryMarshal.Read<T>(data.AsSpan());
|
||||||
|
}
|
||||||
|
private static byte[] BuildResponse()
|
||||||
|
{
|
||||||
|
using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
using BinaryWriter writer = new(stream);
|
||||||
|
writer.Write((ulong)ResultCode.Success);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
public ResultCode GetResult()
|
||||||
|
{
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,8 @@ namespace Ryujinx.HLE.HOS
|
|||||||
string displayVersion,
|
string displayVersion,
|
||||||
bool diskCacheEnabled,
|
bool diskCacheEnabled,
|
||||||
ulong codeAddress,
|
ulong codeAddress,
|
||||||
ulong codeSize);
|
ulong codeSize,
|
||||||
|
string cacheSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
|
||||||
@ -67,10 +68,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
string displayVersion,
|
string displayVersion,
|
||||||
bool diskCacheEnabled,
|
bool diskCacheEnabled,
|
||||||
ulong codeAddress,
|
ulong codeAddress,
|
||||||
ulong codeSize)
|
ulong codeSize,
|
||||||
|
string cacheSelector)
|
||||||
{
|
{
|
||||||
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
_cpuContext.PrepareCodeRange(codeAddress, codeSize);
|
||||||
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled);
|
return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled, cacheSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||||
|
@ -114,7 +114,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize);
|
DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize, "default"); //Ready for exefs profiles
|
||||||
|
|
||||||
return processContext;
|
return processContext;
|
||||||
}
|
}
|
||||||
|
@ -2463,7 +2463,7 @@ namespace Ryujinx.HLE.HOS.Diagnostics.Demangler
|
|||||||
return ParseIntegerLiteral("unsigned short");
|
return ParseIntegerLiteral("unsigned short");
|
||||||
case 'i':
|
case 'i':
|
||||||
_position++;
|
_position++;
|
||||||
return ParseIntegerLiteral("");
|
return ParseIntegerLiteral(string.Empty);
|
||||||
case 'j':
|
case 'j':
|
||||||
_position++;
|
_position++;
|
||||||
return ParseIntegerLiteral("u");
|
return ParseIntegerLiteral("u");
|
||||||
|
@ -116,18 +116,13 @@ namespace Ryujinx.HLE.HOS
|
|||||||
private readonly Dictionary<ulong, ModCache> _appMods; // key is ApplicationId
|
private readonly Dictionary<ulong, ModCache> _appMods; // key is ApplicationId
|
||||||
private PatchCache _patches;
|
private PatchCache _patches;
|
||||||
|
|
||||||
private static readonly EnumerationOptions _dirEnumOptions;
|
private static readonly EnumerationOptions _dirEnumOptions = new()
|
||||||
|
|
||||||
static ModLoader()
|
|
||||||
{
|
{
|
||||||
_dirEnumOptions = new EnumerationOptions
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
{
|
MatchType = MatchType.Simple,
|
||||||
MatchCasing = MatchCasing.CaseInsensitive,
|
RecurseSubdirectories = false,
|
||||||
MatchType = MatchType.Simple,
|
ReturnSpecialDirectories = false,
|
||||||
RecurseSubdirectories = false,
|
};
|
||||||
ReturnSpecialDirectories = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModLoader()
|
public ModLoader()
|
||||||
{
|
{
|
||||||
@ -169,7 +164,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
foreach (var modDir in dir.EnumerateDirectories())
|
foreach (var modDir in dir.EnumerateDirectories())
|
||||||
{
|
{
|
||||||
types.Clear();
|
types.Clear();
|
||||||
Mod<DirectoryInfo> mod = new("", null, true);
|
Mod<DirectoryInfo> mod = new(string.Empty, null, true);
|
||||||
|
|
||||||
if (StrEquals(RomfsDir, modDir.Name))
|
if (StrEquals(RomfsDir, modDir.Name))
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
||||||
{
|
{
|
||||||
@ -25,5 +26,14 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE
|
|||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CommandCmif(350)]
|
||||||
|
// OpenSystemApplicationProxy(u64, pid, handle<copy>) -> object<nn::am::service::IApplicationProxy>
|
||||||
|
public ResultCode OpenSystemApplicationProxy(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IApplicationProxy(context.Request.HandleDesc.PId));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,23 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
|||||||
class IServiceCreator : IpcService
|
class IServiceCreator : IpcService
|
||||||
{
|
{
|
||||||
public IServiceCreator(ServiceCtx context) { }
|
public IServiceCreator(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// CreateNetworkService(pid, u64, u32) -> object<nn::ldn::detail::ISfService>
|
||||||
|
public ResultCode CreateNetworkService(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new ISfService(context));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(8)]
|
||||||
|
// CreateNetworkServiceMonitor(pid, u64) -> object<nn::ldn::detail::ISfServiceMonitor>
|
||||||
|
public ResultCode CreateNetworkServiceMonitor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new ISfServiceMonitor(context));
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs
Normal file
45
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfService.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
||||||
|
{
|
||||||
|
class ISfService : IpcService
|
||||||
|
{
|
||||||
|
public ISfService(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// Initialize()
|
||||||
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(0);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(768)]
|
||||||
|
// CreateGroup(buffer<nn::lp2p::GroupInfo, 0x31)
|
||||||
|
public ResultCode CreateGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1536)]
|
||||||
|
// SendToOtherGroup(nn::lp2p::MacAddress, nn::lp2p::GroupId, s16, s16, u32, buffer<unknown, 0x21>)
|
||||||
|
public ResultCode SendToOtherGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(1544)]
|
||||||
|
// RecvFromOtherGroup(u32, buffer<unknown, 0x22>) -> (nn::lp2p::MacAddress, u16, s16, u32, s32)
|
||||||
|
public ResultCode RecvFromOtherGroup(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
Normal file
86
src/Ryujinx.HLE/HOS/Services/Ldn/Lp2p/ISfServiceMonitor.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Ldn.Lp2p
|
||||||
|
{
|
||||||
|
class ISfServiceMonitor : IpcService
|
||||||
|
{
|
||||||
|
private readonly KEvent _stateChangeEvent;
|
||||||
|
private readonly KEvent _jointEvent;
|
||||||
|
private int _stateChangeEventHandle = 0;
|
||||||
|
private int _jointEventHandle = 0;
|
||||||
|
|
||||||
|
public ISfServiceMonitor(ServiceCtx context)
|
||||||
|
{
|
||||||
|
_stateChangeEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
|
_jointEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// Initialize()
|
||||||
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(0);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(256)]
|
||||||
|
// AttachNetworkInterfaceStateChangeEvent() -> handle<copy>
|
||||||
|
public ResultCode AttachNetworkInterfaceStateChangeEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_stateChangeEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(288)]
|
||||||
|
// GetGroupInfo(buffer<nn::lp2p::GroupInfo, 0x32>)
|
||||||
|
public ResultCode GetGroupInfo(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(296)]
|
||||||
|
// GetGroupInfo2(buffer<nn::lp2p::GroupInfo, 0x32>, buffer<nn::lp2p::GroupInfo, 0x31>)
|
||||||
|
public ResultCode GetGroupInfo2(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(312)]
|
||||||
|
// GetIpConfig(buffer<unknown<0x100>, 0x1a>)
|
||||||
|
public ResultCode GetIpConfig(ServiceCtx context)
|
||||||
|
{
|
||||||
|
Logger.Stub?.PrintStub(LogClass.ServiceLdn);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CommandCmif(328)]
|
||||||
|
// AttachNetworkInterfaceStateChangeEvent() -> handle<copy>
|
||||||
|
public ResultCode AttachJoinEvent(ServiceCtx context)
|
||||||
|
{
|
||||||
|
if (context.Process.HandleTable.GenerateHandle(_jointEvent.ReadableEvent, out _jointEventHandle) != Result.Success)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Out of handles!");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_jointEventHandle);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user