diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index ba372c6bc..18e00b6d2 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -1,4 +1,4 @@ -name: Canary release job +name: Canary CI on: workflow_dispatch: @@ -19,7 +19,6 @@ concurrency: release env: POWERSHELL_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 - RYUJINX_BASE_VERSION: "1.3" RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary" RELEASE: 1 @@ -30,8 +29,8 @@ jobs: strategy: matrix: platform: - - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - #- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } + - { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 } + #- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } steps: @@ -44,11 +43,25 @@ jobs: - name: Overwrite csc problem matcher run: echo "::add-matcher::.github/csc.json" + - name: Install 7zip + run: | + sudo apt install -y 7zip + + - name: Install gli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' + chmod +x gli + mv gli $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 "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT + echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT shell: bash @@ -69,33 +82,20 @@ jobs: dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained - name: Packing Windows builds - if: matrix.platform.os == 'windows-latest' + if: contains(matrix.platform.name, 'win') run: | pushd publish rm libarmeilleure-jitsupport.dylib 7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish popd - - gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' - - ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" + + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install GitLabCli - if: matrix.platform.os == 'ubuntu-latest' - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Packing Linux builds - if: matrix.platform.os == 'ubuntu-latest' + if: contains(matrix.platform.name, 'linux') run: | pushd publish rm libarmeilleure-jitsupport.dylib @@ -103,11 +103,11 @@ jobs: tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz shell: bash - name: Build AppImage (Linux) - if: matrix.platform.os == 'ubuntu-latest' + if: contains(matrix.platform.name, 'linux') run: | BUILD_VERSION="${{ steps.version_info.outputs.build_version }}" PLATFORM_NAME="${{ matrix.platform.name }}" @@ -139,8 +139,8 @@ jobs: pushd publish_appimage mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage popd - - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage" + + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage shell: bash macos_release: @@ -159,10 +159,10 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh 17 - - name: Install GitLabCli + - name: Install gli run: | mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' chmod +x gli mv gli $HOME/.bin/ echo "$HOME/.bin" >> $GITHUB_PATH @@ -183,9 +183,10 @@ jobs: - 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 "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT + echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash - name: Configure for release run: | @@ -200,7 +201,7 @@ jobs: - 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 - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz create_gitlab_release: name: Create GitLab Release @@ -210,37 +211,41 @@ jobs: - release steps: - uses: actions/checkout@v4 - - - 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: Install GitLabCli + + - name: Install gli run: | mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' chmod +x gli mv gli $HOME/.bin/ echo "$HOME/.bin" >> $GITHUB_PATH env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get version info + id: version_info + run: | + echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT + echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash - name: Create tag run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "Canary-${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}" + gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} - name: Create release run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})" + gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})" - name: Send notification webhook run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|FF4500|${{ secrets.CANARY_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false" + gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4 - name: Notify update server of new builds run: | - curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=canary' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}' + gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary + + - name: Advance to the next version + run: | + gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Canary diff --git a/.github/workflows/debug_release.yml b/.github/workflows/debug_release.yml deleted file mode 100644 index b166adb61..000000000 --- a/.github/workflows/debug_release.yml +++ /dev/null @@ -1,224 +0,0 @@ -name: Release job (Debug) - -on: - workflow_dispatch: - inputs: {} - -concurrency: release - -env: - POWERSHELL_TELEMETRY_OPTOUT: 1 - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - RYUJINX_BASE_VERSION: "1.3" - RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release" - RELEASE: 1 - -jobs: - 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: win-arm64, os: windows-latest, zip_os_name: win_arm64 } - - { 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 }} + 10))" >> $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_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 -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained - - - name: Packing Windows builds - if: matrix.platform.os == 'windows-latest' - run: | - pushd publish - rm libarmeilleure-jitsupport.dylib - 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish - popd - - gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' - - ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install GitLabCli - if: matrix.platform.os == 'ubuntu-latest' - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Packing Linux builds - if: matrix.platform.os == 'ubuntu-latest' - run: | - pushd publish - rm libarmeilleure-jitsupport.dylib - chmod +x Ryujinx.sh Ryujinx - tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish - popd - - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" - 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 - - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage" - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync" - shell: bash - - macos_release: - name: Release MacOS universal - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-dotnet@v4 - with: - global-json-file: global.json - - - name: Setup LLVM 17 - run: | - wget https://apt.llvm.org/llvm.sh - chmod +x llvm.sh - sudo ./llvm.sh 17 - - - name: Install GitLabCli - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - 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 }} + 10))" >> $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_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 ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" - - create_gitlab_release: - name: Create GitLab Release - runs-on: ubuntu-24.04 - needs: - - macos_release - - release - steps: - - uses: actions/checkout@v4 - - - name: Get version info - id: version_info - run: | - echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} + 10))" >> $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: Install GitLabCli - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create release - run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79b51d8e9..3c1b6f2de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,18 @@ -name: Release job +name: Stable CI on: workflow_dispatch: - inputs: {} + inputs: + is_bugfix_release: + description: "Bug fix release: If checked, this will increment the third number for only Stable, and leave the Major version alone for both Stable and Canary." + required: true + type: boolean concurrency: release env: POWERSHELL_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1 - RYUJINX_BASE_VERSION: "1.3" RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release" RELEASE: 1 @@ -20,8 +23,8 @@ jobs: strategy: matrix: platform: - - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - #- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } + - { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 } + #- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } steps: @@ -33,12 +36,30 @@ jobs: - name: Overwrite csc problem matcher run: echo "::add-matcher::.github/csc.json" + + - name: Install 7zip + run: | + sudo apt install -y 7zip + + - name: Install gli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' + chmod +x gli + mv gli $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 + if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then + echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT + else + echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT + fi + echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT shell: bash @@ -58,47 +79,34 @@ jobs: dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained - name: Packing Windows builds - if: matrix.platform.os == 'windows-latest' + if: contains(matrix.platform.name, 'win') run: | pushd publish rm libarmeilleure-jitsupport.dylib 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish popd - - gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' - - ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" + + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Install GitLabCli - if: matrix.platform.os == 'ubuntu-latest' - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Packing Linux builds - if: matrix.platform.os == 'ubuntu-latest' + if: contains(matrix.platform.name, 'linux') run: | pushd publish rm libarmeilleure-jitsupport.dylib chmod +x Ryujinx.sh Ryujinx tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd - - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" + + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build AppImage (Linux) - if: matrix.platform.os == 'ubuntu-latest' + if: contains(matrix.platform.name, 'linux') run: | BUILD_VERSION="${{ steps.version_info.outputs.build_version }}" PLATFORM_NAME="${{ matrix.platform.name }}" @@ -131,7 +139,7 @@ jobs: mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage popd - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage" + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage shell: bash macos_release: @@ -150,10 +158,10 @@ jobs: chmod +x llvm.sh sudo ./llvm.sh 17 - - name: Install GitLabCli + - name: Install gli run: | mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' chmod +x gli mv gli $HOME/.bin/ echo "$HOME/.bin" >> $GITHUB_PATH @@ -174,9 +182,14 @@ jobs: - 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 + if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then + echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT + else + echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT + fi + echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash - name: Configure for release run: | @@ -189,7 +202,8 @@ jobs: - name: Publish macOS Ryujinx run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" + + gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r 5 -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz create_gitlab_release: name: Create GitLab Release @@ -200,32 +214,45 @@ jobs: steps: - uses: actions/checkout@v4 - - 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: Install GitLabCli + - name: Install gli run: | mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64' chmod +x gli mv gli $HOME/.bin/ echo "$HOME/.bin" >> $GITHUB_PATH env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Get version info + id: version_info + run: | + if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then + echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT + else + echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT + fi + echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT + shell: bash + - name: Create release run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|msd:${{ steps.version_info.outputs.build_version }}" - + gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}" + - name: Send notification webhook run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=SendUpdateMessage "${{ steps.version_info.outputs.build_version }}|32cd32|${{ secrets.STABLE_DISCORD_WEBHOOK }}|https://avatars.githubusercontent.com/u/192939710?s=200&v=4|false" - + gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4 + - name: Notify update server of new builds run: | - curl 'https://update.ryujinx.app/api/v1/admin/refresh_cache?rc=stable' -X PATCH -H 'accept: */*' -H 'Authorization: ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }}' + gli refresh-version-cache -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable + + - name: Advance to the next version + run: | + if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then + gli advance-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} + else + gli increment-version -T ${{ secrets.UPDATE_SERVER_ADMIN_TOKEN }} -c Stable + fi diff --git a/.gitignore b/.gitignore index 0c46c50c0..6f887e638 100644 --- a/.gitignore +++ b/.gitignore @@ -100,6 +100,7 @@ DocProject/Help/html # Click-Once directory publish/ +RyubingMaintainerTools/ # Publish Web Output *.Publish.xml diff --git a/assets/Languages.json b/assets/Languages.json new file mode 100644 index 000000000..e921b6e30 --- /dev/null +++ b/assets/Languages.json @@ -0,0 +1,24 @@ +{ + "Languages": { + "ar_SA": "اَلْعَرَبِيَّةُ", + "de_DE": "Deutsch", + "el_GR": "Ελληνικά", + "en_US": "English (US)", + "es_ES": "Español (ES)", + "fr_FR": "Français (FR)", + "he_IL": "עִברִית", + "it_IT": "Italiano", + "ja_JP": "日本語", + "ko_KR": "한국어", + "no_NO": "Norsk", + "pl_PL": "Polski", + "pt_BR": "Português (BR)", + "ru_RU": "Русский", + "sv_SE": "Svenska", + "th_TH": "ภาษาไทย", + "tr_TR": "Türkçe", + "uk_UA": "Українська", + "zh_CN": "简体中文", + "zh_TW": "繁體中文 (台灣)" + } +} \ No newline at end of file diff --git a/assets/Locales.md b/assets/Locales.md new file mode 100644 index 000000000..39b7e4d46 --- /dev/null +++ b/assets/Locales.md @@ -0,0 +1,60 @@ + +# Ryubing Locales + +Ryubing Locales uses a custom format, which uses a file for defining the supported languages and a folder of json files for the locales themselves. +Each json file holds the locales for a specific part of the emulator, e.g. the Setup Wizard locales are in `SetupWizard.json`, and each locale entry in the file includes all the supported languages in the same place. + +## Languages +in the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator. +The file includes a table of the langauge codes and their langauge names. + + #Example of the format for Languages.json + { + "Languages": { + "ar_SA": "اَلْعَرَبِيَّةُ", + "en_US": "English (US)", + ... + "zh_TW": "繁體中文 (台灣)" + } + } + +## Locales +in the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator. +Each json file holds locales for a specific part of the emulator in a large array of locale objects. +Each locale is made up an ID used for lookup and a list of the languages and their matching translations. +Any empty string or null value will automatically use the English translation instead in the emulator. + +### Format +When adding a new locale, you just need to add the ID and the en_US language translation, then the validation system will add default values for the rest of languages automatically, when rebuilding the project. +If you want to signal that a translation is supposed to match the English translation, you just have to replace the empty string with `null`. +When you want to check what translations are missing for a language just search for `"": ""`, e.g: `"en_US": ""` (but with any other language, as English will never be missing translations). + +### Legacy file (Root.json) +Currently all older locales are stored in `Root.json`, but they are slowly being moved into newer, more descriptive json files, to make the locale system more accessible. +Do **not** add new locales to `Root.json`. +If no json file exists for the specific part of the emulator you're working on, you should instead add a new json file for that part. + + #Example of the format for Root.json + { + "Locales": [ + { + "ID": "MenuBarActionsOpenMiiEditor", + "Translations": { + "ar_SA": "", + "en_US": "Mii Editor", + ... + "zh_TW": "Mii 編輯器" + } + }, + { + "ID": "KeyNumber9", + "Translations": { + "ar_SA": "٩", + "en_US": "9", + ... + "zh_TW": null + } + } + ] + } + \ No newline at end of file diff --git a/assets/locales.json b/assets/Locales/Root.json similarity index 99% rename from assets/locales.json rename to assets/Locales/Root.json index 8899bf692..aa8937247 100644 --- a/assets/locales.json +++ b/assets/Locales/Root.json @@ -1,72 +1,5 @@ { - "Info": { - "Format1": "The Locale file uses a custom Unified format.", - "Format2": "The file starts with a list of all the supported languages.", - "Format3": "Each locale is made up an ID used for lookup and a list", - "Format4": "of the languages and their matching translations.", - "Format5": "When adding a new locale you just need to add the ID and", - "Format6": "the en_US language translation, then the validation system", - "Format7": "will add the rest of the languages automatically on rebuild.", - "Format8": "By default the languages will be added with an empty string.", - "Format9": "Any empty string or null value will automatically match the", - "Format10": "English translation.", - "Format11": "If you want to signal that a translation is supposed to", - "Format12": "match the English translation, you just have to replace the", - "Format13": "empty string with null.", - "Format14": "Translators who want to check what translations are missing", - "Format15": "for their language just need to search for:", - "Format16": "{'lang_code': ''} with double quotes instead of single", - "Format17": "e.g: {'en_US': ''} (but with any other language as English", - "Format18": "will never be missing translations)." - }, - "Languages": [ - "ar_SA", - "de_DE", - "el_GR", - "en_US", - "es_ES", - "fr_FR", - "he_IL", - "it_IT", - "ja_JP", - "ko_KR", - "no_NO", - "pl_PL", - "pt_BR", - "ru_RU", - "sv_SE", - "th_TH", - "tr_TR", - "uk_UA", - "zh_CN", - "zh_TW" - ], "Locales": [ - { - "ID": "Language", - "Translations": { - "ar_SA": "اَلْعَرَبِيَّةُ", - "de_DE": "Deutsch", - "el_GR": "Ελληνικά", - "en_US": "English (US)", - "es_ES": "Español (ES)", - "fr_FR": "Français (FR)", - "he_IL": "עִברִית", - "it_IT": "Italiano", - "ja_JP": "日本語", - "ko_KR": "한국어", - "no_NO": "Norsk", - "pl_PL": "Polski", - "pt_BR": "Português (BR)", - "ru_RU": "Русский", - "sv_SE": "Svenska", - "th_TH": "ภาษาไทย", - "tr_TR": "Türkçe", - "uk_UA": "Українська", - "zh_CN": "简体中文", - "zh_TW": "繁體中文 (台灣)" - } - }, { "ID": "MenuBarActionsOpenMiiEditor", "Translations": { diff --git a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs index 0c74f1e7b..b1e61f6c1 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter2.cs @@ -19,6 +19,11 @@ namespace Ryujinx.Audio.Renderer.Parameter.Effect /// The output channel indices that will be used by the . /// public Array6 Output; + + /// + /// Reserved/unused. + /// + private readonly uint _padding; /// /// Biquad filter numerator (b0, b1, b2). diff --git a/src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs b/src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs index a07f0c4ae..a97e7b409 100644 --- a/src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs +++ b/src/Ryujinx.BuildValidationTasks/LocalesValidationTask.cs @@ -11,9 +11,7 @@ namespace Ryujinx.BuildValidationTasks { static readonly JsonSerializerOptions _jsonOptions = new() { - WriteIndented = true, - NewLine = "\n", - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + WriteIndented = true, NewLine = "\n", Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; public LocalesValidationTask() { } @@ -22,77 +20,116 @@ namespace Ryujinx.BuildValidationTasks { Console.WriteLine("Running Locale Validation Task..."); - string path = projectPath + "assets/locales.json"; + bool encounteredIssue = false; + string langPath = projectPath + "assets/Languages.json"; string data; - using (StreamReader sr = new(path)) + using (StreamReader sr = new(langPath)) { data = sr.ReadToEnd(); } - LocalesJson json; - if (isGitRunner && data.Contains("\r\n")) - throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix..."); + throw new FormatException("Languages.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix..."); + + LanguagesJson langJson; try { - json = JsonSerializer.Deserialize(data); - + langJson = JsonSerializer.Deserialize(data); } catch (JsonException e) { throw new JsonException(e.Message); //shorter and easier stacktrace } - bool encounteredIssue = false; - - for (int i = 0; i < json.Locales.Count; i++) + foreach ((string code, string lang) in langJson.Languages) { - LocalesEntry locale = json.Locales[i]; - - foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang))) + if (string.IsNullOrEmpty(lang)) { - encounteredIssue = true; - - if (!isGitRunner) - { - locale.Translations.Add(langCode, string.Empty); - Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'"); - } - else - { - Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!"); - } + throw new JsonException($"{code} language name missing!"); } - - foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"])) - { - encounteredIssue = true; - - if (!isGitRunner) - { - locale.Translations[langCode] = string.Empty; - Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it..."); - } - else - { - Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!"); - } - } - - locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value); - json.Locales[i] = locale; } - if (isGitRunner && encounteredIssue) - throw new JsonException("1 or more locales are invalid! Rebuild locally to fix..."); + string folderPath = projectPath + "assets/Locales/"; - string jsonString = JsonSerializer.Serialize(json, _jsonOptions); + string[] paths = Directory.GetFiles(folderPath, "*.json", SearchOption.AllDirectories); - using (StreamWriter sw = new(path)) + foreach (string path in paths) { - sw.Write(jsonString); + using (StreamReader sr = new(path)) + { + data = sr.ReadToEnd(); + } + + if (isGitRunner && data.Contains("\r\n")) + throw new FormatException($"{Path.GetFileName(path)} is using CRLF line endings! It should be using LF line endings, rebuild locally to fix..."); + + LocalesJson json; + + try + { + json = JsonSerializer.Deserialize(data); + } + catch (JsonException e) + { + throw new JsonException(e.Message); //shorter and easier stacktrace + } + + + for (int i = 0; i < json.Locales.Count; i++) + { + LocalesEntry locale = json.Locales[i]; + + foreach (string langCode in + langJson.Languages.Keys.Where(lang => !locale.Translations.ContainsKey(lang))) + { + encounteredIssue = true; + + if (!isGitRunner) + { + locale.Translations.Add(langCode, string.Empty); + Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'"); + } + else + { + Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!"); + } + } + + foreach (string langCode in langJson.Languages.Keys.Where(lang => + locale.Translations.ContainsKey(lang) && lang != "en_US" && + locale.Translations[lang] == locale.Translations["en_US"])) + { + encounteredIssue = true; + + if (!isGitRunner) + { + locale.Translations[langCode] = string.Empty; + Console.WriteLine( + $"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it..."); + } + else + { + Console.WriteLine( + $"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!"); + } + } + + locale.Translations = locale.Translations.OrderBy(pair => pair.Key) + .ToDictionary(pair => pair.Key, pair => pair.Value); + json.Locales[i] = locale; + } + + if (isGitRunner && encounteredIssue) + throw new JsonException("1 or more locales are invalid! Rebuild locally to fix..."); + + string jsonString = JsonSerializer.Serialize(json, _jsonOptions); + + using (StreamWriter sw = new(path)) + { + sw.Write(jsonString); + } } Console.WriteLine("Finished Locale Validation Task!"); @@ -100,10 +137,13 @@ namespace Ryujinx.BuildValidationTasks return true; } + struct LanguagesJson + { + public Dictionary Languages { get; set; } + } + struct LocalesJson { - public Dictionary Info { get; set; } - public List Languages { get; set; } public List Locales { get; set; } } diff --git a/src/Ryujinx.Common/Configuration/AntiAliasing.cs b/src/Ryujinx.Common/Configuration/AntiAliasing.cs index 5e3e1b891..1510c23ee 100644 --- a/src/Ryujinx.Common/Configuration/AntiAliasing.cs +++ b/src/Ryujinx.Common/Configuration/AntiAliasing.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AntiAliasing { None, diff --git a/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs b/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs index f3a9e1646..23ecd5870 100644 --- a/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs +++ b/src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AspectRatio { Fixed4x3, diff --git a/src/Ryujinx.Common/Configuration/BackendThreading.cs b/src/Ryujinx.Common/Configuration/BackendThreading.cs index 4fbb56bcb..198c2eed1 100644 --- a/src/Ryujinx.Common/Configuration/BackendThreading.cs +++ b/src/Ryujinx.Common/Configuration/BackendThreading.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum BackendThreading { Auto, diff --git a/src/Ryujinx.Common/Configuration/GraphicsBackend.cs b/src/Ryujinx.Common/Configuration/GraphicsBackend.cs index e3b4f91b0..ca6298eb5 100644 --- a/src/Ryujinx.Common/Configuration/GraphicsBackend.cs +++ b/src/Ryujinx.Common/Configuration/GraphicsBackend.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum GraphicsBackend { Vulkan, diff --git a/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs b/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs index dfe28405c..707715bbb 100644 --- a/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs +++ b/src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum GraphicsDebugLevel { None, diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs index 3e317c47c..2d4c23ea7 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/GamepadInputId.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid.Controller { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum GamepadInputId : byte { Unbound, diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs index fd8391289..fa23e00d3 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/Motion/MotionInputBackendType.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid.Controller.Motion { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum MotionInputBackendType : byte { Invalid, diff --git a/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs b/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs index 8f9539c46..6e9920edd 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Controller/StickInputId.cs @@ -1,15 +1,14 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid.Controller { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum StickInputId : byte { Unbound, Left, Right, - + Count, } } diff --git a/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs b/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs index cf61b79fd..904c6d13d 100644 --- a/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs +++ b/src/Ryujinx.Common/Configuration/Hid/ControllerType.cs @@ -1,4 +1,3 @@ -using Ryujinx.Common.Utilities; using System; using System.Text.Json.Serialization; @@ -6,7 +5,7 @@ namespace Ryujinx.Common.Configuration.Hid { // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical [Flags] - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum ControllerType { None, diff --git a/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs index b8cfcae2d..c3336dc64 100644 --- a/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs +++ b/src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum InputBackendType { Invalid, diff --git a/src/Ryujinx.Common/Configuration/Hid/Key.cs b/src/Ryujinx.Common/Configuration/Hid/Key.cs index e3dd8e50c..cf0f4c076 100644 --- a/src/Ryujinx.Common/Configuration/Hid/Key.cs +++ b/src/Ryujinx.Common/Configuration/Hid/Key.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum Key { Unknown, diff --git a/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs b/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs index 05e8f3fa4..4675486ef 100644 --- a/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs +++ b/src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs @@ -1,10 +1,9 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration.Hid { // This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum PlayerIndex { Player1 = 0, diff --git a/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs b/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs index 93031928f..83d1e8ae2 100644 --- a/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs +++ b/src/Ryujinx.Common/Configuration/MemoryManagerMode.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum MemoryManagerMode : byte { SoftwarePageTable, diff --git a/src/Ryujinx.Common/Configuration/ScalingFilter.cs b/src/Ryujinx.Common/Configuration/ScalingFilter.cs index 474685d49..9040b1be0 100644 --- a/src/Ryujinx.Common/Configuration/ScalingFilter.cs +++ b/src/Ryujinx.Common/Configuration/ScalingFilter.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum ScalingFilter { Bilinear, diff --git a/src/Ryujinx.Common/Logging/LogClass.cs b/src/Ryujinx.Common/Logging/LogClass.cs index eb0821db6..7c6810599 100644 --- a/src/Ryujinx.Common/Logging/LogClass.cs +++ b/src/Ryujinx.Common/Logging/LogClass.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Logging { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum LogClass { Application, diff --git a/src/Ryujinx.Common/Logging/LogLevel.cs b/src/Ryujinx.Common/Logging/LogLevel.cs index 54261cee5..282b07111 100644 --- a/src/Ryujinx.Common/Logging/LogLevel.cs +++ b/src/Ryujinx.Common/Logging/LogLevel.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Common.Logging { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum LogLevel { Debug, diff --git a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs index 834210e07..88f5956e9 100644 --- a/src/Ryujinx.Common/Memory/MemoryStreamManager.cs +++ b/src/Ryujinx.Common/Memory/MemoryStreamManager.cs @@ -7,6 +7,9 @@ namespace Ryujinx.Common.Memory { private static readonly RecyclableMemoryStreamManager _shared = new(); + private static readonly ObjectPool _streamPool = + new(() => new RecyclableMemoryStream(_shared, Guid.NewGuid(), null, 0)); + /// /// We don't expose the RecyclableMemoryStreamManager directly because version 2.x /// returns them as MemoryStream. This Shared class is here to a) offer only the GetStream() versions we use @@ -19,7 +22,12 @@ namespace Ryujinx.Common.Memory /// /// A RecyclableMemoryStream public static RecyclableMemoryStream GetStream() - => new(_shared); + { + RecyclableMemoryStream stream = _streamPool.Allocate(); + stream.SetLength(0); + + return stream; + } /// /// Retrieve a new MemoryStream object with the contents copied from the provided @@ -55,7 +63,8 @@ namespace Ryujinx.Common.Memory RecyclableMemoryStream stream = null; try { - stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); + stream = _streamPool.Allocate(); + stream.SetLength(0); stream.Write(buffer); stream.Position = 0; return stream; @@ -83,7 +92,8 @@ namespace Ryujinx.Common.Memory RecyclableMemoryStream stream = null; try { - stream = new RecyclableMemoryStream(_shared, id, tag, count); + stream = _streamPool.Allocate(); + stream.SetLength(0); stream.Write(buffer, offset, count); stream.Position = 0; return stream; @@ -94,6 +104,11 @@ namespace Ryujinx.Common.Memory throw; } } + + public static void ReleaseStream(RecyclableMemoryStream stream) + { + _streamPool.Release(stream); + } } } } diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs index 45bb7d537..35cfa0a69 100644 --- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -127,7 +127,7 @@ namespace Ryujinx.Common public static string[] GetAllAvailableResources(string path, string ext = "") { return ResolveManifestPath(path).Item1.GetManifestResourceNames() - .Where(r => r.EndsWith(ext)) + .Where(r => r.StartsWith(path.Replace('/', '.')) && r.EndsWith(ext)) .ToArray(); } diff --git a/src/Ryujinx.Common/Utilities/OsUtils.cs b/src/Ryujinx.Common/Utilities/OsUtils.cs index a0791b092..29c6e187c 100644 --- a/src/Ryujinx.Common/Utilities/OsUtils.cs +++ b/src/Ryujinx.Common/Utilities/OsUtils.cs @@ -20,5 +20,21 @@ namespace Ryujinx.Common.Utilities Debug.Assert(res != -1); } } + + // "dumpable" attribute of the calling process + private const int PR_SET_DUMPABLE = 4; + + [DllImport("libc", SetLastError = true)] + private static extern int prctl(int option, int arg2); + + public static void SetCoreDumpable(bool dumpable) + { + if (OperatingSystem.IsLinux()) + { + int dumpableInt = dumpable ? 1 : 0; + int result = prctl(PR_SET_DUMPABLE, dumpableInt); + Debug.Assert(result == 0); + } + } } } diff --git a/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs b/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs deleted file mode 100644 index d7eb3d556..000000000 --- a/src/Ryujinx.Common/Utilities/TypedStringEnumConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -#nullable enable -using Ryujinx.Common.Logging; -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace Ryujinx.Common.Utilities -{ - /// - /// Specifies that value of will be serialized as string in JSONs - /// - /// - /// Trimming friendly alternative to . - /// Get rid of this converter if dotnet supports similar functionality out of the box. - /// - /// Type of enum to serialize - public sealed class TypedStringEnumConverter : JsonConverter where TEnum : struct, Enum - { - public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - string? enumValue = reader.GetString(); - - if (Enum.TryParse(enumValue, out TEnum value)) - { - return value; - } - - Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\""); - return default; - } - - public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } -} diff --git a/src/Ryujinx.Graphics.GAL/IPipeline.cs b/src/Ryujinx.Graphics.GAL/IPipeline.cs index b8409a573..da20da870 100644 --- a/src/Ryujinx.Graphics.GAL/IPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/IPipeline.cs @@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.GAL void SetRasterizerDiscard(bool discard); void SetRenderTargetColorMasks(ReadOnlySpan componentMask); - void SetRenderTargets(ITexture[] colors, ITexture depthStencil); + void SetRenderTargets(Span colors, ITexture depthStencil); void SetScissors(ReadOnlySpan> regions); diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs index ca7c8c8c2..2641ae528 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/Commands/SetRenderTargetsCommand.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL.Multithreading.Model; using Ryujinx.Graphics.GAL.Multithreading.Resources; +using System; using System.Buffers; namespace Ryujinx.Graphics.GAL.Multithreading.Commands @@ -8,11 +9,13 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands { public static readonly ArrayPool ArrayPool = ArrayPool.Create(512, 50); public readonly CommandType CommandType => CommandType.SetRenderTargets; + private int _colorsCount; private TableRef _colors; private TableRef _depthStencil; - public void Set(TableRef colors, TableRef depthStencil) + public void Set(int colorsCount, TableRef colors, TableRef depthStencil) { + _colorsCount = colorsCount; _colors = colors; _depthStencil = depthStencil; } @@ -20,16 +23,15 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands public static void Run(ref SetRenderTargetsCommand command, ThreadedRenderer threaded, IRenderer renderer) { ITexture[] colors = command._colors.Get(threaded); - ITexture[] colorsCopy = ArrayPool.Rent(colors.Length); + Span colorsSpan = colors.AsSpan(0, command._colorsCount); - for (int i = 0; i < colors.Length; i++) + for (int i = 0; i < colorsSpan.Length; i++) { - colorsCopy[i] = ((ThreadedTexture)colors[i])?.Base; + colorsSpan[i] = ((ThreadedTexture)colorsSpan[i])?.Base; } - renderer.Pipeline.SetRenderTargets(colorsCopy, command._depthStencil.GetAs(threaded)?.Base); + renderer.Pipeline.SetRenderTargets(colorsSpan, command._depthStencil.GetAs(threaded)?.Base); - ArrayPool.Return(colorsCopy); ArrayPool.Return(colors); } } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs index ea3fd1e11..6873574b7 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedPipeline.cs @@ -267,12 +267,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading _renderer.QueueCommand(); } - public unsafe void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public unsafe void SetRenderTargets(Span colors, ITexture depthStencil) { ITexture[] colorsCopy = SetRenderTargetsCommand.ArrayPool.Rent(colors.Length); - colors.CopyTo(colorsCopy, 0); + colors.CopyTo(colorsCopy.AsSpan()); - _renderer.New()->Set(Ref(colorsCopy), Ref(depthStencil)); + _renderer.New()->Set(colors.Length, Ref(colorsCopy), Ref(depthStencil)); _renderer.QueueCommand(); } diff --git a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs index 3c179da36..66ac31ab4 100644 --- a/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs +++ b/src/Ryujinx.Graphics.GAL/Multithreading/ThreadedRenderer.cs @@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading /// public class ThreadedRenderer : IRenderer { - private const int SpanPoolBytes = 4 * 1024 * 1024; + private const int SpanPoolBytes = 8 * 1024 * 1024; private const int MaxRefsPerCommand = 2; private const int QueueCount = 10000; diff --git a/src/Ryujinx.Graphics.Gpu/GpuContext.cs b/src/Ryujinx.Graphics.Gpu/GpuContext.cs index 8b1277c47..7ee32e83d 100644 --- a/src/Ryujinx.Graphics.Gpu/GpuContext.cs +++ b/src/Ryujinx.Graphics.Gpu/GpuContext.cs @@ -404,9 +404,12 @@ namespace Ryujinx.Graphics.Gpu if (force || _pendingSync || (syncPoint && SyncpointActions.Count > 0)) { - foreach (ISyncActionHandler action in SyncActions) + for (int i = 0; i < SyncActions.Count; i++) { - action.SyncPreAction(syncPoint); + if (SyncActions[i].SyncPreAction(syncPoint)) + { + SyncActions.RemoveAt(i--); + } } foreach (ISyncActionHandler action in SyncpointActions) diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs index fe22b9e63..1f2ee1a47 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs @@ -411,7 +411,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// flushes often enough, which is determined by the flush balance. /// /// - public void SyncPreAction(bool syncpoint) + public bool SyncPreAction(bool syncpoint) { if (syncpoint || NextSyncCopies()) { @@ -421,6 +421,8 @@ namespace Ryujinx.Graphics.Gpu.Image _registeredBufferSync = _modifiedSync; } } + + return true; } /// diff --git a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs index 277a30689..3bf02f54d 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/Buffer.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Buffer, used to store vertex and index data, uniform and storage buffers, and others. /// - class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable + class Buffer : INonOverlappingRange, ISyncActionHandler, IDisposable { private const ulong GranularBufferThreshold = 4096; @@ -41,6 +41,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// End address of the buffer in guest memory. /// public ulong EndAddress => Address + Size; + + public Buffer Next { get; set; } + public Buffer Previous { get; set; } /// /// Increments when the buffer is (partially) unmapped or disposed. @@ -87,6 +90,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly bool _useGranular; private bool _syncActionRegistered; + private bool _bufferInherited; private int _referenceCount = 1; @@ -113,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong size, BufferStage stage, bool sparseCompatible, - RangeItem[] baseBuffers) + Buffer[] baseBuffers) { _context = context; _physicalMemory = physicalMemory; @@ -134,15 +138,15 @@ namespace Ryujinx.Graphics.Gpu.Memory if (baseBuffers.Length != 0) { baseHandles = new List(); - foreach (RangeItem item in baseBuffers) + foreach (Buffer item in baseBuffers) { - if (item.Value._useGranular) + if (item._useGranular) { - baseHandles.AddRange(item.Value._memoryTrackingGranular.Handles); + baseHandles.AddRange(item._memoryTrackingGranular.Handles); } else { - baseHandles.Add(item.Value._memoryTracking); + baseHandles.Add(item._memoryTracking); } } } @@ -247,14 +251,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the buffer. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -389,11 +393,16 @@ namespace Ryujinx.Graphics.Gpu.Memory /// This will copy any buffer ranges designated for pre-flushing. /// /// True if the action is a guest syncpoint - public void SyncPreAction(bool syncpoint) + public bool SyncPreAction(bool syncpoint) { + if (_bufferInherited) + { + return true; + } + if (_referenceCount == 0) { - return; + return false; } if (BackingState.ShouldChangeBacking()) @@ -410,6 +419,8 @@ namespace Ryujinx.Graphics.Gpu.Memory _modifiedRanges?.GetRangesAtSync(Address, Size, _context.SyncNumber, _syncPreRangeAction); } } + + return false; } void SyncPreRangeAction(ulong address, ulong size) @@ -426,10 +437,13 @@ namespace Ryujinx.Graphics.Gpu.Memory { _syncActionRegistered = false; + if (_bufferInherited) + { + return true; + } + if (_useGranular) { - - _modifiedRanges?.GetRanges(Address, Size, _syncRangeAction); } else @@ -453,6 +467,8 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The buffer to inherit from public void InheritModifiedRanges(Buffer from) { + from._bufferInherited = true; + if (from._modifiedRanges is { HasRanges: true }) { if (from._syncActionRegistered && !_syncActionRegistered) diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs index a81e7e98f..e674eb1d7 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferBackingState.cs @@ -56,7 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Parent buffer /// Initial buffer stage /// Buffers to inherit state from - public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, RangeItem[] baseBuffers) + public BufferBackingState(GpuContext context, Buffer parent, BufferStage stage, Buffer[] baseBuffers) { _size = (int)parent.Size; _systemMemoryType = context.Capabilities.MemoryType; @@ -102,9 +102,9 @@ namespace Ryujinx.Graphics.Gpu.Memory if (baseBuffers.Length != 0) { - foreach (RangeItem item in baseBuffers) + foreach (Buffer item in baseBuffers) { - CombineState(item.Value.BackingState); + CombineState(item.BackingState); } } } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index 0d623ff95..83869ed02 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -79,16 +79,13 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int index = 0; index < range.Count; index++) { MemoryRange subRange = range.GetSubRange(index); - - _buffers.Lock.EnterReadLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size); + + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(subRange.Address, subRange.Size); for (int i = 0; i < overlaps.Length; i++) { - overlaps[i].Value.Unmapped(subRange.Address, subRange.Size); + overlaps[i].Unmapped(subRange.Address, subRange.Size); } - - _buffers.Lock.ExitReadLock(); } } @@ -328,7 +325,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask; ulong alignedSize = alignedEndAddress - alignedAddress; - Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize).Value; + Buffer buffer = _buffers.FindOverlap(alignedAddress, alignedSize); BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false); alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize); @@ -395,7 +392,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (subRange.Address != MemoryManager.PteUnmapped) { - Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value; + Buffer buffer = _buffers.FindOverlap(subRange.Address, subRange.Size); virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size); physicalBuffers.Add(buffer); @@ -487,10 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The type of usage that created the buffer private void CreateBufferAligned(ulong address, ulong size, BufferStage stage) { - Buffer newBuffer = null; - - _buffers.Lock.EnterWriteLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size); if (overlaps.Length != 0) { @@ -521,7 +515,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { // Try to grow the buffer by 1.5x of its current size. // This improves performance in the cases where the buffer is resized often by small amounts. - ulong existingSize = overlaps[0].Value.Size; + ulong existingSize = overlaps[0].Size; ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask; size = Math.Max(size, growthSize); @@ -535,39 +529,22 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < overlaps.Length; i++) { - anySparseCompatible |= overlaps[i].Value.SparseCompatible; + anySparseCompatible |= overlaps[i].SparseCompatible; } - RangeItem[] overlapsArray = overlaps.ToArray(); + Buffer[] overlapsArray = overlaps.ToArray(); _buffers.RemoveRange(overlaps[0], overlaps[^1]); - _buffers.Lock.ExitWriteLock(); - ulong newSize = endAddress - address; - newBuffer = CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray); - } - else - { - _buffers.Lock.ExitWriteLock(); + _buffers.Add(CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlapsArray)); } } else { - _buffers.Lock.ExitWriteLock(); - // No overlap, just create a new buffer. - newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, []); - } - - if (newBuffer is not null) - { - _buffers.Lock.EnterWriteLock(); - - _buffers.Add(newBuffer); - - _buffers.Lock.ExitWriteLock(); + _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseCompatible: false, [])); } } @@ -583,10 +560,8 @@ namespace Ryujinx.Graphics.Gpu.Memory private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment) { bool sparseAligned = alignment >= SparseBufferAlignmentSize; - Buffer newBuffer = null; - _buffers.Lock.EnterWriteLock(); - Span> overlaps = _buffers.FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = _buffers.FindOverlapsAsSpan(address, size); if (overlaps.Length != 0) { @@ -598,7 +573,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (overlaps[0].Address > address || overlaps[0].EndAddress < endAddress || (overlaps[0].Address & (alignment - 1)) != 0 || - (!overlaps[0].Value.SparseCompatible && sparseAligned)) + (!overlaps[0].SparseCompatible && sparseAligned)) { // We need to make sure the new buffer is properly aligned. // However, after the range is aligned, it is possible that it @@ -622,35 +597,18 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong newSize = endAddress - address; - RangeItem[] overlapsArray = overlaps.ToArray(); + Buffer[] overlapsArray = overlaps.ToArray(); _buffers.RemoveRange(overlaps[0], overlaps[^1]); - _buffers.Lock.ExitWriteLock(); - - newBuffer = CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray); - } - else - { - _buffers.Lock.ExitWriteLock(); + _buffers.Add(CreateBufferAligned(address, newSize, stage, sparseAligned, overlapsArray)); } } else { - _buffers.Lock.ExitWriteLock(); - // No overlap, just create a new buffer. - newBuffer = new(_context, _physicalMemory, address, size, stage, sparseAligned, []); - } - - if (newBuffer is not null) - { - _buffers.Lock.EnterWriteLock(); - - _buffers.Add(newBuffer); - - _buffers.Lock.ExitWriteLock(); - } + _buffers.Add(new(_context, _physicalMemory, address, size, stage, sparseAligned, [])); + } } /// @@ -663,13 +621,13 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The type of usage that created the buffer /// Indicates if the buffer can be used in a sparse buffer mapping /// Buffers overlapping the range - private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, RangeItem[] overlaps) + private Buffer CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps) { Buffer newBuffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps); for (int index = 0; index < overlaps.Length; index++) { - Buffer buffer = overlaps[index].Value; + Buffer buffer = overlaps[index]; int dstOffset = (int)(buffer.Address - newBuffer.Address); @@ -897,7 +855,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { MemoryRange subRange = range.GetSubRange(i); - Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size).Value; + Buffer subBuffer = _buffers.FindOverlap(subRange.Address, subRange.Size); subBuffer.SynchronizeMemory(subRange.Address, subRange.Size); @@ -945,7 +903,7 @@ namespace Ryujinx.Graphics.Gpu.Memory if (size != 0) { - buffer = _buffers.FindOverlap(address, size).Value; + buffer = _buffers.FindOverlap(address, size); buffer.CopyFromDependantVirtualBuffers(); buffer.SynchronizeMemory(address, size); @@ -957,7 +915,7 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - buffer = _buffers.FindOverlapFast(address, 1).Value; + buffer = _buffers.FindOverlapFast(address, 1); } return buffer; @@ -995,7 +953,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (size != 0) { - Buffer buffer = _buffers.FindOverlap(address, size).Value; + Buffer buffer = _buffers.FindOverlap(address, size); if (copyBackVirtual) { diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs index 9c50eaf2f..aef04abc6 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// A range within a buffer that has been modified by the GPU. /// - class BufferModifiedRange : INonOverlappingRange + class BufferModifiedRange : INonOverlappingRange { /// /// Start address of the range in guest memory. @@ -24,6 +24,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// End address of the range in guest memory. /// public ulong EndAddress => Address + Size; + + public BufferModifiedRange Next { get; set; } + public BufferModifiedRange Previous { get; set; } /// /// The GPU sync number at the time of the last modification. @@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the modified range. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -119,11 +122,11 @@ namespace Ryujinx.Graphics.Gpu.Memory // Slices a given region using the modified regions in the list. Calls the action for the new slices. Lock.EnterReadLock(); - Span> overlaps = FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size); for (int i = 0; i < overlaps.Length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; if (overlap.Address > address) { @@ -157,7 +160,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong syncNumber = _context.SyncNumber; // We may overlap with some existing modified regions. They must be cut into by the new entry. Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); + (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size); if (first is null) { @@ -170,34 +173,39 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (first.Address == address && first.EndAddress == endAddress) { - first.Value.SyncNumber = syncNumber; - first.Value.Parent = this; + first.SyncNumber = syncNumber; + first.Parent = this; Lock.ExitWriteLock(); return; } if (first.Address < address) { - first.Value.Size = address - first.Address; - Update(first); - if (first.EndAddress > endAddress) { Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, - first.Value.SyncNumber, first.Value.Parent)); + first.SyncNumber, first.Parent)); } + + first.Size = address - first.Address; } else { if (first.EndAddress > endAddress) { - first.Value.Size = first.EndAddress - endAddress; - first.Value.Address = endAddress; - Update(first); + first.Size = first.EndAddress - endAddress; + first.Address = endAddress; } else { - Remove(first.Value); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; + + Lock.ExitWriteLock(); + + return; } } @@ -207,38 +215,39 @@ namespace Ryujinx.Graphics.Gpu.Memory return; } - BufferModifiedRange buffPre = null; - BufferModifiedRange buffPost = null; - bool extendsPost = false; - bool extendsPre = false; - if (first.Address < address) { - buffPre = new BufferModifiedRange(first.Address, address - first.Address, - first.Value.SyncNumber, first.Value.Parent); - extendsPre = true; + first.Size = address - first.Address; + first = first.Next; } if (last.EndAddress > endAddress) { - buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, - last.Value.SyncNumber, last.Value.Parent); - extendsPost = true; + last.Size = last.EndAddress - endAddress; + last.Address = endAddress; + last = last.Previous; } - RemoveRange(first, last); - - if (extendsPre) + if (first.Address < last.Address) { - Add(buffPre); + RemoveRange(first.Next, last); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; } - - if (extendsPost) + else if (first.Address == last.Address) { - Add(buffPost); + first.Address = address; + first.Size = size; + first.SyncNumber = syncNumber; + first.Parent = this; } - - Add(new BufferModifiedRange(address, size, syncNumber, this)); + else + { + Add(new BufferModifiedRange(address, size, syncNumber, this)); + } + Lock.ExitWriteLock(); } @@ -252,11 +261,11 @@ namespace Ryujinx.Graphics.Gpu.Memory public void GetRangesAtSync(ulong address, ulong size, ulong syncNumber, Action rangeAction) { Lock.EnterReadLock(); - Span> overlaps = FindOverlapsAsSpan(address, size); + ReadOnlySpan overlaps = FindOverlapsAsSpan(address, size); for (int i = 0; i < overlaps.Length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; if (overlap.SyncNumber == syncNumber) { @@ -277,18 +286,18 @@ namespace Ryujinx.Graphics.Gpu.Memory { // We use the non-span method here because keeping the lock will cause a deadlock. Lock.EnterReadLock(); - RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int length); + BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int length); Lock.ExitReadLock(); if (length != 0) { for (int i = 0; i < length; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; rangeAction(overlap.Address, overlap.Size); } - ArrayPool>.Shared.Return(overlaps); + ArrayPool.Shared.Return(overlaps); } } @@ -301,7 +310,7 @@ namespace Ryujinx.Graphics.Gpu.Memory public bool HasRange(ulong address, ulong size) { Lock.EnterReadLock(); - RangeItem first = FindOverlapFast(address, size); + BufferModifiedRange first = FindOverlapFast(address, size); bool result = first is not null; Lock.ExitReadLock(); return result; @@ -336,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// The start address of the flush range /// The end address of the flush range private void RemoveRangesAndFlush( - RangeItem[] overlaps, + BufferModifiedRange[] overlaps, int rangeCount, long highestDiff, ulong currentSync, @@ -349,7 +358,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = overlaps[i].Value; + BufferModifiedRange overlap = overlaps[i]; long diff = (long)(overlap.SyncNumber - currentSync); @@ -358,7 +367,14 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong clampAddress = Math.Max(address, overlap.Address); ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); - ClearPart(overlap, clampAddress, clampEnd); + if (i == 0 || i == rangeCount - 1) + { + ClearPart(overlap, clampAddress, clampEnd); + } + else + { + Remove(overlap); + } RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, _flushAction); } @@ -398,7 +414,7 @@ namespace Ryujinx.Graphics.Gpu.Memory Lock.EnterWriteLock(); // We use the non-span method here because the array is partially modified by the code, which would invalidate a span. - RangeItem[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); + BufferModifiedRange[] overlaps = FindOverlapsAsArray(address, size, out int rangeCount); if (rangeCount == 0) { @@ -414,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Memory for (int i = 0; i < rangeCount; i++) { - BufferModifiedRange overlap = overlaps![i].Value; + BufferModifiedRange overlap = overlaps![i]; long diff = (long)(overlap.SyncNumber - currentSync); @@ -436,7 +452,7 @@ namespace Ryujinx.Graphics.Gpu.Memory RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); - ArrayPool>.Shared.Return(overlaps!); + ArrayPool.Shared.Return(overlaps!); Lock.ExitWriteLock(); } @@ -452,7 +468,9 @@ namespace Ryujinx.Graphics.Gpu.Memory public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction) { ranges.Lock.EnterReadLock(); - BufferModifiedRange[] inheritRanges = ranges.ToArray(); + int rangesCount = ranges.Count; + BufferModifiedRange[] inheritRanges = ArrayPool.Shared.Rent(ranges.Count); + ranges.Items.AsSpan(0, ranges.Count).CopyTo(inheritRanges); ranges.Lock.ExitReadLock(); // Copy over the migration from the previous range list @@ -478,22 +496,26 @@ namespace Ryujinx.Graphics.Gpu.Memory ranges._migrationTarget = this; Lock.EnterWriteLock(); - - foreach (BufferModifiedRange range in inheritRanges) + + for (int i = 0; i < rangesCount; i++) { + BufferModifiedRange range = inheritRanges[i]; Add(range); } Lock.ExitWriteLock(); ulong currentSync = _context.SyncNumber; - foreach (BufferModifiedRange range in inheritRanges) + for (int i = 0; i < rangesCount; i++) { + BufferModifiedRange range = inheritRanges[i]; if (range.SyncNumber != currentSync) { registerRangeAction(range.Address, range.Size); } } + + ArrayPool.Shared.Return(inheritRanges); } /// @@ -534,18 +556,25 @@ namespace Ryujinx.Graphics.Gpu.Memory private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) { - Remove(overlap); - // If the overlap extends outside of the clear range, make sure those parts still exist. if (overlap.Address < address) { - Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + if (overlap.EndAddress > endAddress) + { + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + + overlap.Size = address - overlap.Address; } - - if (overlap.EndAddress > endAddress) + else if (overlap.EndAddress > endAddress) { - Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + overlap.Size = overlap.EndAddress - endAddress; + overlap.Address = endAddress; + } + else + { + Remove(overlap); } } @@ -558,7 +587,7 @@ namespace Ryujinx.Graphics.Gpu.Memory { ulong endAddress = address + size; Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); + (BufferModifiedRange first, BufferModifiedRange last) = FindOverlapsAsNodes(address, size); if (first is null) { @@ -570,26 +599,24 @@ namespace Ryujinx.Graphics.Gpu.Memory { if (first.Address < address) { - first.Value.Size = address - first.Address; - Update(first); + first.Size = address - first.Address; if (first.EndAddress > endAddress) { Add(new BufferModifiedRange(endAddress, first.EndAddress - endAddress, - first.Value.SyncNumber, first.Value.Parent)); + first.SyncNumber, first.Parent)); } } else { if (first.EndAddress > endAddress) { - first.Value.Size = first.EndAddress - endAddress; - first.Value.Address = endAddress; - Update(first); + first.Size = first.EndAddress - endAddress; + first.Address = endAddress; } else { - Remove(first.Value); + Remove(first); } } @@ -605,14 +632,14 @@ namespace Ryujinx.Graphics.Gpu.Memory if (first.Address < address) { buffPre = new BufferModifiedRange(first.Address, address - first.Address, - first.Value.SyncNumber, first.Value.Parent); + first.SyncNumber, first.Parent); extendsPre = true; } if (last.EndAddress > endAddress) { buffPost = new BufferModifiedRange(endAddress, last.EndAddress - endAddress, - last.Value.SyncNumber, last.Value.Parent); + last.SyncNumber, last.Parent); extendsPost = true; } diff --git a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs index 1d44ee65f..0339d8617 100644 --- a/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Memory/VirtualRangeCache.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Memory /// /// Represents a GPU virtual memory range. /// - private class VirtualRange : INonOverlappingRange + private class VirtualRange : INonOverlappingRange { /// /// GPU virtual address where the range starts. @@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory /// public ulong EndAddress => Address + Size; + public VirtualRange Next { get; set; } + public VirtualRange Previous { get; set; } + /// /// Physical regions where the GPU virtual region is mapped. /// @@ -54,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Memory /// Checks if a given range overlaps with the buffer. /// /// Start address of the range - /// Size in bytes of the range + /// End address of the range /// True if the range overlaps, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } - public INonOverlappingRange Split(ulong splitAddress) + public INonOverlappingRange Split(ulong splitAddress) { throw new NotImplementedException(); } @@ -122,7 +125,7 @@ namespace Ryujinx.Graphics.Gpu.Memory ulong originalVa = gpuVa; _virtualRanges.Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size); + (VirtualRange first, VirtualRange last) = _virtualRanges.FindOverlapsAsNodes(gpuVa, size); if (first is not null) { @@ -147,8 +150,8 @@ namespace Ryujinx.Graphics.Gpu.Memory } else { - found = first.Value.Range.Count == 1 || IsSparseAligned(first.Value.Range); - range = first.Value.Range.Slice(gpuVa - first.Address, size); + found = first.Range.Count == 1 || IsSparseAligned(first.Range); + range = first.Range.Slice(gpuVa - first.Address, size); } } else diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs index d470d2f07..b26ed8ad7 100644 --- a/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs +++ b/src/Ryujinx.Graphics.Gpu/Synchronization/ISyncActionHandler.cs @@ -17,6 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Synchronization /// Action to be performed immediately before sync is created. /// /// True if the action is a guest syncpoint - void SyncPreAction(bool syncpoint) { } + bool SyncPreAction(bool syncpoint) { return true; } } } diff --git a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs index c8ca02140..e58e6f2b9 100644 --- a/src/Ryujinx.Graphics.OpenGL/Pipeline.cs +++ b/src/Ryujinx.Graphics.OpenGL/Pipeline.cs @@ -1166,7 +1166,7 @@ namespace Ryujinx.Graphics.OpenGL } } - public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public void SetRenderTargets(Span colors, ITexture depthStencil) { EnsureFramebuffer(); diff --git a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs index 919c45b9d..6d03fcd0d 100644 --- a/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs +++ b/src/Ryujinx.Graphics.Vulkan/FramebufferParams.cs @@ -71,17 +71,31 @@ namespace Ryujinx.Graphics.Vulkan HasDepthStencil = isDepthStencil; } - public FramebufferParams(Device device, ITexture[] colors, ITexture depthStencil) + public FramebufferParams(Device device, ReadOnlySpan colors, ITexture depthStencil) { _device = device; - int colorsCount = colors.Count(IsValidTextureView); + int colorsCount = 0; + _colorsCanonical = new TextureView[colors.Length]; + + for (int i = 0; i < colors.Length; i++) + { + ITexture color = colors[i]; + if (color is TextureView { Valid: true } view) + { + colorsCount++; + _colorsCanonical[i] = view; + } + else + { + _colorsCanonical[i] = null; + } + } int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); _attachments = new Auto[count]; _colors = new TextureView[colorsCount]; - _colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray(); AttachmentSamples = new uint[count]; AttachmentFormats = new VkFormat[count]; @@ -165,9 +179,17 @@ namespace Ryujinx.Graphics.Vulkan _totalCount = colors.Length; } - public FramebufferParams Update(ITexture[] colors, ITexture depthStencil) + public FramebufferParams Update(ReadOnlySpan colors, ITexture depthStencil) { - int colorsCount = colors.Count(IsValidTextureView); + int colorsCount = 0; + + foreach (ITexture color in colors) + { + if (IsValidTextureView(color)) + { + colorsCount++; + } + } int count = colorsCount + (IsValidTextureView(depthStencil) ? 1 : 0); diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs index 40ad7716d..b226ce1f3 100644 --- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs +++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs @@ -1,7 +1,7 @@ +using Ryujinx.Common; using Ryujinx.Common.Memory; using Silk.NET.Vulkan; using System; -using System.Buffers; namespace Ryujinx.Graphics.Vulkan { @@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Vulkan /// class MultiFenceHolder { + public static readonly ObjectPool FencePool = new(() => new FenceHolder[CommandBufferPool.MaxCommandBuffers]); + private const int BufferUsageTrackingGranularity = 4096; public FenceHolder[] Fences { get; } @@ -20,7 +22,7 @@ namespace Ryujinx.Graphics.Vulkan /// public MultiFenceHolder() { - Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); + Fences = FencePool.Allocate(); } /// @@ -29,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan /// Size of the buffer public MultiFenceHolder(int size) { - Fences = ArrayPool.Shared.Rent(CommandBufferPool.MaxCommandBuffers); + Fences = FencePool.Allocate(); _bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity); } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs index f2f68378f..0172b5b56 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -1035,7 +1035,7 @@ namespace Ryujinx.Graphics.Vulkan } } - private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + private void SetRenderTargetsInternal(Span colors, ITexture depthStencil, bool filterWriteMasked) { CreateFramebuffer(colors, depthStencil, filterWriteMasked); CreateRenderPass(); @@ -1043,7 +1043,7 @@ namespace Ryujinx.Graphics.Vulkan SignalAttachmentChange(); } - public void SetRenderTargets(ITexture[] colors, ITexture depthStencil) + public void SetRenderTargets(Span colors, ITexture depthStencil) { _framebufferUsingColorWriteMask = false; SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR); @@ -1389,7 +1389,7 @@ namespace Ryujinx.Graphics.Vulkan _currentPipelineHandle = 0; } - private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked) + private void CreateFramebuffer(Span colors, ITexture depthStencil, bool filterWriteMasked) { if (filterWriteMasked) { @@ -1399,7 +1399,7 @@ namespace Ryujinx.Graphics.Vulkan // Just try to remove duplicate attachments. // Save a copy of the array to rebind when mask changes. - void MaskOut() + void MaskOut(ReadOnlySpan colors) { if (!_framebufferUsingColorWriteMask) { @@ -1436,12 +1436,12 @@ namespace Ryujinx.Graphics.Vulkan if (vkBlend.ColorWriteMask == 0) { colors[i] = null; - MaskOut(); + MaskOut(colors); } else if (vkBlend2.ColorWriteMask == 0) { colors[j] = null; - MaskOut(); + MaskOut(colors); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs index 149759906..15759b0de 100644 --- a/src/Ryujinx.Graphics.Vulkan/SyncManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/SyncManager.cs @@ -1,6 +1,6 @@ using Ryujinx.Common.Logging; using Silk.NET.Vulkan; -using System.Buffers; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -193,7 +193,8 @@ namespace Ryujinx.Graphics.Vulkan { _firstHandle = first.ID + 1; _handles.RemoveAt(0); - ArrayPool.Shared.Return(first.Waitable.Fences); + Array.Clear(first.Waitable.Fences); + MultiFenceHolder.FencePool.Release(first.Waitable.Fences); first.Waitable = null; } } diff --git a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs index d3094d842..0fb70c6b0 100644 --- a/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs +++ b/src/Ryujinx.HLE/Debugger/Debugger.Rcmd.cs @@ -1,43 +1,91 @@ using Gommon; using Ryujinx.Common.Logging; +using Ryujinx.HLE.HOS.Kernel.Memory; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text; namespace Ryujinx.HLE.Debugger { public partial class Debugger { + private sealed record RcmdEntry(string[] Names, Func Handler, string[] HelpLines); + + // Atmosphere/libraries/libmesosphere/source/kern_k_memory_block_manager.cpp + private static readonly string[] _memoryStateNames = + { + "----- Free -----", + "Io ", + "Static ", + "Code ", + "CodeData ", + "Normal ", + "Shared ", + "Alias ", + "AliasCode ", + "AliasCodeData ", + "Ipc ", + "Stack ", + "ThreadLocal ", + "Transfered ", + "SharedTransfered", + "SharedCode ", + "Inaccessible ", + "NonSecureIpc ", + "NonDeviceIpc ", + "Kernel ", + "GeneratedCode ", + "CodeOut ", + "Coverage ", + }; + static Debugger() { - _rcmdDelegates.Add(["help"], - _ => _rcmdDelegates.Keys - .Where(x => !x[0].Equals("help")) - .Select(x => x.JoinToString('\n')) - .JoinToString('\n') + '\n' - ); - _rcmdDelegates.Add(["get info"], dbgr => dbgr.GetProcessInfo()); - _rcmdDelegates.Add(["backtrace", "bt"], dbgr => dbgr.GetStackTrace()); - _rcmdDelegates.Add(["registers", "reg"], dbgr => dbgr.GetRegisters()); - _rcmdDelegates.Add(["minidump"], dbgr => dbgr.GetMinidump()); + _rcmdDelegates.Add(new RcmdEntry( + ["help"], + (dbgr, _) => _rcmdDelegates + .Where(entry => entry.HelpLines.Length > 0) + .SelectMany(entry => entry.HelpLines) + .JoinToString('\n') + '\n', + Array.Empty())); + + _rcmdDelegates.Add(new RcmdEntry(["get info"], (dbgr, _) => dbgr.GetProcessInfo(), ["get info"])); + _rcmdDelegates.Add(new RcmdEntry(["backtrace", "bt"], (dbgr, _) => dbgr.GetStackTrace(), ["backtrace", "bt"])); + _rcmdDelegates.Add(new RcmdEntry(["registers", "reg"], (dbgr, _) => dbgr.GetRegisters(), ["registers", "reg"])); + _rcmdDelegates.Add(new RcmdEntry(["minidump"], (dbgr, _) => dbgr.GetMinidump(), ["minidump"])); + _rcmdDelegates.Add(new RcmdEntry(["get mappings"], (dbgr, args) => dbgr.GetMemoryMappings(args), ["get mappings", "get mappings {address}"])); + _rcmdDelegates.Add(new RcmdEntry(["get mapping"], (dbgr, args) => dbgr.GetMemoryMapping(args), ["get mapping {address}"])); } - private static readonly Dictionary> _rcmdDelegates = new(); + private static readonly List _rcmdDelegates = []; - public static Func FindRcmdDelegate(string command) + public static string CallRcmdDelegate(Debugger debugger, string command) { - Func searchResult = _ => $"Unknown command: {command}\n"; + string originalCommand = command ?? string.Empty; + string trimmedCommand = originalCommand.Trim(); - foreach ((string[] names, Func dlg) in _rcmdDelegates) + foreach (RcmdEntry entry in _rcmdDelegates) { - if (names.ContainsIgnoreCase(command.Trim())) + foreach (string name in entry.Names) { - searchResult = dlg; - break; + if (trimmedCommand.Equals(name, StringComparison.OrdinalIgnoreCase)) + { + return entry.Handler(debugger, string.Empty); + } + + if (trimmedCommand.Length > name.Length && + trimmedCommand.StartsWith(name, StringComparison.OrdinalIgnoreCase) && + char.IsWhiteSpace(trimmedCommand[name.Length])) + { + string arguments = trimmedCommand[name.Length..].TrimStart(); + return entry.Handler(debugger, arguments); + } } } - return searchResult; + return $"Unknown command: {originalCommand}\n"; } public string GetStackTrace() @@ -86,5 +134,181 @@ namespace Ryujinx.HLE.Debugger return $"Error getting process info: {e.Message}\n"; } } + + public string GetMemoryMappings(string arguments) + { + if (Process?.MemoryManager is not { } memoryManager) + { + return "No application process found\n"; + } + + string trimmedArgs = arguments?.Trim() ?? string.Empty; + + ulong startAddress = 0; + if (!string.IsNullOrEmpty(trimmedArgs)) + { + if (!TryParseAddressArgument(trimmedArgs, out startAddress)) + { + return $"Invalid address: {trimmedArgs}\n"; + } + } + + ulong requestedAddress = startAddress; + ulong currentAddress = Math.Max(requestedAddress, memoryManager.AddrSpaceStart); + StringBuilder sb = new(); + sb.AppendLine($"Mappings (starting from 0x{requestedAddress:x10}):"); + + if (currentAddress >= memoryManager.AddrSpaceEnd) + { + return sb.ToString(); + } + + while (currentAddress < memoryManager.AddrSpaceEnd) + { + KMemoryInfo info = memoryManager.QueryMemory(currentAddress); + + try + { + if (info.Size == 0 || info.Address >= memoryManager.AddrSpaceEnd) + { + break; + } + + sb.AppendLine(FormatMapping(info, indent: true)); + + if (info.Address > ulong.MaxValue - info.Size) + { + break; + } + + ulong nextAddress = info.Address + info.Size; + if (nextAddress <= currentAddress) + { + break; + } + + currentAddress = nextAddress; + } + finally + { + KMemoryInfo.Pool.Release(info); + } + } + + return sb.ToString(); + } + + public string GetMemoryMapping(string arguments) + { + if (Process?.MemoryManager is not { } memoryManager) + { + return "No application process found\n"; + } + + string trimmedArgs = arguments?.Trim() ?? string.Empty; + + if (string.IsNullOrEmpty(trimmedArgs)) + { + return "Missing address argument for `get mapping`\n"; + } + + if (!TryParseAddressArgument(trimmedArgs, out ulong address)) + { + return $"Invalid address: {trimmedArgs}\n"; + } + + KMemoryInfo info = memoryManager.QueryMemory(address); + + try + { + return FormatMapping(info, indent: false) + '\n'; + } + finally + { + KMemoryInfo.Pool.Release(info); + } + } + + private static string FormatMapping(KMemoryInfo info, bool indent) + { + ulong endAddress; + + if (info.Size == 0) + { + endAddress = info.Address; + } + else if (info.Address > ulong.MaxValue - (info.Size - 1)) + { + endAddress = ulong.MaxValue; + } + else + { + endAddress = info.Address + info.Size - 1; + } + + string prefix = indent ? " " : string.Empty; + return $"{prefix}0x{info.Address:x10} - 0x{endAddress:x10} {GetPermissionString(info)} {GetMemoryStateName(info.State)} {GetAttributeFlags(info)} [{info.IpcRefCount}, {info.DeviceRefCount}]"; + } + + private static string GetPermissionString(KMemoryInfo info) + { + if ((info.State & MemoryState.UserMask) == MemoryState.Unmapped) + { + return " "; + } + + return info.Permission switch + { + KMemoryPermission.ReadAndExecute => "r-x", + KMemoryPermission.Read => "r--", + KMemoryPermission.ReadAndWrite => "rw-", + _ => "---" + }; + } + + private static string GetMemoryStateName(MemoryState state) + { + int stateIndex = (int)(state & MemoryState.UserMask); + if ((uint)stateIndex < _memoryStateNames.Length) + { + return _memoryStateNames[stateIndex]; + } + + return "Unknown "; + } + + private static bool TryParseAddressArgument(string text, out ulong value) + { + value = 0; + + if (string.IsNullOrWhiteSpace(text)) + { + return false; + } + + string trimmed = text.Trim(); + + if (trimmed.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + trimmed = trimmed[2..]; + } + + if (trimmed.Length == 0) + { + return false; + } + + return ulong.TryParse(trimmed, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value); + } + + private static string GetAttributeFlags(KMemoryInfo info) + { + char locked = info.Attribute.HasFlag(MemoryAttribute.Borrowed) ? 'L' : '-'; + char ipc = info.Attribute.HasFlag(MemoryAttribute.IpcMapped) ? 'I' : '-'; + char device = info.Attribute.HasFlag(MemoryAttribute.DeviceMapped) ? 'D' : '-'; + char uncached = info.Attribute.HasFlag(MemoryAttribute.Uncached) ? 'U' : '-'; + + return $"{locked}{ipc}{device}{uncached}"; + } } } diff --git a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs index 66308003f..0b9e12f71 100644 --- a/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs +++ b/src/Ryujinx.HLE/Debugger/Gdb/Commands.cs @@ -404,9 +404,8 @@ namespace Ryujinx.HLE.Debugger.Gdb string command = Helpers.FromHex(hexCommand); Logger.Debug?.Print(LogClass.GdbStub, $"Received Rcmd: {command}"); - Func rcmd = Debugger.FindRcmdDelegate(command); - - Processor.ReplyHex(rcmd(Debugger)); + string response = Debugger.CallRcmdDelegate(Debugger, command); + Processor.ReplyHex(response); } catch (Exception e) { diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs index 9f38de42b..d0fe0f1a7 100644 --- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -483,10 +483,29 @@ namespace Ryujinx.HLE.FileSystem { if (Directory.Exists(keysSource)) { - foreach (string filePath in Directory.EnumerateFiles(keysSource, "*.keys")) + string[] keyPaths = Directory.EnumerateFiles(keysSource, "*.keys").ToArray(); + + if (keyPaths.Length is 0) + throw new FileNotFoundException($"Directory '{keysSource}' contained no '.keys' files."); + + foreach (string filePath in keyPaths) { - VerifyKeysFile(filePath); - File.Copy(filePath, Path.Combine(installDirectory, Path.GetFileName(filePath)), true); + try + { + VerifyKeysFile(filePath); + } + catch (Exception e) + { + Logger.Error?.Print(LogClass.Application, e.Message); + continue; + } + + string destPath = Path.Combine(installDirectory, Path.GetFileName(filePath)); + + if (File.Exists(destPath)) + File.Delete(destPath); + + File.Copy(filePath, destPath, true); } return; @@ -501,13 +520,25 @@ namespace Ryujinx.HLE.FileSystem using FileStream file = File.OpenRead(keysSource); - if (info.Extension is ".keys") + if (info.Extension is not ".keys") + throw new InvalidFirmwarePackageException("Input file extension is not .keys"); + + try { VerifyKeysFile(keysSource); - File.Copy(keysSource, Path.Combine(installDirectory, info.Name), true); - } - else + } + catch + { throw new InvalidFirmwarePackageException("Input file is not a valid key package"); + } + + string dest = Path.Combine(installDirectory, info.Name); + + if (File.Exists(dest)) + File.Delete(dest); + + // overwrite: true seems to not work on its own? https://github.com/Ryubing/Issues/issues/189 + File.Copy(keysSource, dest, true); } private void FinishInstallation(string temporaryDirectory, string registeredDirectory) @@ -985,8 +1016,8 @@ namespace Ryujinx.HLE.FileSystem public static 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}$"; + string genericPattern = "^[a-z0-9_]+ = [a-z0-9]+$"; + string titlePattern = "^[a-z0-9]{32} = [a-z0-9]{32}$"; if (File.Exists(filePath)) { @@ -994,24 +1025,13 @@ namespace Ryujinx.HLE.FileSystem string fileName = Path.GetFileName(filePath); string[] lines = File.ReadAllLines(filePath); - bool verified; - switch (fileName) + bool verified = fileName switch { - 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."); - } + "prod.keys" or "console.keys" or "dev.keys" => VerifyKeys(lines, genericPattern), + "title.keys" => VerifyKeys(lines, titlePattern), + _ => throw new FormatException( + $"Keys file name \"{fileName}\" not supported. Only \"prod.keys\", \"title.keys\", \"console.keys\", \"dev.keys\" are supported.") + }; if (!verified) { diff --git a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs index 7df2b778f..13a93db39 100644 --- a/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs +++ b/src/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -2,6 +2,7 @@ using Microsoft.IO; using Ryujinx.Common; using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -37,7 +38,7 @@ namespace Ryujinx.HLE.HOS.Ipc public IpcMessage(ReadOnlySpan data, long cmdPtr) { - using RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data); BinaryReader reader = new(ms); @@ -123,6 +124,8 @@ namespace Ryujinx.HLE.HOS.Ipc } ObjectIds = []; + + MemoryStreamManager.Shared.ReleaseStream(ms); } public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs index 373899b7b..d22cfb469 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KBufferDescriptorTable.cs @@ -20,6 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc _exchangeBufferDescriptors = new List(MaxInternalBuffersCount); } + public KBufferDescriptorTable Clear() + { + _sendBufferDescriptors.Clear(); + _receiveBufferDescriptors.Clear(); + _exchangeBufferDescriptors.Clear(); + + return this; + } + public Result AddSendBuffer(ulong src, ulong dst, ulong size, MemoryState state) { return Add(_sendBufferDescriptors, src, dst, size, state); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs index 385f09020..d83e14ba3 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; @@ -32,7 +33,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { KThread currentThread = KernelStatic.GetCurrentThread(); - KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize); + KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize); KernelContext.CriticalSection.Enter(); @@ -55,7 +56,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { KThread currentThread = KernelStatic.GetCurrentThread(); - KSessionRequest request = new(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); + KSessionRequest request = _parent.ServerSession.RequestPool.Allocate().Set(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent); KernelContext.CriticalSection.Enter(); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs index edc3d819e..f2c22c9f3 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs @@ -10,6 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KServerSession : KSynchronizationObject { + public readonly ObjectPool RequestPool = new(() => new KSessionRequest()); + private static readonly MemoryState[] _ipcMemoryStates = [ MemoryState.IpcBuffer3, @@ -274,6 +276,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc KernelContext.CriticalSection.Leave(); WakeClientThread(request, clientResult); + + RequestPool.Release(request); } if (clientHeader.ReceiveListType < 2 && @@ -627,6 +631,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc CloseAllHandles(clientMsg, serverHeader, clientProcess); FinishRequest(request, clientResult); + + RequestPool.Release(request); } if (clientHeader.ReceiveListType < 2 && @@ -865,6 +871,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc // Unmap buffers from server. FinishRequest(request, clientResult); + + RequestPool.Release(request); return serverResult; } @@ -1098,6 +1106,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc foreach (KSessionRequest request in IterateWithRemovalOfAllRequests()) { FinishRequest(request, KernelResult.PortRemoteClosed); + + RequestPool.Release(request); } } @@ -1117,6 +1127,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { SendResultToAsyncRequestClient(request, KernelResult.PortRemoteClosed); } + + RequestPool.Release(request); } WakeServerThreads(KernelResult.PortRemoteClosed); diff --git a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs index bc3eef71e..69a0d3a02 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Ipc/KSessionRequest.cs @@ -5,18 +5,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc { class KSessionRequest { - public KBufferDescriptorTable BufferDescriptorTable { get; } + public KBufferDescriptorTable BufferDescriptorTable { get; private set; } - public KThread ClientThread { get; } + public KThread ClientThread { get; private set; } public KProcess ServerProcess { get; set; } - public KWritableEvent AsyncEvent { get; } + public KWritableEvent AsyncEvent { get; private set; } - public ulong CustomCmdBuffAddr { get; } - public ulong CustomCmdBuffSize { get; } + public ulong CustomCmdBuffAddr { get; private set; } + public ulong CustomCmdBuffSize { get; private set; } - public KSessionRequest( + public KSessionRequest Set( KThread clientThread, ulong customCmdBuffAddr, ulong customCmdBuffSize, @@ -27,7 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc CustomCmdBuffSize = customCmdBuffSize; AsyncEvent = asyncEvent; - BufferDescriptorTable = new KBufferDescriptorTable(); + BufferDescriptorTable = BufferDescriptorTable?.Clear() ?? new KBufferDescriptorTable(); + + return this; } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs index 278a9b2ff..c9ac86fc9 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs @@ -1,10 +1,8 @@ -using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.Horizon.Common; using System; using System.Collections.Generic; -using System.Linq; using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.Threading @@ -12,12 +10,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading class KAddressArbiter { private const int HasListenersMask = 0x40000000; - private static readonly ObjectPool _threadArrayPool = new(() => []); private readonly KernelContext _context; - private readonly List _condVarThreads; - private readonly List _arbiterThreads; + private readonly Dictionary> _condVarThreads; + private readonly Dictionary> _arbiterThreads; + private readonly ByDynamicPriority _byDynamicPriority; public KAddressArbiter(KernelContext context) { @@ -25,6 +23,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _condVarThreads = []; _arbiterThreads = []; + _byDynamicPriority = new ByDynamicPriority(); } public Result ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle) @@ -140,9 +139,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = mutexAddress; currentThread.ThreadHandleForUserMutex = threadHandle; - currentThread.CondVarAddress = condVarAddress; - _condVarThreads.Add(currentThread); + if (_condVarThreads.TryGetValue(condVarAddress, out List threads)) + { + int i = 0; + + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _condVarThreads.Add(condVarAddress, [currentThread]); + } if (timeout != 0) { @@ -165,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexOwner?.RemoveMutexWaiter(currentThread); - _condVarThreads.Remove(currentThread); + _condVarThreads[condVarAddress].Remove(currentThread); _context.CriticalSection.Leave(); @@ -200,13 +213,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { _context.CriticalSection.Enter(); - static bool SignalProcessWideKeyPredicate(KThread thread, ulong address) + int validThreads = 0; + _condVarThreads.TryGetValue(address, out List threads); + + if (threads is not null && threads.Count > 0) { - return thread.CondVarAddress == address; + validThreads = WakeThreads(threads, count, TryAcquireMutex); } - - int validThreads = WakeThreads(_condVarThreads, count, TryAcquireMutex, SignalProcessWideKeyPredicate, address); - + if (validThreads == 0) { KernelTransfer.KernelToUser(address, 0); @@ -315,9 +329,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = address; currentThread.WaitingInArbitration = true; + + if (_arbiterThreads.TryGetValue(address, out List threads)) + { + int i = 0; - _arbiterThreads.Add(currentThread); - + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _arbiterThreads.Add(address, [currentThread]); + } + currentThread.Reschedule(ThreadSchedState.Paused); if (timeout > 0) @@ -336,7 +365,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (currentThread.WaitingInArbitration) { - _arbiterThreads.Remove(currentThread); + _arbiterThreads[address].Remove(currentThread); currentThread.WaitingInArbitration = false; } @@ -392,9 +421,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading currentThread.MutexAddress = address; currentThread.WaitingInArbitration = true; + + if (_arbiterThreads.TryGetValue(address, out List threads)) + { + int i = 0; - _arbiterThreads.Add(currentThread); - + if (threads.Count > 0) + { + i = threads.BinarySearch(currentThread, _byDynamicPriority); + if (i < 0) i = ~i; + } + + threads.Insert(i, currentThread); + } + else + { + _arbiterThreads.Add(address, [currentThread]); + } + currentThread.Reschedule(ThreadSchedState.Paused); if (timeout > 0) @@ -413,7 +457,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (currentThread.WaitingInArbitration) { - _arbiterThreads.Remove(currentThread); + _arbiterThreads[address].Remove(currentThread); currentThread.WaitingInArbitration = false; } @@ -486,15 +530,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // or equal to the Count of threads to be signaled, or Count is zero // or negative. It is incremented if there are no threads waiting. int waitingCount = 0; - - foreach (KThread thread in _arbiterThreads) + + if (_arbiterThreads.TryGetValue(address, out List threads)) { - if (thread.MutexAddress == address && - ++waitingCount >= count) - { - break; - } + waitingCount = threads.Count; } + if (waitingCount > 0) { @@ -561,55 +602,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading thread.WaitingInArbitration = false; } - static bool ArbiterThreadPredecate(KThread thread, ulong address) - { - return thread.MutexAddress == address; - } + _arbiterThreads.TryGetValue(address, out List threads); - WakeThreads(_arbiterThreads, count, RemoveArbiterThread, ArbiterThreadPredecate, address); + if (threads is not null && threads.Count > 0) + { + WakeThreads(threads, count, RemoveArbiterThread); + } } private static int WakeThreads( List threads, int count, - Action removeCallback, - Func predicate, - ulong address = 0) + Action removeCallback) { - KThread[] candidates = _threadArrayPool.Allocate(); - if (candidates.Length < threads.Count) - { - Array.Resize(ref candidates, threads.Count); - } - - int validCount = 0; - - for (int i = 0; i < threads.Count; i++) - { - if (predicate(threads[i], address)) - { - candidates[validCount++] = threads[i]; - } - } - - Span candidatesSpan = candidates.AsSpan(..validCount); - - candidatesSpan.Sort((x, y) => (x.DynamicPriority.CompareTo(y.DynamicPriority))); + int validCount = count > 0 ? Math.Min(count, threads.Count) : threads.Count; - if (count > 0) - { - candidatesSpan = candidatesSpan[..Math.Min(count, candidatesSpan.Length)]; - } - - foreach (KThread thread in candidatesSpan) + for (int i = 0; i < validCount; i++) { + KThread thread = threads[i]; removeCallback(thread); - threads.Remove(thread); } - _threadArrayPool.Release(candidates); - + threads.RemoveRange(0, validCount); + return validCount; } + + private class ByDynamicPriority : IComparer + { + public int Compare(KThread x, KThread y) + { + return x!.DynamicPriority.CompareTo(y!.DynamicPriority); + } + } } } diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs index 64cd4d595..aaa4ccd99 100644 --- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs +++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs @@ -61,8 +61,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading public KSynchronizationObject SignaledObj { get; set; } - public ulong CondVarAddress { get; set; } - private ulong _entrypoint; private ThreadStart _customThreadStart; private bool _forcedUnschedulable; diff --git a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs index 0e35b4812..cb61f6e3c 100644 --- a/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs +++ b/src/Ryujinx.HLE/HOS/Services/Account/Acc/Types/AccountState.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.HLE.HOS.Services.Account.Acc { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AccountState { Closed, diff --git a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs index 8e0f515ba..7aac6f3ea 100644 --- a/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs +++ b/src/Ryujinx.HLE/HOS/Services/Am/AppletAE/AllSystemAppletProxiesService/SystemAppletProxy/ISelfController.cs @@ -416,7 +416,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.Sys return ResultCode.InvalidParameters; } - Logger.Stub?.PrintStub(LogClass.ServiceAm, new { albumReportOption }); + context.Device.UIHandler.TakeScreenshot(); return ResultCode.Success; } diff --git a/src/Ryujinx.HLE/HOS/Services/IpcService.cs b/src/Ryujinx.HLE/HOS/Services/IpcService.cs index 4c354ebc6..c7dee64fb 100644 --- a/src/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/src/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -23,6 +23,9 @@ namespace Ryujinx.HLE.HOS.Services private int _selfId; private bool _isDomain; + // cache array so we don't recreate it all the time + private object[] _parameters = [null]; + public IpcService(ServerBase server = null, bool registerTipc = false) { Stopwatch sw = Stopwatch.StartNew(); @@ -146,7 +149,9 @@ namespace Ryujinx.HLE.HOS.Services { Logger.Trace?.Print(LogClass.KernelIpc, $"{service.GetType().Name}: {processRequest.Name}"); - result = (ResultCode)processRequest.Invoke(service, [context]); + _parameters[0] = context; + + result = (ResultCode)processRequest.Invoke(service, _parameters); } else { @@ -196,7 +201,9 @@ namespace Ryujinx.HLE.HOS.Services { Logger.Debug?.Print(LogClass.KernelIpc, $"{GetType().Name}: {processRequest.Name}"); - result = (ResultCode)processRequest.Invoke(this, [context]); + _parameters[0] = context; + + result = (ResultCode)processRequest.Invoke(this, _parameters); } else { diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs index 598c7e6e2..ed14b3e15 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nv/INvDrvServices.cs @@ -59,6 +59,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv // TODO: This should call set:sys::GetDebugModeFlag private readonly bool _debugModeEnabled = false; + + private byte[] _ioctl2Buffer = []; + private byte[] _ioctlArgumentBuffer = []; + private byte[] _ioctl3Buffer = []; public INvDrvServices(ServiceCtx context) : base(context.Device.System.NvDrvServer) { @@ -128,27 +132,38 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments)) { - arguments = new byte[inputDataSize]; + if (_ioctlArgumentBuffer.Length < (int)inputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize); + context.Memory.Read(inputDataPosition, arguments); } - else - { - arguments = arguments.ToArray(); - } } else if (isWrite) { - byte[] outputData = new byte[outputDataSize]; - - arguments = new Span(outputData); + if (_ioctlArgumentBuffer.Length < (int)outputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)outputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)outputDataSize); } else { - byte[] temp = new byte[inputDataSize]; + if (!context.Memory.TryReadUnsafe(inputDataPosition, (int)inputDataSize, out arguments)) + { + if (_ioctlArgumentBuffer.Length < (int)inputDataSize) + { + Array.Resize(ref _ioctlArgumentBuffer, (int)inputDataSize); + } + + arguments = _ioctlArgumentBuffer.AsSpan(0, (int)inputDataSize); - context.Memory.Read(inputDataPosition, temp); - - arguments = new Span(temp); + context.Memory.Read(inputDataPosition, arguments); + } } return NvResult.Success; @@ -270,7 +285,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); } } } @@ -474,13 +489,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - byte[] inlineInBuffer = null; - if (!context.Memory.TryReadUnsafe(inlineInBufferPosition, (int)inlineInBufferSize, out Span inlineInBufferSpan)) { - inlineInBuffer = _byteArrayPool.Rent((int)inlineInBufferSize); - inlineInBufferSpan = inlineInBuffer; - context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan[..(int)inlineInBufferSize]); + if (_ioctl2Buffer.Length < (int)inlineInBufferSize) + { + Array.Resize(ref _ioctl2Buffer, (int)inlineInBufferSize); + } + + inlineInBufferSpan = _ioctl2Buffer.AsSpan(0, (int)inlineInBufferSize); + context.Memory.Read(inlineInBufferPosition, inlineInBufferSpan); } if (errorCode == NvResult.Success) @@ -489,7 +506,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan[..(int)inlineInBufferSize]); + NvInternalResult internalResult = deviceFile.Ioctl2(ioctlCommand, arguments, inlineInBufferSpan); if (internalResult == NvInternalResult.NotImplemented) { @@ -500,15 +517,10 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); } } } - - if (inlineInBuffer is not null) - { - _byteArrayPool.Return(inlineInBuffer); - } } context.ResponseData.Write((uint)errorCode); @@ -531,13 +543,15 @@ namespace Ryujinx.HLE.HOS.Services.Nv errorCode = GetIoctlArgument(context, ioctlCommand, out Span arguments); - byte[] inlineOutBuffer = null; - if (!context.Memory.TryReadUnsafe(inlineOutBufferPosition, (int)inlineOutBufferSize, out Span inlineOutBufferSpan)) { - inlineOutBuffer = _byteArrayPool.Rent((int)inlineOutBufferSize); - inlineOutBufferSpan = inlineOutBuffer; - context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize]); + if (_ioctl3Buffer.Length < (int)inlineOutBufferSize) + { + Array.Resize(ref _ioctl3Buffer, (int)inlineOutBufferSize); + } + + inlineOutBufferSpan = _ioctl3Buffer.AsSpan(0, (int)inlineOutBufferSize); + context.Memory.Read(inlineOutBufferPosition, inlineOutBufferSpan); } if (errorCode == NvResult.Success) @@ -546,7 +560,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv if (errorCode == NvResult.Success) { - NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan[..(int)inlineOutBufferSize]); + NvInternalResult internalResult = deviceFile.Ioctl3(ioctlCommand, arguments, inlineOutBufferSpan); if (internalResult == NvInternalResult.NotImplemented) { @@ -557,16 +571,11 @@ namespace Ryujinx.HLE.HOS.Services.Nv if ((ioctlCommand.DirectionValue & NvIoctl.Direction.Write) != 0) { - context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments.ToArray()); - context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan[..(int)inlineOutBufferSize].ToArray()); + context.Memory.Write(context.Request.GetBufferType0x22(0).Position, arguments); + context.Memory.Write(inlineOutBufferPosition, inlineOutBufferSpan); } } } - - if (inlineOutBuffer is not null) - { - _byteArrayPool.Return(inlineOutBuffer); - } } context.ResponseData.Write((uint)errorCode); diff --git a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs index dcc01bf38..a54dc637e 100644 --- a/src/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/src/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -454,8 +454,9 @@ namespace Ryujinx.HLE.HOS.Services response.RawData = _responseDataStream.ToArray(); - using RecyclableMemoryStream responseStream = response.GetStreamTipc(); + RecyclableMemoryStream responseStream = response.GetStreamTipc(); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + MemoryStreamManager.Shared.ReleaseStream(responseStream); } else { @@ -464,8 +465,9 @@ namespace Ryujinx.HLE.HOS.Services if (!isTipcCommunication) { - using RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); + RecyclableMemoryStream responseStream = response.GetStream((long)_selfThread.TlsAddress, recvListAddr | ((ulong)PointerBufferSize << 48)); _selfProcess.CpuMemory.Write(_selfThread.TlsAddress, responseStream.GetReadOnlySequence()); + MemoryStreamManager.Shared.ReleaseStream(responseStream); } return shouldReply; diff --git a/src/Ryujinx.HLE/UI/IHostUIHandler.cs b/src/Ryujinx.HLE/UI/IHostUIHandler.cs index b5c5cb168..79b479d8a 100644 --- a/src/Ryujinx.HLE/UI/IHostUIHandler.cs +++ b/src/Ryujinx.HLE/UI/IHostUIHandler.cs @@ -68,5 +68,10 @@ namespace Ryujinx.HLE.UI /// Displays the player select dialog and returns the selected profile. /// UserProfile ShowPlayerSelectDialog(); + + /// + /// Takes a screenshot from the current renderer and saves it in the screenshots folder. + /// + void TakeScreenshot(); } } diff --git a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs index d723e5322..412337b6b 100644 --- a/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs +++ b/src/Ryujinx.Horizon/Sdk/OsTypes/Impl/MultiWaitImpl.cs @@ -19,6 +19,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl private int _waitingThreadHandle; private MultiWaitHolderBase _signaledHolder; + + ObjectPool _objectHandlePool = new(() => new int[64]); + ObjectPool _objectPool = new(() => new MultiWaitHolderBase[64]); public long CurrentTime { get; private set; } @@ -76,11 +79,15 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl private MultiWaitHolderBase WaitAnyHandleImpl(bool infinite, long timeout) { - Span objectHandles = new int[64]; + int[] objectHandles = _objectHandlePool.Allocate(); + Span objectHandlesSpan = objectHandles; + objectHandlesSpan.Clear(); - Span objects = new MultiWaitHolderBase[64]; + MultiWaitHolderBase[] objects = _objectPool.Allocate(); + Span objectsSpan = objects; + objectsSpan.Clear(); - int count = FillObjectsArray(objectHandles, objects); + int count = FillObjectsArray(objectHandlesSpan, objectsSpan); long endTime = infinite ? long.MaxValue : PerformanceCounter.ElapsedMilliseconds * 1000000; @@ -98,7 +105,7 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl } else { - index = WaitSynchronization(objectHandles[..count], minTimeout); + index = WaitSynchronization(objectHandlesSpan[..count], minTimeout); DebugUtil.Assert(index != WaitInvalid); } @@ -116,12 +123,18 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl { _signaledHolder = minTimeoutObject; + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } } else { + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return null; } @@ -131,6 +144,9 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl { if (_signaledHolder != null) { + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } @@ -139,8 +155,11 @@ namespace Ryujinx.Horizon.Sdk.OsTypes.Impl default: lock (_lock) { - _signaledHolder = objects[index]; + _signaledHolder = objectsSpan[index]; + _objectHandlePool.Release(objectHandles); + _objectPool.Release(objects); + return _signaledHolder; } } diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index dd8907a4b..84f9e89ab 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -532,8 +532,6 @@ namespace Ryujinx.Input.HLE hidKeyboard.Modifier |= value << entry.Target; } - - ArrayPool.Shared.Return(keyboardState.KeysState); return hidKeyboard; diff --git a/src/Ryujinx.Input/HLE/NpadManager.cs b/src/Ryujinx.Input/HLE/NpadManager.cs index 866504128..f2936aa72 100644 --- a/src/Ryujinx.Input/HLE/NpadManager.cs +++ b/src/Ryujinx.Input/HLE/NpadManager.cs @@ -20,7 +20,6 @@ namespace Ryujinx.Input.HLE { public class NpadManager : IDisposable { - private static readonly ObjectPool> _hleMotionStatesPool = new (() => new List(NpadDevices.MaxControllers)); private readonly CemuHookClient _cemuHookClient; private readonly Lock _lock = new(); @@ -40,6 +39,9 @@ namespace Ryujinx.Input.HLE private bool _enableKeyboard; private bool _enableMouse; private Switch _device; + + private readonly List _hleInputStates = []; + private readonly List _hleMotionStates = new(NpadDevices.MaxControllers); public NpadManager(IGamepadDriver keyboardDriver, IGamepadDriver gamepadDriver, IGamepadDriver mouseDriver) { @@ -217,8 +219,8 @@ namespace Ryujinx.Input.HLE { lock (_lock) { - List hleInputStates = []; - List hleMotionStates = _hleMotionStatesPool.Allocate(); + _hleInputStates.Clear(); + _hleMotionStates.Clear(); KeyboardInput? hleKeyboardInput = null; @@ -260,14 +262,14 @@ namespace Ryujinx.Input.HLE inputState.PlayerId = playerIndex; motionState.Item1.PlayerId = playerIndex; - hleInputStates.Add(inputState); - hleMotionStates.Add(motionState.Item1); + _hleInputStates.Add(inputState); + _hleMotionStates.Add(motionState.Item1); if (isJoyconPair && !motionState.Item2.Equals(default)) { motionState.Item2.PlayerId = playerIndex; - hleMotionStates.Add(motionState.Item2); + _hleMotionStates.Add(motionState.Item2); } } @@ -276,8 +278,8 @@ namespace Ryujinx.Input.HLE hleKeyboardInput = NpadController.GetHLEKeyboardInput(_keyboardDriver); } - _device.Hid.Npads.Update(hleInputStates); - _device.Hid.Npads.UpdateSixAxis(hleMotionStates); + _device.Hid.Npads.Update(_hleInputStates); + _device.Hid.Npads.UpdateSixAxis(_hleMotionStates); if (hleKeyboardInput.HasValue) { @@ -328,10 +330,7 @@ namespace Ryujinx.Input.HLE _device.Hid.Mouse.Update(0, 0); } - _device.TamperMachine.UpdateInput(hleInputStates); - - hleMotionStates.Clear(); - _hleMotionStatesPool.Release(hleMotionStates); + _device.TamperMachine.UpdateInput(_hleInputStates); } } diff --git a/src/Ryujinx.Input/IKeyboard.cs b/src/Ryujinx.Input/IKeyboard.cs index 7fecaaa5d..c51d5aea3 100644 --- a/src/Ryujinx.Input/IKeyboard.cs +++ b/src/Ryujinx.Input/IKeyboard.cs @@ -8,6 +8,8 @@ namespace Ryujinx.Input /// public interface IKeyboard : IGamepad { + private static bool[] _keyState; + /// /// Check if a given key is pressed on the keyboard. /// @@ -29,15 +31,17 @@ namespace Ryujinx.Input [MethodImpl(MethodImplOptions.AggressiveInlining)] static KeyboardStateSnapshot GetStateSnapshot(IKeyboard keyboard) { + if (_keyState is null) + { + _keyState = new bool[(int)Key.Count]; + } - bool[] keysState = ArrayPool.Shared.Rent((int)Key.Count); - for (Key key = 0; key < Key.Count; key++) { - keysState[(int)key] = keyboard.IsPressed(key); + _keyState[(int)key] = keyboard.IsPressed(key); } - return new KeyboardStateSnapshot(keysState); + return new KeyboardStateSnapshot(_keyState); } } } diff --git a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs index c6a0197d4..09311a830 100644 --- a/src/Ryujinx.Memory/Range/INonOverlappingRange.cs +++ b/src/Ryujinx.Memory/Range/INonOverlappingRange.cs @@ -3,7 +3,7 @@ namespace Ryujinx.Memory.Range /// /// Range of memory that can be split in two. /// - public interface INonOverlappingRange : IRange + public interface INonOverlappingRange : IRangeListRange where T : class, IRangeListRange { /// /// Split this region into two, around the specified address. @@ -11,6 +11,6 @@ namespace Ryujinx.Memory.Range /// /// Address to split the region around /// The second part of the split region, with start address at the given split. - public INonOverlappingRange Split(ulong splitAddress); + public INonOverlappingRange Split(ulong splitAddress); } } diff --git a/src/Ryujinx.Memory/Range/IRange.cs b/src/Ryujinx.Memory/Range/IRange.cs index c85e21d1d..c8f50a921 100644 --- a/src/Ryujinx.Memory/Range/IRange.cs +++ b/src/Ryujinx.Memory/Range/IRange.cs @@ -24,8 +24,8 @@ namespace Ryujinx.Memory.Range /// Check if this range overlaps with another. /// /// Base address - /// Size of the range + /// EndAddress of the range /// True if overlapping, false otherwise - bool OverlapsWith(ulong address, ulong size); + bool OverlapsWith(ulong address, ulong endAddress); } } diff --git a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs index 7560d2ae7..1ef51644c 100644 --- a/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs +++ b/src/Ryujinx.Memory/Range/NonOverlappingRangeList.cs @@ -11,7 +11,7 @@ namespace Ryujinx.Memory.Range /// A range list that assumes ranges are non-overlapping, with list items that can be split in two to avoid overlaps. /// /// Type of the range. - public unsafe class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange + public class NonOverlappingRangeList : RangeListBase where T : class, INonOverlappingRange { public readonly ReaderWriterLockSlim Lock = new(); @@ -32,83 +32,18 @@ namespace Ryujinx.Memory.Range /// The item to be added public override void Add(T item) { + Debug.Assert(item.Address != item.EndAddress); + int index = BinarySearch(item.Address); if (index < 0) { index = ~index; } - - RangeItem rangeItem = _rangeItemPool.Allocate().Set(item); - - Insert(index, rangeItem); - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(T item) - { - int index = BinarySearch(item.Address); - - if (index >= 0 && Items[index].Value.Equals(item)) - { - RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - return false; - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(RangeItem item) - { - int index = BinarySearch(item.Address); - - RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Insert(int index, RangeItem item) - { - Debug.Assert(item.Address != item.EndAddress); if (Count + 1 > Items.Length) { - Array.Resize(ref Items, Items.Length + BackingGrowthSize); + Array.Resize(ref Items, (int)(Items.Length * 1.5)); } if (index >= Count) @@ -145,8 +80,6 @@ namespace Ryujinx.Memory.Range [MethodImpl(MethodImplOptions.AggressiveInlining)] private void RemoveAt(int index) { - _rangeItemPool.Release(Items[index]); - if (index < Count - 1) { Items[index + 1].Previous = index > 0 ? Items[index - 1] : null; @@ -173,7 +106,7 @@ namespace Ryujinx.Memory.Range { int index = BinarySearch(item.Address); - if (index >= 0 && Items[index].Value.Equals(item)) + if (index >= 0 && Items[index] == item) { RemoveAt(index); @@ -188,7 +121,7 @@ namespace Ryujinx.Memory.Range /// /// The first item in the range of items to be removed /// The last item in the range of items to be removed - public override void RemoveRange(RangeItem startItem, RangeItem endItem) + public override void RemoveRange(T startItem, T endItem) { if (startItem is null) { @@ -197,7 +130,7 @@ namespace Ryujinx.Memory.Range if (startItem == endItem) { - Remove(startItem.Value); + Remove(startItem); return; } @@ -229,42 +162,45 @@ namespace Ryujinx.Memory.Range /// Size of the range public void RemoveRange(ulong address, ulong size) { - int startIndex = BinarySearchLeftEdge(address, address + size); + (int startIndex, int endIndex) = BinarySearchEdges(address, address + size); if (startIndex < 0) { return; } - int endIndex = startIndex; - - while (Items[endIndex] is not null && Items[endIndex].Address < address + size) + if (startIndex == endIndex - 1) { - if (endIndex == Count - 1) - { - break; - } - - endIndex++; + RemoveAt(startIndex); + return; } - if (endIndex < Count - 1) + RemoveRangeInternal(startIndex, endIndex); + } + + /// + /// Removes a range of items from the item list + /// + /// Start index of the range + /// End index of the range (exclusive) + private void RemoveRangeInternal(int index, int endIndex) + { + if (endIndex < Count) { - Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; + Items[endIndex].Previous = index > 0 ? Items[index - 1] : null; } - if (startIndex > 0) + if (index > 0) { - Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; + Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null; } - - if (endIndex < Count - 1) + if (endIndex < Count) { - Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); + Array.Copy(Items, endIndex, Items, index, Count - endIndex); } - Count -= endIndex - startIndex + 1; + Count -= endIndex - index; } /// @@ -296,8 +232,8 @@ namespace Ryujinx.Memory.Range // So we need to return both the split 0-1 and 1-2 ranges. Lock.EnterWriteLock(); - (RangeItem first, RangeItem last) = FindOverlapsAsNodes(address, size); - list = new List(); + (T first, T last) = FindOverlapsAsNodes(address, size); + list = []; if (first is null) { @@ -311,42 +247,41 @@ namespace Ryujinx.Memory.Range ulong lastAddress = address; ulong endAddress = address + size; - RangeItem current = first; + T current = first; while (last is not null && current is not null && current.Address < endAddress) { - T region = current.Value; - if (first == last && region.Address == address && region.Size == size) + if (first == last && current.Address == address && current.Size == size) { // Exact match, no splitting required. - list.Add(region); + list.Add(current); Lock.ExitWriteLock(); return; } - if (lastAddress < region.Address) + if (lastAddress < current.Address) { // There is a gap between this region and the last. We need to fill it. - T fillRegion = factory(lastAddress, region.Address - lastAddress); + T fillRegion = factory(lastAddress, current.Address - lastAddress); list.Add(fillRegion); Add(fillRegion); } - if (region.Address < address) + if (current.Address < address) { // Split the region around our base address and take the high half. - region = Split(region, address); + current = Split(current, address); } - if (region.EndAddress > address + size) + if (current.EndAddress > address + size) { // Split the region around our end address and take the low half. - Split(region, address + size); + Split(current, address + size); } - list.Add(region); - lastAddress = region.EndAddress; + list.Add(current); + lastAddress = current.EndAddress; current = current.Next; } @@ -374,7 +309,6 @@ namespace Ryujinx.Memory.Range private T Split(T region, ulong splitAddress) { T newRegion = (T)region.Split(splitAddress); - Update(region); Add(newRegion); return newRegion; } @@ -386,16 +320,11 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The leftmost overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlap(ulong address, ulong size) + public override T FindOverlap(ulong address, ulong size) { int index = BinarySearchLeftEdge(address, address + size); - if (index < 0) - { - return null; - } - - return Items[index]; + return index < 0 ? null : Items[index]; } /// @@ -405,16 +334,11 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or null if none is found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlapFast(ulong address, ulong size) + public override T FindOverlapFast(ulong address, ulong size) { int index = BinarySearch(address, address + size); - if (index < 0) - { - return null; - } - - return Items[index]; + return index < 0 ? null : Items[index]; } /// @@ -424,23 +348,18 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The first and last overlapping items, or null if none are found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (RangeItem, RangeItem) FindOverlapsAsNodes(ulong address, ulong size) + public (T, T) FindOverlapsAsNodes(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - if (index < 0) - { - return (null, null); - } - - return (Items[index], Items[endIndex - 1]); + return index < 0 ? (null, null) : (Items[index], Items[endIndex - 1]); } - public RangeItem[] FindOverlapsAsArray(ulong address, ulong size, out int length) + public T[] FindOverlapsAsArray(ulong address, ulong size, out int length) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - RangeItem[] result; + T[] result; if (index < 0) { @@ -449,29 +368,20 @@ namespace Ryujinx.Memory.Range } else { - result = ArrayPool>.Shared.Rent(endIndex - index); + result = ArrayPool.Shared.Rent(endIndex - index); length = endIndex - index; - Array.Copy(Items, index, result, 0, endIndex - index); + Items.AsSpan(index, endIndex - index).CopyTo(result); } return result; } - public Span> FindOverlapsAsSpan(ulong address, ulong size) + public ReadOnlySpan FindOverlapsAsSpan(ulong address, ulong size) { (int index, int endIndex) = BinarySearchEdges(address, address + size); - Span> result; - - if (index < 0) - { - result = []; - } - else - { - result = Items.AsSpan().Slice(index, endIndex - index); - } + ReadOnlySpan result = index < 0 ? [] : Items.AsSpan(index, endIndex - index); return result; } @@ -480,7 +390,7 @@ namespace Ryujinx.Memory.Range { for (int i = 0; i < Count; i++) { - yield return Items[i].Value; + yield return Items[i]; } } } diff --git a/src/Ryujinx.Memory/Range/RangeList.cs b/src/Ryujinx.Memory/Range/RangeList.cs index 63025f1e8..e7ea55a94 100644 --- a/src/Ryujinx.Memory/Range/RangeList.cs +++ b/src/Ryujinx.Memory/Range/RangeList.cs @@ -14,14 +14,14 @@ namespace Ryujinx.Memory.Range /// startIndex is inclusive. /// endIndex is exclusive. /// - public readonly struct OverlapResult where T : IRange + public readonly struct OverlapResult where T : class, IRangeListRange { public readonly int StartIndex = -1; public readonly int EndIndex = -1; - public readonly RangeItem QuickResult; + public readonly T QuickResult; public int Count => EndIndex - StartIndex; - public OverlapResult(int startIndex, int endIndex, RangeItem quickResult = null) + public OverlapResult(int startIndex, int endIndex, T quickResult = null) { this.StartIndex = startIndex; this.EndIndex = endIndex; @@ -33,7 +33,7 @@ namespace Ryujinx.Memory.Range /// Sorted list of ranges that supports binary search. /// /// Type of the range. - public class RangeList : RangeListBase where T : IRange + public class RangeList : RangeListBase where T : class, IRangeListRange { public readonly ReaderWriterLockSlim Lock = new(); @@ -61,104 +61,6 @@ namespace Ryujinx.Memory.Range index = ~index; } - Insert(index, new RangeItem(item)); - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(T item) - { - int index = BinarySearch(item.Address); - - if (index >= 0) - { - while (index < Count) - { - if (Items[index].Value.Equals(item)) - { - RangeItem rangeItem = new(item) { Previous = Items[index].Previous, Next = Items[index].Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - if (Items[index].Address > item.Address) - { - break; - } - - index++; - } - } - - return false; - } - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected override bool Update(RangeItem item) - { - int index = BinarySearch(item.Address); - - if (index >= 0) - { - while (index < Count) - { - if (Items[index].Equals(item)) - { - RangeItem rangeItem = new(item.Value) { Previous = item.Previous, Next = item.Next }; - - if (index > 0) - { - Items[index - 1].Next = rangeItem; - } - - if (index < Count - 1) - { - Items[index + 1].Previous = rangeItem; - } - - Items[index] = rangeItem; - - return true; - } - - if (Items[index].Address > item.Address) - { - break; - } - - index++; - } - } - - return false; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Insert(int index, RangeItem item) - { - Debug.Assert(item.Address != item.EndAddress); - - Debug.Assert(item.Address % 32 == 0); - if (Count + 1 > Items.Length) { Array.Resize(ref Items, Items.Length + BackingGrowthSize); @@ -220,7 +122,7 @@ namespace Ryujinx.Memory.Range /// The first item in the range of items to be removed /// The last item in the range of items to be removed [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void RemoveRange(RangeItem startItem, RangeItem endItem) + public override void RemoveRange(T startItem, T endItem) { if (startItem is null) { @@ -229,30 +131,29 @@ namespace Ryujinx.Memory.Range if (startItem == endItem) { - Remove(startItem.Value); + Remove(startItem); return; } - int startIndex = BinarySearch(startItem.Address); - int endIndex = BinarySearch(endItem.Address); + (int index, int endIndex) = BinarySearchEdges(startItem.Address, endItem.EndAddress); - if (endIndex < Count - 1) + if (endIndex < Count) { - Items[endIndex + 1].Previous = startIndex > 0 ? Items[startIndex - 1] : null; + Items[endIndex].Previous = index > 0 ? Items[index - 1] : null; } - if (startIndex > 0) + if (index > 0) { - Items[startIndex - 1].Next = endIndex < Count - 1 ? Items[endIndex + 1] : null; + Items[index - 1].Next = endIndex < Count ? Items[endIndex] : null; } - if (endIndex < Count - 1) + if (endIndex < Count) { - Array.Copy(Items, endIndex + 1, Items, startIndex, Count - endIndex - 1); + Array.Copy(Items, endIndex, Items, index, Count - endIndex); } - Count -= endIndex - startIndex + 1; + Count -= endIndex - index; } /// @@ -268,7 +169,7 @@ namespace Ryujinx.Memory.Range { while (index < Count) { - if (Items[index].Value.Equals(item)) + if (Items[index] == item) { RemoveAt(index); @@ -298,7 +199,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or the default value for the type if none found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlap(ulong address, ulong size) + public override T FindOverlap(ulong address, ulong size) { int index = BinarySearchLeftEdge(address, address + size); @@ -321,7 +222,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// The overlapping item, or the default value for the type if none found [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override RangeItem FindOverlapFast(ulong address, ulong size) + public override T FindOverlapFast(ulong address, ulong size) { int index = BinarySearch(address, address + size); @@ -340,7 +241,7 @@ namespace Ryujinx.Memory.Range /// Size in bytes of the range /// Output array where matches will be written. It is automatically resized to fit the results /// Range information of overlapping items found - private OverlapResult FindOverlaps(ulong address, ulong size, ref RangeItem[] output) + private OverlapResult FindOverlaps(ulong address, ulong size, ref T[] output) { int outputCount = 0; @@ -353,7 +254,7 @@ namespace Ryujinx.Memory.Range for (int i = startIndex; i < Count; i++) { - ref RangeItem item = ref Items[i]; + T item = Items[i]; if (item.Address >= endAddress) { @@ -398,7 +299,7 @@ namespace Ryujinx.Memory.Range { for (int i = 0; i < Count; i++) { - yield return Items[i].Value; + yield return Items[i]; } } } diff --git a/src/Ryujinx.Memory/Range/RangeListBase.cs b/src/Ryujinx.Memory/Range/RangeListBase.cs index 01fe1b0dc..9f0284253 100644 --- a/src/Ryujinx.Memory/Range/RangeListBase.cs +++ b/src/Ryujinx.Memory/Range/RangeListBase.cs @@ -1,56 +1,22 @@ using Ryujinx.Common; +using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Ryujinx.Memory.Range { - public class RangeItem where TValue : IRange + public interface IRangeListRange : IRange where TValue : class, IRangeListRange { - public RangeItem Next; - public RangeItem Previous; - - public ulong Address; - public ulong EndAddress; - - public TValue Value; - - public RangeItem() - { - - } - - public RangeItem(TValue value) - { - Address = value.Address; - EndAddress = value.Address + value.Size; - Value = value; - } - - public RangeItem Set(TValue value) - { - Next = null; - Previous = null; - Address = value.Address; - EndAddress = value.Address + value.Size; - Value = value; - - return this; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool OverlapsWith(ulong address, ulong endAddress) - { - return Address < endAddress && address < EndAddress; - } + public TValue Next { get; set; } + public TValue Previous { get; set; } } - public unsafe abstract class RangeListBase : IEnumerable where T : IRange + public unsafe abstract class RangeListBase : IEnumerable where T : class, IRangeListRange { - protected static readonly ObjectPool> _rangeItemPool = new(() => new RangeItem()); private const int BackingInitialSize = 1024; - protected RangeItem[] Items; + protected T[] Items; protected readonly int BackingGrowthSize; public int Count { get; protected set; } @@ -62,32 +28,18 @@ namespace Ryujinx.Memory.Range protected RangeListBase(int backingInitialSize = BackingInitialSize) { BackingGrowthSize = backingInitialSize; - Items = new RangeItem[backingInitialSize]; + Items = new T[backingInitialSize]; } public abstract void Add(T item); - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The item to be updated - /// True if the item was located and updated, false otherwise - protected abstract bool Update(T item); - - /// - /// Updates an item's end address on the list. Address must be the same. - /// - /// The RangeItem to be updated - /// True if the item was located and updated, false otherwise - protected abstract bool Update(RangeItem item); public abstract bool Remove(T item); - public abstract void RemoveRange(RangeItem startItem, RangeItem endItem); + public abstract void RemoveRange(T startItem, T endItem); - public abstract RangeItem FindOverlap(ulong address, ulong size); + public abstract T FindOverlap(ulong address, ulong size); - public abstract RangeItem FindOverlapFast(ulong address, ulong size); + public abstract T FindOverlapFast(ulong address, ulong size); /// /// Performs binary search on the internal list of items. @@ -106,7 +58,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; if (item.Address == address) { @@ -144,7 +96,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; if (item.OverlapsWith(address, endAddress)) { @@ -185,7 +137,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -237,7 +189,7 @@ namespace Ryujinx.Memory.Range int middle = right - (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -282,7 +234,7 @@ namespace Ryujinx.Memory.Range if (Count == 1) { - ref RangeItem item = ref Items[0]; + T item = Items[0]; if (item.OverlapsWith(address, endAddress)) { @@ -312,7 +264,7 @@ namespace Ryujinx.Memory.Range int middle = left + (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); @@ -369,7 +321,7 @@ namespace Ryujinx.Memory.Range int middle = right - (range >> 1); - ref RangeItem item = ref Items[middle]; + T item = Items[middle]; bool match = item.OverlapsWith(address, endAddress); diff --git a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs index 7226fe954..09703a253 100644 --- a/src/Ryujinx.Memory/Tracking/AbstractRegion.cs +++ b/src/Ryujinx.Memory/Tracking/AbstractRegion.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Memory.Tracking /// /// A region of memory. /// - abstract class AbstractRegion : INonOverlappingRange + abstract class AbstractRegion : INonOverlappingRange where T : class, INonOverlappingRange { /// /// Base address. @@ -21,6 +21,9 @@ namespace Ryujinx.Memory.Tracking /// End address. /// public ulong EndAddress => Address + Size; + + public T Next { get; set; } + public T Previous { get; set; } /// /// Create a new region. @@ -37,11 +40,11 @@ namespace Ryujinx.Memory.Tracking /// Check if this range overlaps with another. /// /// Base address - /// Size of the range + /// End address /// True if overlapping, false otherwise - public bool OverlapsWith(ulong address, ulong size) + public bool OverlapsWith(ulong address, ulong endAddress) { - return Address < address + size && address < EndAddress; + return Address < endAddress && address < EndAddress; } /// @@ -68,6 +71,6 @@ namespace Ryujinx.Memory.Tracking /// /// Address to split the region around /// The second part of the split region, with start address at the given split. - public abstract INonOverlappingRange Split(ulong splitAddress); + public abstract INonOverlappingRange Split(ulong splitAddress); } } diff --git a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs index b4b06da8c..0160791e8 100644 --- a/src/Ryujinx.Memory/Tracking/MemoryTracking.cs +++ b/src/Ryujinx.Memory/Tracking/MemoryTracking.cs @@ -81,10 +81,10 @@ namespace Ryujinx.Memory.Tracking { NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; regions.Lock.EnterReadLock(); - Span> overlaps = regions.FindOverlapsAsSpan(va, size); + ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size); for (int i = 0; i < overlaps.Length; i++) { - VirtualRegion region = overlaps[i].Value; + VirtualRegion region = overlaps[i]; // If the region has been fully remapped, signal that it has been mapped again. bool remapped = _memoryManager.IsRangeMapped(region.Address, region.Size); @@ -117,11 +117,11 @@ namespace Ryujinx.Memory.Tracking { NonOverlappingRangeList regions = type == 0 ? _virtualRegions : _guestVirtualRegions; regions.Lock.EnterReadLock(); - Span> overlaps = regions.FindOverlapsAsSpan(va, size); + ReadOnlySpan overlaps = regions.FindOverlapsAsSpan(va, size); for (int i = 0; i < overlaps.Length; i++) { - overlaps[i].Value.SignalMappingChanged(false); + overlaps[i].SignalMappingChanged(false); } regions.Lock.ExitReadLock(); } @@ -301,7 +301,7 @@ namespace Ryujinx.Memory.Tracking // We use the non-span method here because keeping the lock will cause a deadlock. regions.Lock.EnterReadLock(); - RangeItem[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); + VirtualRegion[] overlaps = regions.FindOverlapsAsArray(address, size, out int length); regions.Lock.ExitReadLock(); if (length == 0 && !precise) @@ -327,7 +327,7 @@ namespace Ryujinx.Memory.Tracking for (int i = 0; i < length; i++) { - VirtualRegion region = overlaps[i].Value; + VirtualRegion region = overlaps[i]; if (precise) { @@ -341,7 +341,7 @@ namespace Ryujinx.Memory.Tracking if (length != 0) { - ArrayPool>.Shared.Return(overlaps); + ArrayPool.Shared.Return(overlaps); } } } diff --git a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs index b86631db2..e95754c7a 100644 --- a/src/Ryujinx.Memory/Tracking/VirtualRegion.cs +++ b/src/Ryujinx.Memory/Tracking/VirtualRegion.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Memory.Tracking /// /// A region of virtual memory. /// - class VirtualRegion : AbstractRegion + class VirtualRegion : AbstractRegion { public List Handles = []; @@ -137,7 +137,7 @@ namespace Ryujinx.Memory.Tracking } } - public override INonOverlappingRange Split(ulong splitAddress) + public override INonOverlappingRange Split(ulong splitAddress) { VirtualRegion newRegion = new(_tracking, splitAddress, EndAddress - splitAddress, Guest, _lastPermission); Size = splitAddress - Address; diff --git a/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs index 4de92ee4a..068931fa3 100644 --- a/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs +++ b/src/Ryujinx.UI.LocaleGenerator/LocaleGenerator.cs @@ -1,5 +1,7 @@ using Microsoft.CodeAnalysis; using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Text; @@ -10,23 +12,34 @@ namespace Ryujinx.UI.LocaleGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - IncrementalValuesProvider localeFile = context.AdditionalTextsProvider.Where(static x => x.Path.EndsWith("locales.json")); + IncrementalValuesProvider localeFiles = context.AdditionalTextsProvider.Where(static x => Path.GetDirectoryName(x.Path)?.Replace('\\', '/').EndsWith("assets/Locales") ?? false); - IncrementalValuesProvider contents = localeFile.Select((text, cancellationToken) => text.GetText(cancellationToken)!.ToString()); + IncrementalValueProvider> collectedContents = localeFiles.Select((text, cancellationToken) => (text.GetText(cancellationToken)!.ToString(), Path.GetFileName(text.Path))).Collect(); - context.RegisterSourceOutput(contents, (spc, content) => + context.RegisterSourceOutput(collectedContents, (spc, contents) => { - IEnumerable lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"ID\":")).Select(x => x.Split(':')[1].Trim().Replace("\"", string.Empty).Replace(",", string.Empty)); - StringBuilder enumSourceBuilder = new(); enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;"); enumSourceBuilder.AppendLine("public enum LocaleKeys"); enumSourceBuilder.AppendLine("{"); - foreach (string? line in lines) + + foreach ((string, string) content in contents) { - enumSourceBuilder.AppendLine($" {line},"); + IEnumerable lines = content.Item1.Split('\n').Where(x => x.Trim().StartsWith("\"ID\":")).Select(x => x.Split(':')[1].Trim().Replace("\"", string.Empty).Replace(",", string.Empty)); + + foreach (string? line in lines) + { + if (content.Item2 == "Root.json") + { + enumSourceBuilder.AppendLine($" {line},"); + } + else + { + enumSourceBuilder.AppendLine($" {content.Item2.Split('.')[0]}_{line},"); + } + } } - + enumSourceBuilder.AppendLine("}"); spc.AddSource("LocaleKeys", enumSourceBuilder.ToString()); diff --git a/src/Ryujinx/Common/LocaleManager.cs b/src/Ryujinx/Common/LocaleManager.cs index bc9cfdf15..330ef4f18 100644 --- a/src/Ryujinx/Common/LocaleManager.cs +++ b/src/Ryujinx/Common/LocaleManager.cs @@ -8,6 +8,8 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; +using System.IO; +using System.Linq; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Common.Locale @@ -158,52 +160,86 @@ namespace Ryujinx.Ava.Common.Locale LocaleChanged?.Invoke(); } - private static LocalesJson? _localeData; + private static LocalesData? _localeData; private static Dictionary LoadJsonLanguage(string languageCode) { Dictionary localeStrings = new(); - _localeData ??= EmbeddedResources.ReadAllText("Ryujinx/Assets/Locale.json") - .Into(it => JsonHelper.Deserialize(it, LocalesJsonContext.Default.LocalesJson)); - - foreach (LocalesEntry locale in _localeData.Value.Locales) + if (_localeData is null) { - if (locale.Translations.Count < _localeData.Value.Languages.Count) + Dictionary locales = []; + + foreach (string uri in EmbeddedResources.GetAllAvailableResources("Ryujinx/Assets/Locales", ".json")) { - throw new Exception( - $"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!"); + string path = uri[..^".json".Length]; + path = path.Replace('.', '/'); + path = path.Append(".json"); + + locales.TryAdd(Path.GetFileName(path), EmbeddedResources.ReadAllText(path) + .Into(it => JsonHelper.Deserialize(it, LocalesJsonContext.Default.LocalesJson))); } - - if (locale.Translations.Count > _localeData.Value.Languages.Count) + + _localeData = new LocalesData { - throw new Exception( - $"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!"); - } + Languages = EmbeddedResources.ReadAllText("Ryujinx/Assets/Languages.json") + .Into(it => JsonHelper.Deserialize(it, LanguagesJsonContext.Default.LanguagesJson)).Languages.Keys.ToList(), + LocalesFiles = locales + }; + - if (!Enum.TryParse(locale.ID, out LocaleKeys localeKey)) - continue; + } - string str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val) - ? val - : locale.Translations[DefaultLanguageCode]; - - if (string.IsNullOrEmpty(str)) + foreach (LocalesJson file in _localeData.Value.LocalesFiles.Values) + { + foreach (LocalesEntry locale in file.Locales) { - throw new Exception( - $"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null"); - } + if (locale.Translations.Count < _localeData.Value.Languages.Count) + { + throw new Exception( + $"Locale key {{{locale.ID}}} is missing languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!"); + } - localeStrings[localeKey] = str; + if (locale.Translations.Count > _localeData.Value.Languages.Count) + { + throw new Exception( + $"Locale key {{{locale.ID}}} has too many languages! Has {locale.Translations.Count} translations, expected {_localeData.Value.Languages.Count}!"); + } + + if (!Enum.TryParse(locale.ID, out LocaleKeys localeKey)) + continue; + + string str = locale.Translations.TryGetValue(languageCode, out string val) && !string.IsNullOrEmpty(val) + ? val + : locale.Translations[DefaultLanguageCode]; + + if (string.IsNullOrEmpty(str)) + { + throw new Exception( + $"Locale key '{locale.ID}' has no valid translations for desired language {languageCode}! {DefaultLanguageCode} is an empty string or null"); + } + + localeStrings[localeKey] = str; + } } return localeStrings; } } - public struct LocalesJson + public struct LocalesData { public List Languages { get; set; } + public Dictionary LocalesFiles { get; set; } + } + + public struct LanguagesJson + { + public Dictionary Languages { get; set; } + } + + public struct LocalesJson + { public List Locales { get; set; } } @@ -216,4 +252,8 @@ namespace Ryujinx.Ava.Common.Locale [JsonSourceGenerationOptions(WriteIndented = true)] [JsonSerializable(typeof(LocalesJson))] internal partial class LocalesJsonContext : JsonSerializerContext; + + [JsonSourceGenerationOptions(WriteIndented = true)] + [JsonSerializable(typeof(LanguagesJson))] + internal partial class LanguagesJsonContext : JsonSerializerContext; } diff --git a/src/Ryujinx/Headless/Windows/WindowBase.cs b/src/Ryujinx/Headless/Windows/WindowBase.cs index 8e06a3f20..49b2a389a 100644 --- a/src/Ryujinx/Headless/Windows/WindowBase.cs +++ b/src/Ryujinx/Headless/Windows/WindowBase.cs @@ -580,5 +580,10 @@ namespace Ryujinx.Headless { return AccountSaveDataManager.GetLastUsedUser(); } + + public void TakeScreenshot() + { + throw new NotImplementedException(); + } } } diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs index 4904b8464..d77e79756 100644 --- a/src/Ryujinx/Program.cs +++ b/src/Ryujinx/Program.cs @@ -17,6 +17,7 @@ using Ryujinx.Common.Configuration; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; +using Ryujinx.Common.Utilities; using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Headless; using Ryujinx.SDL3.Common; @@ -46,7 +47,7 @@ namespace Ryujinx.Ava public static int Main(string[] args) { Version = ReleaseInformation.Version; - + if (OperatingSystem.IsWindows()) { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041)) @@ -55,8 +56,11 @@ namespace Ryujinx.Ava return 0; } - if (Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files") || - Environment.CurrentDirectory.StartsWithIgnoreCase("C:\\Program Files (x86)")) + var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); + var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + + if (Environment.CurrentDirectory.StartsWithIgnoreCase(programFiles) || + Environment.CurrentDirectory.StartsWithIgnoreCase(programFilesX86)) { _ = Win32NativeInterop.MessageBoxA(nint.Zero, "Ryujinx is not intended to be run from the Program Files folder. Please move it out and relaunch.", $"Ryujinx {Version}", MbIconwarning); return 0; @@ -73,11 +77,23 @@ namespace Ryujinx.Ava } } + bool noGuiArg = ConsumeCommandLineArgument(ref args, "--no-gui") || ConsumeCommandLineArgument(ref args, "nogui"); + bool coreDumpArg = ConsumeCommandLineArgument(ref args, "--core-dumps"); + + // TODO: Ryujinx causes core dumps on Linux when it exits "uncleanly", eg. through an unhandled exception. + // This is undesirable and causes very odd behavior during development (the process stops responding, + // the .NET debugger freezes or suddenly detaches, /tmp/ gets filled etc.), unless explicitly requested by the user. + // This needs to be investigated, but calling prctl() is better than modifying system-wide settings or leaving this be. + if (!coreDumpArg) + { + OsUtils.SetCoreDumpable(false); + } + PreviewerDetached = true; - if (args.Length > 0 && args[0] is "--no-gui" or "nogui") + if (noGuiArg) { - HeadlessRyujinx.Entrypoint(args[1..]); + HeadlessRyujinx.Entrypoint(args); return 0; } @@ -112,6 +128,14 @@ namespace Ryujinx.Ava : [Win32RenderingMode.Software] }); + private static bool ConsumeCommandLineArgument(ref string[] args, string targetArgument) + { + List argList = [.. args]; + bool found = argList.Remove(targetArgument); + args = argList.ToArray(); + return found; + } + private static void Initialize(string[] args) { // Ensure Discord presence timestamp begins at the absolute start of when Ryujinx is launched @@ -177,7 +201,6 @@ namespace Ryujinx.Ava } } - public static string GetDirGameUserConfig(string gameId, bool changeFolderForGame = false) { if (string.IsNullOrEmpty(gameId)) diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj index 31dc20aac..ddb013412 100644 --- a/src/Ryujinx/Ryujinx.csproj +++ b/src/Ryujinx/Ryujinx.csproj @@ -134,7 +134,7 @@ - + @@ -156,8 +156,8 @@ Assets\RyujinxGameCompatibility.csv - - Assets\Locale.json + + Assets @@ -178,6 +178,6 @@ - + diff --git a/src/Ryujinx/Systems/Configuration/AudioBackend.cs b/src/Ryujinx/Systems/Configuration/AudioBackend.cs index f1a0c2362..da75c9f7c 100644 --- a/src/Ryujinx/Systems/Configuration/AudioBackend.cs +++ b/src/Ryujinx/Systems/Configuration/AudioBackend.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum AudioBackend { Dummy, diff --git a/src/Ryujinx/Systems/Configuration/System/Language.cs b/src/Ryujinx/Systems/Configuration/System/Language.cs index 3087653e9..dd44dff37 100644 --- a/src/Ryujinx/Systems/Configuration/System/Language.cs +++ b/src/Ryujinx/Systems/Configuration/System/Language.cs @@ -1,10 +1,9 @@ -using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.SystemState; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.System { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum Language { Japanese, diff --git a/src/Ryujinx/Systems/Configuration/System/Region.cs b/src/Ryujinx/Systems/Configuration/System/Region.cs index 2ba657876..a85dcb85e 100644 --- a/src/Ryujinx/Systems/Configuration/System/Region.cs +++ b/src/Ryujinx/Systems/Configuration/System/Region.cs @@ -1,10 +1,9 @@ -using Ryujinx.Common.Utilities; using Ryujinx.HLE.HOS.SystemState; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.System { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum Region { Japan, diff --git a/src/Ryujinx/Systems/Configuration/UI/FocusLostType.cs b/src/Ryujinx/Systems/Configuration/UI/FocusLostType.cs index 9e22f3dca..96eb8ee26 100644 --- a/src/Ryujinx/Systems/Configuration/UI/FocusLostType.cs +++ b/src/Ryujinx/Systems/Configuration/UI/FocusLostType.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.UI { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum FocusLostType { DoNothing, diff --git a/src/Ryujinx/Systems/Configuration/UI/UpdaterType.cs b/src/Ryujinx/Systems/Configuration/UI/UpdaterType.cs index bc2696780..584763722 100644 --- a/src/Ryujinx/Systems/Configuration/UI/UpdaterType.cs +++ b/src/Ryujinx/Systems/Configuration/UI/UpdaterType.cs @@ -1,9 +1,8 @@ -using Ryujinx.Common.Utilities; using System.Text.Json.Serialization; namespace Ryujinx.Ava.Systems.Configuration.UI { - [JsonConverter(typeof(TypedStringEnumConverter))] + [JsonConverter(typeof(JsonStringEnumConverter))] public enum UpdaterType { Off, diff --git a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs index 38670e5d5..45235ee3f 100644 --- a/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs +++ b/src/Ryujinx/UI/Applet/AvaHostUIHandler.cs @@ -327,5 +327,10 @@ namespace Ryujinx.Ava.UI.Applet return profile; } + + public void TakeScreenshot() + { + _parent.ViewModel.AppHost.ScreenshotRequested = true; + } } } diff --git a/src/Ryujinx/UI/RyujinxApp.axaml.cs b/src/Ryujinx/UI/RyujinxApp.axaml.cs index 34c2d96ca..efe67d6a7 100644 --- a/src/Ryujinx/UI/RyujinxApp.axaml.cs +++ b/src/Ryujinx/UI/RyujinxApp.axaml.cs @@ -56,6 +56,8 @@ namespace Ryujinx.Ava if (OperatingSystem.IsMacOS()) { + // Switches macOS key held behavior to repeat the input key instead of showing the character accents menu (like doing on an iOS keyboard would). + // https://macos-defaults.com/keyboard/applepressandholdenabled.html Process.Start("/usr/bin/defaults", "write org.ryujinx.Ryujinx ApplePressAndHoldEnabled -bool false"); } } diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 2236b27f6..651dc901c 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1333,7 +1333,10 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public void TakeScreenshot() => AppHost.ScreenshotRequested = true; + public void TakeScreenshot() + { + AppHost.ScreenshotRequested = true; + } public void HideUi() => ShowMenuAndStatusBar = false; diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs index f6bf43795..6c9f4f367 100644 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml.cs @@ -85,29 +85,16 @@ namespace Ryujinx.Ava.UI.Views.Main private static IEnumerable GenerateLanguageMenuItems() { - const string LocalePath = "Ryujinx/Assets/Locale.json"; + const string LanguagesPath = "Ryujinx/Assets/Languages.json"; - string languageJson = EmbeddedResources.ReadAllText(LocalePath); + string languageJson = EmbeddedResources.ReadAllText(LanguagesPath); string currentLanguageCode = LocaleManager.Instance.CurrentLanguageCode; - LocalesJson locales = JsonHelper.Deserialize(languageJson, LocalesJsonContext.Default.LocalesJson); + LanguagesJson languages = JsonHelper.Deserialize(languageJson, LanguagesJsonContext.Default.LanguagesJson); - foreach (string language in locales.Languages) + foreach ((string code, string language) in languages.Languages) { - int index = locales.Locales.FindIndex(x => x.ID == "Language"); - string languageName; - - if (index == -1) - { - languageName = language; - } - else - { - string tr = locales.Locales[index].Translations[language]; - languageName = string.IsNullOrEmpty(tr) - ? language - : tr; - } + string languageName = string.IsNullOrEmpty(language) ? code : language; MenuItem menuItem = new() {