Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8dd1eb333c | ||
|
7dc3a62c14 | ||
|
e59dba42ef | ||
|
bd6937ae5c | ||
|
b82e789d4f | ||
|
4a6724622e | ||
|
0c73eba3db | ||
|
a082e14ede | ||
|
d29da11d5f | ||
|
ea07328aea | ||
|
a0b3d82ee0 | ||
|
609de33b0b | ||
|
dfc0819e72 | ||
|
d4803356bb | ||
|
459efd0db7 | ||
|
8bb7a3fc97 | ||
|
628d092fc6 | ||
|
6c90d50c8e | ||
|
d56bab1e24 | ||
|
a37e2d6e44 | ||
|
25123232bd | ||
|
8927e0669f | ||
|
bbed3b9926 | ||
|
24c8b0edc0 | ||
|
e5066449a5 | ||
|
d704bcd93b | ||
|
c94f0fbb83 | ||
|
d1b30fbe08 | ||
|
4505a7f162 | ||
|
ccbbaddbcb | ||
|
8bf102d2cd | ||
|
2adf031830 | ||
|
bb4a28b525 | ||
|
a8fbcdae9f | ||
|
4e81ab4229 | ||
|
4117c13377 | ||
|
20a392ad55 | ||
|
70fcba39de | ||
|
7795b662a9 |
69
.github/workflows/build.yml
vendored
69
.github/workflows/build.yml
vendored
@@ -10,28 +10,17 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
|
||||||
configuration: [Debug, Release]
|
configuration: [Debug, Release]
|
||||||
include:
|
platform:
|
||||||
- os: ubuntu-latest
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
RELEASE_ZIP_OS_NAME: linux_x64
|
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
|
||||||
|
|
||||||
- os: macOS-latest
|
|
||||||
OS_NAME: macOS x64
|
|
||||||
DOTNET_RUNTIME_IDENTIFIER: osx-x64
|
|
||||||
RELEASE_ZIP_OS_NAME: osx_x64
|
|
||||||
|
|
||||||
- os: windows-latest
|
|
||||||
OS_NAME: Windows x64
|
|
||||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
|
||||||
RELEASE_ZIP_OS_NAME: win_x64
|
|
||||||
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
@@ -40,7 +29,7 @@ jobs:
|
|||||||
- uses: actions/setup-dotnet@v4
|
- uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
global-json-file: global.json
|
global-json-file: global.json
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
- name: Overwrite csc problem matcher
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
run: echo "::add-matcher::.github/csc.json"
|
||||||
|
|
||||||
@@ -49,6 +38,16 @@ jobs:
|
|||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Change config filename
|
||||||
|
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
shell: bash
|
||||||
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
|
- name: Change config filename for macOS
|
||||||
|
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
shell: bash
|
||||||
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'macOS-latest'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||||
|
|
||||||
@@ -58,46 +57,47 @@ jobs:
|
|||||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
retry-codes: 139
|
retry-codes: 139
|
||||||
|
if: matrix.platform.name != 'linux-arm64'
|
||||||
|
|
||||||
- name: Publish Ryujinx
|
- name: Publish Ryujinx
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Headless.SDL2
|
- name: Publish Ryujinx.Headless.SDL2
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Ava
|
- name: Publish Ryujinx.Ava
|
||||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
- name: Set executable bit
|
- name: Set executable bit
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||||
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
|
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
|
||||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
|
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish
|
path: publish
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_sdl2_headless
|
path: publish_sdl2_headless
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Ava artifact
|
- name: Upload Ryujinx.Ava artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_ava
|
path: publish_ava
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
name: macOS Universal (${{ matrix.configuration }})
|
name: macOS Universal (${{ matrix.configuration }})
|
||||||
@@ -135,6 +135,11 @@ jobs:
|
|||||||
id: git_short_hash
|
id: git_short_hash
|
||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Change config filename
|
||||||
|
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
shell: bash
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx.Ava
|
- name: Publish macOS Ryujinx.Ava
|
||||||
run: |
|
run: |
|
||||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
|
||||||
|
4
.github/workflows/mako.yml
vendored
4
.github/workflows/mako.yml
vendored
@@ -9,10 +9,6 @@ on:
|
|||||||
types: [created, edited]
|
types: [created, edited]
|
||||||
issues:
|
issues:
|
||||||
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
|
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
|
||||||
pull_request_review:
|
|
||||||
types: [submitted, dismissed]
|
|
||||||
pull_request_review_comment:
|
|
||||||
types: [created, edited]
|
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
|
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
|
||||||
|
|
||||||
|
43
.github/workflows/release.yml
vendored
43
.github/workflows/release.yml
vendored
@@ -45,22 +45,15 @@ jobs:
|
|||||||
})
|
})
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release ${{ matrix.OS_NAME }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, windows-latest ]
|
platform:
|
||||||
include:
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- os: ubuntu-latest
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
|
||||||
RELEASE_ZIP_OS_NAME: linux_x64
|
|
||||||
|
|
||||||
- os: windows-latest
|
|
||||||
OS_NAME: Windows x64
|
|
||||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
|
||||||
RELEASE_ZIP_OS_NAME: win_x64
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -85,6 +78,7 @@ jobs:
|
|||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Create output dir
|
- name: Create output dir
|
||||||
@@ -92,42 +86,42 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
run: |
|
||||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
||||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_gtk
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_gtk
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
||||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
|
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
|
||||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@@ -186,6 +180,7 @@ jobs:
|
|||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
|
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx.Ava
|
- name: Publish macOS Ryujinx.Ava
|
||||||
|
@@ -8,8 +8,8 @@
|
|||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
@@ -17,12 +17,11 @@
|
|||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
|
||||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||||
@@ -33,10 +32,10 @@
|
|||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
@@ -45,10 +44,10 @@
|
|||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.1" />
|
<PackageVersion Include="System.Drawing.Common" Version="8.0.1" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -4,7 +4,7 @@ Name=Ryujinx
|
|||||||
Type=Application
|
Type=Application
|
||||||
Icon=Ryujinx
|
Icon=Ryujinx
|
||||||
Exec=Ryujinx.sh %f
|
Exec=Ryujinx.sh %f
|
||||||
Comment=Plays Nintendo Switch applications
|
Comment=A Nintendo Switch Emulator
|
||||||
GenericName=Nintendo Switch Emulator
|
GenericName=Nintendo Switch Emulator
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;Emulator;
|
Categories=Game;Emulator;
|
||||||
|
2
distribution/linux/Ryujinx.sh
Normal file → Executable file
2
distribution/linux/Ryujinx.sh
Normal file → Executable file
@@ -17,4 +17,4 @@ if command -v gamemoderun > /dev/null 2>&1; then
|
|||||||
COMMAND="$COMMAND gamemoderun"
|
COMMAND="$COMMAND gamemoderun"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||||
|
@@ -875,6 +875,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||||
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
|
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
|
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
|
||||||
@@ -995,6 +996,7 @@ namespace ARMeilleure.Decoders
|
|||||||
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
||||||
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
@@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Vpadal(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
|
||||||
|
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Vpaddl(ArmEmitterContext context)
|
public static void Vpaddl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
@@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VRINTR (floating-point).
|
||||||
|
public static void Vrintr_S(ArmEmitterContext context)
|
||||||
|
{
|
||||||
|
if (Optimizations.UseAdvSimd)
|
||||||
|
{
|
||||||
|
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmitScalarUnaryOpF32(context, (op1) =>
|
||||||
|
{
|
||||||
|
return EmitRoundByRMode(context, op1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// VRINTZ (floating-point).
|
// VRINTZ (floating-point).
|
||||||
public static void Vrint_Z(ArmEmitterContext context)
|
public static void Vrint_Z(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
|
@@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
|
||||||
|
{
|
||||||
|
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||||
|
|
||||||
|
int elems = op.GetBytesCount() >> op.Size;
|
||||||
|
int pairs = elems >> 1;
|
||||||
|
|
||||||
|
Operand res = GetVecA32(op.Qd);
|
||||||
|
|
||||||
|
for (int index = 0; index < pairs; index++)
|
||||||
|
{
|
||||||
|
int pairIndex = index * 2;
|
||||||
|
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
|
||||||
|
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
|
||||||
|
|
||||||
|
if (op.Size == 2)
|
||||||
|
{
|
||||||
|
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
|
||||||
|
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
|
||||||
|
|
||||||
|
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVecA32(op.Qd), res);
|
||||||
|
}
|
||||||
|
|
||||||
// Narrow
|
// Narrow
|
||||||
|
|
||||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||||
|
@@ -637,6 +637,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vorn,
|
Vorn,
|
||||||
Vorr,
|
Vorr,
|
||||||
Vpadd,
|
Vpadd,
|
||||||
|
Vpadal,
|
||||||
Vpaddl,
|
Vpaddl,
|
||||||
Vpmax,
|
Vpmax,
|
||||||
Vpmin,
|
Vpmin,
|
||||||
@@ -656,6 +657,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vrintm,
|
Vrintm,
|
||||||
Vrintn,
|
Vrintn,
|
||||||
Vrintp,
|
Vrintp,
|
||||||
|
Vrintr,
|
||||||
Vrintx,
|
Vrintx,
|
||||||
Vrshr,
|
Vrshr,
|
||||||
Vrshrn,
|
Vrshrn,
|
||||||
|
@@ -11,15 +11,15 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.dll</TargetPath>
|
<TargetPath>libsoundio.dll</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.dylib</TargetPath>
|
<TargetPath>libsoundio.dylib</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64'">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<TargetPath>libsoundio.so</TargetPath>
|
<TargetPath>libsoundio.so</TargetPath>
|
||||||
</ContentWithTargetPath>
|
</ContentWithTargetPath>
|
||||||
|
@@ -72,6 +72,7 @@
|
|||||||
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
||||||
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
|
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
|
||||||
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
|
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
|
||||||
|
"GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application",
|
||||||
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
||||||
"StatusBarSystemVersion": "System Version: {0}",
|
"StatusBarSystemVersion": "System Version: {0}",
|
||||||
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
||||||
|
@@ -665,7 +665,7 @@ namespace Ryujinx.Modules
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
|
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid)
|
||||||
{
|
{
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
@@ -683,7 +683,7 @@ namespace Ryujinx.Modules
|
|||||||
#else
|
#else
|
||||||
if (showWarnings)
|
if (showWarnings)
|
||||||
{
|
{
|
||||||
if (ReleaseInformation.IsFlatHubBuild())
|
if (ReleaseInformation.IsFlatHubBuild)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
ContentDialogHelper.CreateWarningDialog(
|
ContentDialogHelper.CreateWarningDialog(
|
||||||
|
@@ -35,7 +35,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Version = ReleaseInformation.GetVersion();
|
Version = ReleaseInformation.Version;
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
||||||
{
|
{
|
||||||
@@ -125,8 +125,8 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
public static void ReloadConfig()
|
public static void ReloadConfig()
|
||||||
{
|
{
|
||||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||||
|
|
||||||
// Now load the configuration as the other subsystems are now registered
|
// Now load the configuration as the other subsystems are now registered
|
||||||
if (File.Exists(localConfigurationPath))
|
if (File.Exists(localConfigurationPath))
|
||||||
|
@@ -43,14 +43,13 @@
|
|||||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||||
<PackageReference Include="Avalonia.Svg" />
|
<PackageReference Include="Avalonia.Svg" />
|
||||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
|
|
||||||
<PackageReference Include="DynamicData" />
|
<PackageReference Include="DynamicData" />
|
||||||
<PackageReference Include="FluentAvaloniaUI" />
|
<PackageReference Include="FluentAvaloniaUI" />
|
||||||
|
|
||||||
<PackageReference Include="OpenTK.Core" />
|
<PackageReference Include="OpenTK.Core" />
|
||||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan" />
|
<PackageReference Include="Silk.NET.Vulkan" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||||
@@ -79,7 +78,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
<TargetPath>alsoft.ini</TargetPath>
|
<TargetPath>alsoft.ini</TargetPath>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -93,7 +92,7 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
|
||||||
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@@ -9,6 +9,7 @@ using Ryujinx.Ava.UI.Windows;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.HOS.Applets;
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(path))
|
if (!string.IsNullOrWhiteSpace(path))
|
||||||
{
|
{
|
||||||
SvgSource source = new();
|
SvgSource source = new(default(Uri));
|
||||||
|
|
||||||
source.Load(EmbeddedResources.GetStream(path));
|
source.Load(EmbeddedResources.GetStream(path));
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
Click="CreateApplicationShortcut_Click"
|
Click="CreateApplicationShortcut_Click"
|
||||||
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||||
IsEnabled="{Binding CreateShortcutEnabled}"
|
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Click="OpenUserSaveDirectory_Click"
|
Click="OpenUserSaveDirectory_Click"
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
@@ -33,11 +32,10 @@
|
|||||||
SelectionChanged="GameList_SelectionChanged">
|
SelectionChanged="GameList_SelectionChanged">
|
||||||
<ListBox.ItemsPanel>
|
<ListBox.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<flex:FlexPanel
|
<WrapPanel
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Top"
|
||||||
AlignContent="FlexStart"
|
Orientation="Horizontal" />
|
||||||
JustifyContent="FlexStart" />
|
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.Styles>
|
<ListBox.Styles>
|
||||||
|
@@ -336,6 +336,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (_contentDialogOverlayWindow is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +393,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
{
|
{
|
||||||
_contentDialogOverlayWindow.Content = null;
|
_contentDialogOverlayWindow.Content = null;
|
||||||
_contentDialogOverlayWindow.Close();
|
_contentDialogOverlayWindow.Close();
|
||||||
|
_contentDialogOverlayWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@@ -17,14 +17,16 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool InSd { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public ModModel(string path, string name, bool enabled)
|
public ModModel(string path, string name, bool enabled, bool inSd)
|
||||||
{
|
{
|
||||||
Path = path;
|
Path = path;
|
||||||
Name = name;
|
Name = name;
|
||||||
Enabled = enabled;
|
Enabled = enabled;
|
||||||
|
InSd = inSd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,8 @@ namespace Ryujinx.Ava.UI.Renderer
|
|||||||
_context.MakeCurrent(_window);
|
_context.MakeCurrent(_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasContext() => _context.IsCurrent;
|
||||||
|
|
||||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||||
{
|
{
|
||||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||||
|
@@ -180,7 +180,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
||||||
{
|
{
|
||||||
SvgSource source = new();
|
SvgSource source = new(default(Uri));
|
||||||
|
|
||||||
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
||||||
|
|
||||||
|
@@ -357,7 +357,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
|
||||||
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
|
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
|
||||||
|
|
||||||
public string LoadHeading
|
public string LoadHeading
|
||||||
{
|
{
|
||||||
@@ -1350,7 +1350,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void OpenLogsFolder()
|
public void OpenLogsFolder()
|
||||||
{
|
{
|
||||||
string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
|
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||||
|
|
||||||
|
if (LoggerModule.LogDirectoryPath != null)
|
||||||
|
{
|
||||||
|
logPath = LoggerModule.LogDirectoryPath;
|
||||||
|
}
|
||||||
|
|
||||||
new DirectoryInfo(logPath).Create();
|
new DirectoryInfo(logPath).Create();
|
||||||
|
|
||||||
|
@@ -102,13 +102,14 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
foreach (var path in modsBasePaths)
|
foreach (var path in modsBasePaths)
|
||||||
{
|
{
|
||||||
|
var inSd = path == ModLoader.GetSdModsBasePath();
|
||||||
var modCache = new ModLoader.ModCache();
|
var modCache = new ModLoader.ModCache();
|
||||||
|
|
||||||
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId);
|
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId);
|
||||||
|
|
||||||
foreach (var mod in modCache.RomfsDirs)
|
foreach (var mod in modCache.RomfsDirs)
|
||||||
{
|
{
|
||||||
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
|
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
|
||||||
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
||||||
{
|
{
|
||||||
Mods.Add(modModel);
|
Mods.Add(modModel);
|
||||||
@@ -117,12 +118,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
foreach (var mod in modCache.RomfsContainers)
|
foreach (var mod in modCache.RomfsContainers)
|
||||||
{
|
{
|
||||||
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
|
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var mod in modCache.ExefsDirs)
|
foreach (var mod in modCache.ExefsDirs)
|
||||||
{
|
{
|
||||||
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled);
|
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
|
||||||
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
||||||
{
|
{
|
||||||
Mods.Add(modModel);
|
Mods.Add(modModel);
|
||||||
@@ -131,7 +132,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
foreach (var mod in modCache.ExefsContainers)
|
foreach (var mod in modCache.ExefsContainers)
|
||||||
{
|
{
|
||||||
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled));
|
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,30 +184,43 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public void Delete(ModModel model)
|
public void Delete(ModModel model)
|
||||||
{
|
{
|
||||||
var modsDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16"));
|
var isSubdir = true;
|
||||||
var parentDir = String.Empty;
|
var pathToDelete = model.Path;
|
||||||
|
var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath();
|
||||||
|
var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16"));
|
||||||
|
|
||||||
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
|
if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir)
|
||||||
{
|
{
|
||||||
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
|
isSubdir = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSubdir)
|
||||||
|
{
|
||||||
|
var parentDir = String.Empty;
|
||||||
|
|
||||||
|
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
|
||||||
{
|
{
|
||||||
parentDir = dir;
|
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
|
||||||
|
{
|
||||||
|
parentDir = dir;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentDir == String.Empty)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||||
|
LocaleKeys.DialogModDeleteNoParentMessage,
|
||||||
|
model.Path));
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentDir == String.Empty)
|
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\"");
|
||||||
{
|
Directory.Delete(pathToDelete, true);
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
|
|
||||||
LocaleKeys.DialogModDeleteNoParentMessage,
|
|
||||||
parentDir));
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{model.Path}\"");
|
|
||||||
Directory.Delete(parentDir, true);
|
|
||||||
|
|
||||||
Mods.Remove(model);
|
Mods.Remove(model);
|
||||||
OnPropertyChanged(nameof(ModCount));
|
OnPropertyChanged(nameof(ModCount));
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
@@ -40,11 +39,10 @@
|
|||||||
ItemsSource="{Binding Profiles}">
|
ItemsSource="{Binding Profiles}">
|
||||||
<ListBox.ItemsPanel>
|
<ListBox.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<flex:FlexPanel
|
<WrapPanel
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Center"
|
||||||
AlignContent="FlexStart"
|
Orientation="Horizontal"/>
|
||||||
JustifyContent="FlexStart" />
|
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.Styles>
|
<ListBox.Styles>
|
||||||
@@ -161,4 +159,4 @@
|
|||||||
Content="{locale:Locale UserProfilesClose}" />
|
Content="{locale:Locale UserProfilesClose}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
@@ -49,13 +48,11 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Height="80"
|
Height="80"
|
||||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||||
<flex:FlexPanel
|
<WrapPanel
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Center"
|
||||||
Direction="Column"
|
Orientation="Vertical">
|
||||||
JustifyContent="SpaceAround"
|
|
||||||
RowSpacing="2">
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -71,7 +68,7 @@
|
|||||||
Text="(REE-YOU-JINX)"
|
Text="(REE-YOU-JINX)"
|
||||||
TextAlignment="Center"
|
TextAlignment="Center"
|
||||||
Width="110" />
|
Width="110" />
|
||||||
</flex:FlexPanel>
|
</WrapPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
|
@@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckLaunchState()
|
private async Task CheckLaunchState()
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
||||||
{
|
{
|
||||||
@@ -271,23 +271,11 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
if (LinuxHelper.PkExecPath is not null)
|
if (LinuxHelper.PkExecPath is not null)
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
|
||||||
{
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
await ShowVmMaxMapCountDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(async () =>
|
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
|
||||||
{
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
await ShowVmMaxMapCountWarning();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,12 +292,12 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
ShowKeyErrorOnLoad = false;
|
ShowKeyErrorOnLoad = false;
|
||||||
|
|
||||||
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||||
{
|
{
|
||||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
await Updater.BeginParse(this, false).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
|
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
|
||||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
@@ -404,7 +392,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
LoadApplications();
|
LoadApplications();
|
||||||
|
|
||||||
|
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
CheckLaunchState();
|
CheckLaunchState();
|
||||||
|
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetMainContent(Control content = null)
|
private void SetMainContent(Control content = null)
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
@@ -6,8 +7,8 @@ namespace Ryujinx.Common.Configuration
|
|||||||
{
|
{
|
||||||
public static class AppDataManager
|
public static class AppDataManager
|
||||||
{
|
{
|
||||||
public const string DefaultBaseDir = "Ryujinx";
|
private const string DefaultBaseDir = "Ryujinx";
|
||||||
public const string DefaultPortableDir = "portable";
|
private const string DefaultPortableDir = "portable";
|
||||||
|
|
||||||
// The following 3 are always part of Base Directory
|
// The following 3 are always part of Base Directory
|
||||||
private const string GamesDir = "games";
|
private const string GamesDir = "games";
|
||||||
@@ -109,8 +110,7 @@ namespace Ryujinx.Common.Configuration
|
|||||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||||
{
|
{
|
||||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
|
||||||
Directory.Delete(oldConfigPath, true);
|
|
||||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,41 +127,13 @@ namespace Ryujinx.Common.Configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
||||||
// Should be removed, when the existance of the old directory isn't checked anymore.
|
// Should be removed, when the existence of the old directory isn't checked anymore.
|
||||||
private static bool IsPathSymlink(string path)
|
private static bool IsPathSymlink(string path)
|
||||||
{
|
{
|
||||||
FileAttributes attributes = File.GetAttributes(path);
|
FileAttributes attributes = File.GetAttributes(path);
|
||||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
|
||||||
{
|
|
||||||
var dir = new DirectoryInfo(sourceDir);
|
|
||||||
|
|
||||||
if (!dir.Exists)
|
|
||||||
{
|
|
||||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
|
||||||
Directory.CreateDirectory(destinationDir);
|
|
||||||
|
|
||||||
foreach (FileInfo file in dir.GetFiles())
|
|
||||||
{
|
|
||||||
if (file.Name == ".DS_Store")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (DirectoryInfo subDir in subDirs)
|
|
||||||
{
|
|
||||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||||
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ using Ryujinx.Common.SystemInterop;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ namespace Ryujinx.Common.Logging
|
|||||||
|
|
||||||
public readonly struct Log
|
public readonly struct Log
|
||||||
{
|
{
|
||||||
|
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
|
||||||
|
|
||||||
internal readonly LogLevel Level;
|
internal readonly LogLevel Level;
|
||||||
|
|
||||||
internal Log(LogLevel level)
|
internal Log(LogLevel level)
|
||||||
@@ -100,7 +104,12 @@ namespace Ryujinx.Common.Logging
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
|
private static string FormatMessage(LogClass logClass, string caller, string message)
|
||||||
|
{
|
||||||
|
message = message.Replace(_homeDir, _homeDirRedacted);
|
||||||
|
|
||||||
|
return $"{logClass} {caller}: {message}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Log? Debug { get; private set; }
|
public static Log? Debug { get; private set; }
|
||||||
|
@@ -13,31 +13,71 @@ namespace Ryujinx.Common.Logging.Targets
|
|||||||
|
|
||||||
string ILogTarget.Name { get => _name; }
|
string ILogTarget.Name { get => _name; }
|
||||||
|
|
||||||
public FileLogTarget(string path, string name)
|
public FileLogTarget(string name, FileStream fileStream)
|
||||||
: this(path, name, FileShare.Read, FileMode.Append)
|
{
|
||||||
{ }
|
_name = name;
|
||||||
|
_logWriter = new StreamWriter(fileStream);
|
||||||
|
_formatter = new DefaultLogFormatter();
|
||||||
|
}
|
||||||
|
|
||||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
public static FileStream PrepareLogFile(string path)
|
||||||
{
|
{
|
||||||
// Ensure directory is present
|
// Ensure directory is present
|
||||||
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
|
DirectoryInfo logDir = new(path);
|
||||||
logDir.Create();
|
try
|
||||||
|
{
|
||||||
|
logDir.Create();
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up old logs, should only keep 3
|
// Clean up old logs, should only keep 3
|
||||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||||
for (int i = 0; i < files.Length - 2; i++)
|
for (int i = 0; i < files.Length - 2; i++)
|
||||||
{
|
{
|
||||||
files[i].Delete();
|
try
|
||||||
|
{
|
||||||
|
files[i].Delete();
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string version = ReleaseInformation.GetVersion();
|
string version = ReleaseInformation.Version;
|
||||||
|
|
||||||
// Get path for the current time
|
// Get path for the current time
|
||||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||||
|
|
||||||
_name = name;
|
try
|
||||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
{
|
||||||
_formatter = new DefaultLogFormatter();
|
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (IOException exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Log(object sender, LogEventArgs args)
|
public void Log(object sender, LogEventArgs args)
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
@@ -9,50 +7,25 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
private const string FlatHubChannelOwner = "flathub";
|
private const string FlatHubChannelOwner = "flathub";
|
||||||
|
|
||||||
public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||||
public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||||
|
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||||
|
|
||||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||||
|
|
||||||
public static bool IsValid()
|
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||||
{
|
|
||||||
return !BuildGitHash.StartsWith("%%") &&
|
|
||||||
!ReleaseChannelName.StartsWith("%%") &&
|
|
||||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
|
||||||
!ReleaseChannelRepo.StartsWith("%%");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsFlatHubBuild()
|
public static bool IsValid =>
|
||||||
{
|
!BuildGitHash.StartsWith("%%") &&
|
||||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
!ReleaseChannelName.StartsWith("%%") &&
|
||||||
}
|
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||||
|
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||||
|
!ConfigFileName.StartsWith("%%");
|
||||||
|
|
||||||
public static string GetVersion()
|
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||||
{
|
|
||||||
if (IsValid())
|
|
||||||
{
|
|
||||||
return BuildVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||||
}
|
|
||||||
|
|
||||||
#if FORCE_EXTERNAL_BASE_DIR
|
|
||||||
public static string GetBaseApplicationDirectory()
|
|
||||||
{
|
|
||||||
return AppDataManager.BaseDirPath;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
public static string GetBaseApplicationDirectory()
|
|
||||||
{
|
|
||||||
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
return AppDataManager.BaseDirPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return AppDomain.CurrentDomain.BaseDirectory;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Utilities
|
||||||
|
{
|
||||||
|
public static class FileSystemUtils
|
||||||
|
{
|
||||||
|
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||||
|
{
|
||||||
|
// Get information about the source directory
|
||||||
|
var dir = new DirectoryInfo(sourceDir);
|
||||||
|
|
||||||
|
// Check if the source directory exists
|
||||||
|
if (!dir.Exists)
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache directories before we start copying
|
||||||
|
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||||
|
|
||||||
|
// Create the destination directory
|
||||||
|
Directory.CreateDirectory(destinationDir);
|
||||||
|
|
||||||
|
// Get the files in the source directory and copy to the destination directory
|
||||||
|
foreach (FileInfo file in dir.GetFiles())
|
||||||
|
{
|
||||||
|
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||||
|
file.CopyTo(targetFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If recursive and copying subdirectories, recursively call this method
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
foreach (DirectoryInfo subDir in dirs)
|
||||||
|
{
|
||||||
|
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||||
|
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void MoveDirectory(string sourceDir, string destinationDir)
|
||||||
|
{
|
||||||
|
CopyDirectory(sourceDir, destinationDir, true);
|
||||||
|
Directory.Delete(sourceDir, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
public readonly List<InstInfo> Instructions;
|
public readonly List<InstInfo> Instructions;
|
||||||
public readonly bool EndsWithBranch;
|
public readonly bool EndsWithBranch;
|
||||||
public readonly bool HasHostCall;
|
public readonly bool HasHostCall;
|
||||||
|
public readonly bool HasHostCallSkipContext;
|
||||||
public readonly bool IsTruncated;
|
public readonly bool IsTruncated;
|
||||||
public readonly bool IsLoopEnd;
|
public readonly bool IsLoopEnd;
|
||||||
public readonly bool IsThumb;
|
public readonly bool IsThumb;
|
||||||
@@ -20,6 +21,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
List<InstInfo> instructions,
|
List<InstInfo> instructions,
|
||||||
bool endsWithBranch,
|
bool endsWithBranch,
|
||||||
bool hasHostCall,
|
bool hasHostCall,
|
||||||
|
bool hasHostCallSkipContext,
|
||||||
bool isTruncated,
|
bool isTruncated,
|
||||||
bool isLoopEnd,
|
bool isLoopEnd,
|
||||||
bool isThumb)
|
bool isThumb)
|
||||||
@@ -31,6 +33,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
Instructions = instructions;
|
Instructions = instructions;
|
||||||
EndsWithBranch = endsWithBranch;
|
EndsWithBranch = endsWithBranch;
|
||||||
HasHostCall = hasHostCall;
|
HasHostCall = hasHostCall;
|
||||||
|
HasHostCallSkipContext = hasHostCallSkipContext;
|
||||||
IsTruncated = isTruncated;
|
IsTruncated = isTruncated;
|
||||||
IsLoopEnd = isLoopEnd;
|
IsLoopEnd = isLoopEnd;
|
||||||
IsThumb = isThumb;
|
IsThumb = isThumb;
|
||||||
@@ -57,6 +60,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
Instructions.GetRange(0, splitIndex),
|
Instructions.GetRange(0, splitIndex),
|
||||||
false,
|
false,
|
||||||
HasHostCall,
|
HasHostCall,
|
||||||
|
HasHostCallSkipContext,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
IsThumb);
|
IsThumb);
|
||||||
@@ -67,6 +71,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
Instructions.GetRange(splitIndex, splitCount),
|
Instructions.GetRange(splitIndex, splitCount),
|
||||||
EndsWithBranch,
|
EndsWithBranch,
|
||||||
HasHostCall,
|
HasHostCall,
|
||||||
|
HasHostCallSkipContext,
|
||||||
IsTruncated,
|
IsTruncated,
|
||||||
IsLoopEnd,
|
IsLoopEnd,
|
||||||
IsThumb);
|
IsThumb);
|
||||||
|
@@ -208,6 +208,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
InstMeta meta;
|
InstMeta meta;
|
||||||
InstFlags extraFlags = InstFlags.None;
|
InstFlags extraFlags = InstFlags.None;
|
||||||
bool hasHostCall = false;
|
bool hasHostCall = false;
|
||||||
|
bool hasHostCallSkipContext = false;
|
||||||
bool isTruncated = false;
|
bool isTruncated = false;
|
||||||
|
|
||||||
do
|
do
|
||||||
@@ -246,9 +247,17 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
meta = InstTableA32<T>.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features);
|
meta = InstTableA32<T>.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meta.Name.IsSystemOrCall() && !hasHostCall)
|
if (meta.Name.IsSystemOrCall())
|
||||||
{
|
{
|
||||||
hasHostCall = meta.Name.IsCall() || InstEmitSystem.NeedsCall(meta.Name);
|
if (!hasHostCall)
|
||||||
|
{
|
||||||
|
hasHostCall = InstEmitSystem.NeedsCall(meta.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasHostCallSkipContext)
|
||||||
|
{
|
||||||
|
hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags));
|
insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags));
|
||||||
@@ -259,8 +268,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
|
|
||||||
if (!isTruncated && IsBackwardsBranch(meta.Name, encoding))
|
if (!isTruncated && IsBackwardsBranch(meta.Name, encoding))
|
||||||
{
|
{
|
||||||
hasHostCall = true;
|
|
||||||
isLoopEnd = true;
|
isLoopEnd = true;
|
||||||
|
hasHostCallSkipContext = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new(
|
return new(
|
||||||
@@ -269,6 +278,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
insts,
|
insts,
|
||||||
!isTruncated,
|
!isTruncated,
|
||||||
hasHostCall,
|
hasHostCall,
|
||||||
|
hasHostCallSkipContext,
|
||||||
isTruncated,
|
isTruncated,
|
||||||
isLoopEnd,
|
isLoopEnd,
|
||||||
isThumb);
|
isThumb);
|
||||||
|
@@ -6,6 +6,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
{
|
{
|
||||||
public readonly List<Block> Blocks;
|
public readonly List<Block> Blocks;
|
||||||
public readonly bool HasHostCall;
|
public readonly bool HasHostCall;
|
||||||
|
public readonly bool HasHostCallSkipContext;
|
||||||
public readonly bool IsTruncated;
|
public readonly bool IsTruncated;
|
||||||
|
|
||||||
public MultiBlock(List<Block> blocks)
|
public MultiBlock(List<Block> blocks)
|
||||||
@@ -15,12 +16,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
Block block = blocks[0];
|
Block block = blocks[0];
|
||||||
|
|
||||||
HasHostCall = block.HasHostCall;
|
HasHostCall = block.HasHostCall;
|
||||||
|
HasHostCallSkipContext = block.HasHostCallSkipContext;
|
||||||
|
|
||||||
for (int index = 1; index < blocks.Count; index++)
|
for (int index = 1; index < blocks.Count; index++)
|
||||||
{
|
{
|
||||||
block = blocks[index];
|
block = blocks[index];
|
||||||
|
|
||||||
HasHostCall |= block.HasHostCall;
|
HasHostCall |= block.HasHostCall;
|
||||||
|
HasHostCallSkipContext |= block.HasHostCallSkipContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
block = blocks[^1];
|
block = blocks[^1];
|
||||||
|
@@ -106,6 +106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32
|
|||||||
if ((regMask & AbiConstants.ReservedRegsMask) == 0)
|
if ((regMask & AbiConstants.ReservedRegsMask) == 0)
|
||||||
{
|
{
|
||||||
_gprMask |= regMask;
|
_gprMask |= regMask;
|
||||||
|
UsedGprsMask |= regMask;
|
||||||
|
|
||||||
return firstCalleeSaved;
|
return firstCalleeSaved;
|
||||||
}
|
}
|
||||||
|
@@ -305,12 +305,23 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
|
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int reservedStackSize = 0;
|
||||||
|
|
||||||
|
if (multiBlock.HasHostCall)
|
||||||
|
{
|
||||||
|
reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask);
|
||||||
|
}
|
||||||
|
else if (multiBlock.HasHostCallSkipContext)
|
||||||
|
{
|
||||||
|
reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers.
|
||||||
|
}
|
||||||
|
|
||||||
RegisterSaveRestore rsr = new(
|
RegisterSaveRestore rsr = new(
|
||||||
regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
|
regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
|
||||||
regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
|
regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
|
||||||
OperandType.FP64,
|
OperandType.FP64,
|
||||||
multiBlock.HasHostCall,
|
multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext,
|
||||||
multiBlock.HasHostCall ? CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask) : 0);
|
reservedStackSize);
|
||||||
|
|
||||||
TailMerger tailMerger = new();
|
TailMerger tailMerger = new();
|
||||||
|
|
||||||
@@ -596,7 +607,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
name == InstName.Ldm ||
|
name == InstName.Ldm ||
|
||||||
name == InstName.Ldmda ||
|
name == InstName.Ldmda ||
|
||||||
name == InstName.Ldmdb ||
|
name == InstName.Ldmdb ||
|
||||||
name == InstName.Ldmib)
|
name == InstName.Ldmib ||
|
||||||
|
name == InstName.Pop)
|
||||||
{
|
{
|
||||||
// Arm32 does not have a return instruction, instead returns are implemented
|
// Arm32 does not have a return instruction, instead returns are implemented
|
||||||
// either using BX LR (for leaf functions), or POP { ... PC }.
|
// either using BX LR (for leaf functions), or POP { ... PC }.
|
||||||
@@ -711,7 +723,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case BranchType.SyncPoint:
|
case BranchType.SyncPoint:
|
||||||
InstEmitSystem.WriteSyncPoint(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset());
|
InstEmitSystem.WriteSyncPoint(
|
||||||
|
context.Writer,
|
||||||
|
ref asm,
|
||||||
|
context.RegisterAllocator,
|
||||||
|
context.TailMerger,
|
||||||
|
context.GetReservedStackOffset(),
|
||||||
|
context.StoreToContext,
|
||||||
|
context.LoadFromContext);
|
||||||
break;
|
break;
|
||||||
case BranchType.SoftwareInterrupt:
|
case BranchType.SoftwareInterrupt:
|
||||||
context.StoreToContext();
|
context.StoreToContext();
|
||||||
|
@@ -199,12 +199,12 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
public static void WriteSpillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||||
{
|
{
|
||||||
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true);
|
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
public static void WriteFillSkipContext(ref Assembler asm, RegisterAllocator regAlloc, int spillOffset)
|
||||||
{
|
{
|
||||||
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false);
|
WriteSpillOrFillSkipContext(ref asm, regAlloc, spillOffset, spill: false);
|
||||||
}
|
}
|
||||||
|
@@ -354,11 +354,18 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
// All instructions that might do a host call should be included here.
|
// All instructions that might do a host call should be included here.
|
||||||
// That is required to reserve space on the stack for caller saved registers.
|
// That is required to reserve space on the stack for caller saved registers.
|
||||||
|
|
||||||
|
return name == InstName.Mrrc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool NeedsCallSkipContext(InstName name)
|
||||||
|
{
|
||||||
|
// All instructions that might do a host call should be included here.
|
||||||
|
// That is required to reserve space on the stack for caller saved registers.
|
||||||
|
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
case InstName.Mcr:
|
case InstName.Mcr:
|
||||||
case InstName.Mrc:
|
case InstName.Mrc:
|
||||||
case InstName.Mrrc:
|
|
||||||
case InstName.Svc:
|
case InstName.Svc:
|
||||||
case InstName.Udf:
|
case InstName.Udf:
|
||||||
return true;
|
return true;
|
||||||
@@ -372,7 +379,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
Assembler asm = new(writer);
|
Assembler asm = new(writer);
|
||||||
|
|
||||||
WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
WriteCall(ref asm, regAlloc, GetBkptHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
||||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId)
|
public static void WriteSvc(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint svcId)
|
||||||
@@ -380,7 +387,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
Assembler asm = new(writer);
|
Assembler asm = new(writer);
|
||||||
|
|
||||||
WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId);
|
WriteCall(ref asm, regAlloc, GetSvcHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, svcId);
|
||||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm)
|
public static void WriteUdf(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset, uint pc, uint imm)
|
||||||
@@ -388,7 +395,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
Assembler asm = new(writer);
|
Assembler asm = new(writer);
|
||||||
|
|
||||||
WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
WriteCall(ref asm, regAlloc, GetUdfHandlerPtr(), skipContext: true, spillBaseOffset, null, pc, imm);
|
||||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: true, spillBaseOffset);
|
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, spillBaseOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2)
|
public static void WriteReadCntpct(CodeWriter writer, RegisterAllocator regAlloc, int spillBaseOffset, int rt, int rt2)
|
||||||
@@ -422,14 +429,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister);
|
WriteFill(ref asm, regAlloc, resultMask, skipContext: false, spillBaseOffset, tempRegister);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteSyncPoint(CodeWriter writer, RegisterAllocator regAlloc, TailMerger tailMerger, int spillBaseOffset)
|
public static void WriteSyncPoint(
|
||||||
{
|
CodeWriter writer,
|
||||||
Assembler asm = new(writer);
|
ref Assembler asm,
|
||||||
|
RegisterAllocator regAlloc,
|
||||||
WriteSyncPoint(writer, ref asm, regAlloc, tailMerger, skipContext: false, spillBaseOffset);
|
TailMerger tailMerger,
|
||||||
}
|
int spillBaseOffset,
|
||||||
|
Action storeToContext = null,
|
||||||
private static void WriteSyncPoint(CodeWriter writer, ref Assembler asm, RegisterAllocator regAlloc, TailMerger tailMerger, bool skipContext, int spillBaseOffset)
|
Action loadFromContext = null)
|
||||||
{
|
{
|
||||||
int tempRegister = regAlloc.AllocateTempGprRegister();
|
int tempRegister = regAlloc.AllocateTempGprRegister();
|
||||||
|
|
||||||
@@ -440,7 +447,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
int branchIndex = writer.InstructionPointer;
|
int branchIndex = writer.InstructionPointer;
|
||||||
asm.Cbnz(rt, 0);
|
asm.Cbnz(rt, 0);
|
||||||
|
|
||||||
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
|
storeToContext?.Invoke();
|
||||||
|
WriteSpill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
|
||||||
|
|
||||||
Operand rn = Register(tempRegister == 0 ? 1 : 0);
|
Operand rn = Register(tempRegister == 0 ? 1 : 0);
|
||||||
|
|
||||||
@@ -449,7 +457,8 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
|
|
||||||
tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32));
|
tailMerger.AddConditionalZeroReturn(writer, asm, Register(0, OperandType.I32));
|
||||||
|
|
||||||
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext, spillBaseOffset, tempRegister);
|
WriteFill(ref asm, regAlloc, 1u << tempRegister, skipContext: true, spillBaseOffset, tempRegister);
|
||||||
|
loadFromContext?.Invoke();
|
||||||
|
|
||||||
asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset);
|
asm.LdrRiUn(rt, Register(regAlloc.FixedContextRegister), NativeContextOffsets.CounterOffset);
|
||||||
|
|
||||||
@@ -514,18 +523,31 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
|
|
||||||
private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
private static void WriteSpill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
||||||
{
|
{
|
||||||
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: true);
|
if (skipContext)
|
||||||
|
{
|
||||||
|
InstEmitFlow.WriteSpillSkipContext(ref asm, regAlloc, spillOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
private static void WriteFill(ref Assembler asm, RegisterAllocator regAlloc, uint exceptMask, bool skipContext, int spillOffset, int tempRegister)
|
||||||
{
|
{
|
||||||
WriteSpillOrFill(ref asm, regAlloc, skipContext, exceptMask, spillOffset, tempRegister, spill: false);
|
if (skipContext)
|
||||||
|
{
|
||||||
|
InstEmitFlow.WriteFillSkipContext(ref asm, regAlloc, spillOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WriteSpillOrFill(ref asm, regAlloc, exceptMask, spillOffset, tempRegister, spill: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteSpillOrFill(
|
private static void WriteSpillOrFill(
|
||||||
ref Assembler asm,
|
ref Assembler asm,
|
||||||
RegisterAllocator regAlloc,
|
RegisterAllocator regAlloc,
|
||||||
bool skipContext,
|
|
||||||
uint exceptMask,
|
uint exceptMask,
|
||||||
int spillOffset,
|
int spillOffset,
|
||||||
int tempRegister,
|
int tempRegister,
|
||||||
@@ -533,11 +555,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
{
|
{
|
||||||
uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask);
|
uint gprMask = regAlloc.UsedGprsMask & ~(AbiConstants.GprCalleeSavedRegsMask | exceptMask);
|
||||||
|
|
||||||
if (skipContext)
|
|
||||||
{
|
|
||||||
gprMask &= ~Compiler.UsableGprsMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!spill)
|
if (!spill)
|
||||||
{
|
{
|
||||||
// We must reload the status register before reloading the GPRs,
|
// We must reload the status register before reloading the GPRs,
|
||||||
@@ -600,11 +617,6 @@ namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
|||||||
|
|
||||||
uint fpSimdMask = regAlloc.UsedFpSimdMask;
|
uint fpSimdMask = regAlloc.UsedFpSimdMask;
|
||||||
|
|
||||||
if (skipContext)
|
|
||||||
{
|
|
||||||
fpSimdMask &= ~Compiler.UsableFpSimdMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (fpSimdMask != 0)
|
while (fpSimdMask != 0)
|
||||||
{
|
{
|
||||||
int reg = BitOperations.TrailingZeroCount(fpSimdMask);
|
int reg = BitOperations.TrailingZeroCount(fpSimdMask);
|
||||||
|
@@ -147,6 +147,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
A1B5G5R5Unorm,
|
A1B5G5R5Unorm,
|
||||||
B8G8R8A8Unorm,
|
B8G8R8A8Unorm,
|
||||||
B8G8R8A8Srgb,
|
B8G8R8A8Srgb,
|
||||||
|
B10G10R10A2Unorm,
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class FormatExtensions
|
public static class FormatExtensions
|
||||||
@@ -260,6 +261,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
case Format.R10G10B10A2Sint:
|
case Format.R10G10B10A2Sint:
|
||||||
case Format.R10G10B10A2Uscaled:
|
case Format.R10G10B10A2Uscaled:
|
||||||
case Format.R10G10B10A2Sscaled:
|
case Format.R10G10B10A2Sscaled:
|
||||||
|
case Format.B10G10R10A2Unorm:
|
||||||
return 4;
|
return 4;
|
||||||
|
|
||||||
case Format.S8Uint:
|
case Format.S8Uint:
|
||||||
@@ -451,6 +453,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
case Format.R32G32Uint:
|
case Format.R32G32Uint:
|
||||||
case Format.B8G8R8A8Unorm:
|
case Format.B8G8R8A8Unorm:
|
||||||
case Format.B8G8R8A8Srgb:
|
case Format.B8G8R8A8Srgb:
|
||||||
|
case Format.B10G10R10A2Unorm:
|
||||||
case Format.R10G10B10A2Unorm:
|
case Format.R10G10B10A2Unorm:
|
||||||
case Format.R10G10B10A2Uint:
|
case Format.R10G10B10A2Uint:
|
||||||
case Format.R8G8B8A8Unorm:
|
case Format.R8G8B8A8Unorm:
|
||||||
@@ -611,6 +614,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
case Format.B5G5R5A1Unorm:
|
case Format.B5G5R5A1Unorm:
|
||||||
case Format.B8G8R8A8Unorm:
|
case Format.B8G8R8A8Unorm:
|
||||||
case Format.B8G8R8A8Srgb:
|
case Format.B8G8R8A8Srgb:
|
||||||
|
case Format.B10G10R10A2Unorm:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -277,6 +277,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
|
|
||||||
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
|
ReadOnlySpan<byte> srcSpan = memoryManager.GetSpan(srcGpuVa + (ulong)srcBaseOffset, srcSize, true);
|
||||||
|
|
||||||
|
// If remapping is disabled, we always copy the components directly, in order.
|
||||||
|
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
|
||||||
|
bool isIdentityRemap = !remap ||
|
||||||
|
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
|
||||||
|
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
|
||||||
|
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
|
||||||
|
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
|
||||||
|
|
||||||
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
|
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
|
||||||
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
|
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
|
||||||
|
|
||||||
@@ -284,7 +292,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
// but only if we are doing a complete copy,
|
// but only if we are doing a complete copy,
|
||||||
// and not for block linear to linear copies, since those are typically accessed from the CPU.
|
// and not for block linear to linear copies, since those are typically accessed from the CPU.
|
||||||
|
|
||||||
if (completeSource && completeDest && !(dstLinear && !srcLinear))
|
if (completeSource && completeDest && !(dstLinear && !srcLinear) && isIdentityRemap)
|
||||||
{
|
{
|
||||||
var target = memoryManager.Physical.TextureCache.FindTexture(
|
var target = memoryManager.Physical.TextureCache.FindTexture(
|
||||||
memoryManager,
|
memoryManager,
|
||||||
@@ -353,14 +361,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
|||||||
TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
|
TextureParams srcParams = new(srcRegionX, srcRegionY, srcBaseOffset, srcBpp, srcLinear, srcCalculator);
|
||||||
TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
|
TextureParams dstParams = new(dstRegionX, dstRegionY, dstBaseOffset, dstBpp, dstLinear, dstCalculator);
|
||||||
|
|
||||||
// If remapping is enabled, we always copy the components directly, in order.
|
|
||||||
// If it's enabled, but the mapping is just XYZW, we also copy them in order.
|
|
||||||
bool isIdentityRemap = !remap ||
|
|
||||||
(_state.State.SetRemapComponentsDstX == SetRemapComponentsDst.SrcX &&
|
|
||||||
(dstComponents < 2 || _state.State.SetRemapComponentsDstY == SetRemapComponentsDst.SrcY) &&
|
|
||||||
(dstComponents < 3 || _state.State.SetRemapComponentsDstZ == SetRemapComponentsDst.SrcZ) &&
|
|
||||||
(dstComponents < 4 || _state.State.SetRemapComponentsDstW == SetRemapComponentsDst.SrcW));
|
|
||||||
|
|
||||||
if (isIdentityRemap)
|
if (isIdentityRemap)
|
||||||
{
|
{
|
||||||
// The order of the components doesn't change, so we can just copy directly
|
// The order of the components doesn't change, so we can just copy directly
|
||||||
|
@@ -26,6 +26,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
public const int PrimitiveRestartStateIndex = 12;
|
public const int PrimitiveRestartStateIndex = 12;
|
||||||
public const int RenderTargetStateIndex = 27;
|
public const int RenderTargetStateIndex = 27;
|
||||||
|
|
||||||
|
// Vertex buffers larger than this size will be clamped to the mapped size.
|
||||||
|
private const ulong VertexBufferSizeToMappedSizeThreshold = 256 * 1024 * 1024; // 256 MB
|
||||||
|
|
||||||
private readonly GpuContext _context;
|
private readonly GpuContext _context;
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
private readonly DeviceStateWithShadow<ThreedClassState> _state;
|
||||||
@@ -1144,6 +1147,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
size = Math.Min(size, maxVertexBufferSize);
|
size = Math.Min(size, maxVertexBufferSize);
|
||||||
}
|
}
|
||||||
|
else if (size > VertexBufferSizeToMappedSizeThreshold)
|
||||||
|
{
|
||||||
|
// Make sure we have a sane vertex buffer size, since in some cases applications
|
||||||
|
// might set the "end address" of the vertex buffer to the end of the GPU address space,
|
||||||
|
// which would result in a several GBs large buffer.
|
||||||
|
|
||||||
|
size = _channel.MemoryManager.GetMappedSize(address, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
|||||||
R16G16Sint = 0xdc,
|
R16G16Sint = 0xdc,
|
||||||
R16G16Uint = 0xdd,
|
R16G16Uint = 0xdd,
|
||||||
R16G16Float = 0xde,
|
R16G16Float = 0xde,
|
||||||
|
B10G10R10A2Unorm = 0xdf,
|
||||||
R11G11B10Float = 0xe0,
|
R11G11B10Float = 0xe0,
|
||||||
R32Sint = 0xe3,
|
R32Sint = 0xe3,
|
||||||
R32Uint = 0xe4,
|
R32Uint = 0xe4,
|
||||||
@@ -104,6 +105,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Types
|
|||||||
ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2),
|
ColorFormat.R16G16Sint => new FormatInfo(Format.R16G16Sint, 1, 1, 4, 2),
|
||||||
ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2),
|
ColorFormat.R16G16Uint => new FormatInfo(Format.R16G16Uint, 1, 1, 4, 2),
|
||||||
ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2),
|
ColorFormat.R16G16Float => new FormatInfo(Format.R16G16Float, 1, 1, 4, 2),
|
||||||
|
ColorFormat.B10G10R10A2Unorm => new FormatInfo(Format.B10G10R10A2Unorm, 1, 1, 4, 4),
|
||||||
ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3),
|
ColorFormat.R11G11B10Float => new FormatInfo(Format.R11G11B10Float, 1, 1, 4, 3),
|
||||||
ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1),
|
ColorFormat.R32Sint => new FormatInfo(Format.R32Sint, 1, 1, 4, 1),
|
||||||
ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
ColorFormat.R32Uint => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
||||||
|
@@ -1630,12 +1630,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If size is zero, we have nothing to flush.
|
|
||||||
if (size == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||||
// In this case it will skip registering the action, but here we are already handling it,
|
// In this case it will skip registering the action, but here we are already handling it,
|
||||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5958;
|
private const uint CodeGenVersion = 6253;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@@ -30,6 +30,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
_thread.Start();
|
_thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasContext() => _backgroundContext.HasContext();
|
||||||
|
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
InBackground = true;
|
InBackground = true;
|
||||||
|
@@ -161,6 +161,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551));
|
Add(Format.A1B5G5R5Unorm, new FormatInfo(4, true, false, All.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551));
|
||||||
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
Add(Format.B8G8R8A8Unorm, new FormatInfo(4, true, false, All.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
Add(Format.B8G8R8A8Srgb, new FormatInfo(4, false, false, All.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte));
|
||||||
|
Add(Format.B10G10R10A2Unorm, new FormatInfo(4, false, false, All.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed));
|
||||||
|
|
||||||
Add(Format.R8Unorm, SizedInternalFormat.R8);
|
Add(Format.R8Unorm, SizedInternalFormat.R8);
|
||||||
Add(Format.R8Uint, SizedInternalFormat.R8ui);
|
Add(Format.R8Uint, SizedInternalFormat.R8ui);
|
||||||
|
@@ -7,21 +7,6 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
{
|
{
|
||||||
void MakeCurrent();
|
void MakeCurrent();
|
||||||
|
|
||||||
// TODO: Support more APIs per platform.
|
bool HasContext();
|
||||||
static bool HasContext()
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
return WGLHelper.GetCurrentContext() != IntPtr.Zero;
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
return GLXHelper.GetCurrentContext() != IntPtr.Zero;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -248,7 +248,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
{
|
{
|
||||||
// alwaysBackground is ignored, since we cannot switch from the current context.
|
// alwaysBackground is ignored, since we cannot switch from the current context.
|
||||||
|
|
||||||
if (IOpenGLContext.HasContext())
|
if (_window.BackgroundContext.HasContext())
|
||||||
{
|
{
|
||||||
action(); // We have a context already - use that (assuming it is the main one).
|
action(); // We have a context already - use that (assuming it is the main one).
|
||||||
}
|
}
|
||||||
|
@@ -578,12 +578,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
type = SamplerType.Texture2D;
|
type = SamplerType.Texture2D;
|
||||||
flags = TextureFlags.Gather;
|
flags = TextureFlags.Gather;
|
||||||
|
|
||||||
if (tld4sOp.Dc)
|
int depthCompareIndex = sourcesList.Count;
|
||||||
{
|
|
||||||
sourcesList.Add(Rb());
|
|
||||||
|
|
||||||
type |= SamplerType.Shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tld4sOp.Aoffi)
|
if (tld4sOp.Aoffi)
|
||||||
{
|
{
|
||||||
@@ -592,7 +587,16 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
flags |= TextureFlags.Offset;
|
flags |= TextureFlags.Offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcesList.Add(Const((int)tld4sOp.TexComp));
|
if (tld4sOp.Dc)
|
||||||
|
{
|
||||||
|
sourcesList.Insert(depthCompareIndex, Rb());
|
||||||
|
|
||||||
|
type |= SamplerType.Shadow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sourcesList.Add(Const((int)tld4sOp.TexComp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -80,9 +80,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex && TranslatorContext.Options.TargetApi == TargetApi.Vulkan)
|
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
||||||
|
// OpenGL requires the point size to be always written on the shader if PROGRAM_POINT_SIZE is set.
|
||||||
|
if (TranslatorContext.Definitions.Stage == ShaderStage.Vertex)
|
||||||
{
|
{
|
||||||
// Vulkan requires the point size to be always written on the shader if the primitive topology is points.
|
|
||||||
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
this.Store(StorageKind.Output, IoVariable.PointSize, null, ConstF(TranslatorContext.Definitions.PointSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Device\Ryujinx.Graphics.Device.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Host1x\Ryujinx.Graphics.Host1x.csproj" />
|
|
||||||
<ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
|
<ProjectReference Include="..\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -257,7 +257,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
|
|
||||||
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
|
scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
|
||||||
|
|
||||||
_pipeline.SetRenderTarget(texture.GetImageViewForAttachment(), (uint)texture.Width, (uint)texture.Height, false, texture.VkFormat);
|
_pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height);
|
||||||
_pipeline.SetRenderTargetColorMasks(colorMasks);
|
_pipeline.SetRenderTargetColorMasks(colorMasks);
|
||||||
_pipeline.SetScissors(scissors);
|
_pipeline.SetScissors(scissors);
|
||||||
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
_pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
@@ -7,10 +8,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
static class FormatTable
|
static class FormatTable
|
||||||
{
|
{
|
||||||
private static readonly VkFormat[] _table;
|
private static readonly VkFormat[] _table;
|
||||||
|
private static readonly Dictionary<VkFormat, Format> _reverseMap;
|
||||||
|
|
||||||
static FormatTable()
|
static FormatTable()
|
||||||
{
|
{
|
||||||
_table = new VkFormat[Enum.GetNames(typeof(Format)).Length];
|
_table = new VkFormat[Enum.GetNames(typeof(Format)).Length];
|
||||||
|
_reverseMap = new Dictionary<VkFormat, Format>();
|
||||||
|
|
||||||
#pragma warning disable IDE0055 // Disable formatting
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
Add(Format.R8Unorm, VkFormat.R8Unorm);
|
Add(Format.R8Unorm, VkFormat.R8Unorm);
|
||||||
@@ -158,12 +161,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16);
|
Add(Format.A1B5G5R5Unorm, VkFormat.R5G5B5A1UnormPack16);
|
||||||
Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm);
|
Add(Format.B8G8R8A8Unorm, VkFormat.B8G8R8A8Unorm);
|
||||||
Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb);
|
Add(Format.B8G8R8A8Srgb, VkFormat.B8G8R8A8Srgb);
|
||||||
|
Add(Format.B10G10R10A2Unorm, VkFormat.A2R10G10B10UnormPack32);
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Add(Format format, VkFormat vkFormat)
|
private static void Add(Format format, VkFormat vkFormat)
|
||||||
{
|
{
|
||||||
_table[(int)format] = vkFormat;
|
_table[(int)format] = vkFormat;
|
||||||
|
_reverseMap[vkFormat] = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VkFormat GetFormat(Format format)
|
public static VkFormat GetFormat(Format format)
|
||||||
@@ -171,6 +176,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _table[(int)format];
|
return _table[(int)format];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Format GetFormat(VkFormat format)
|
||||||
|
{
|
||||||
|
if (!_reverseMap.TryGetValue(format, out Format result))
|
||||||
|
{
|
||||||
|
return Format.B8G8R8A8Unorm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public static Format ConvertRgba8SrgbToUnorm(Format format)
|
public static Format ConvertRgba8SrgbToUnorm(Format format)
|
||||||
{
|
{
|
||||||
return format switch
|
return format switch
|
||||||
|
@@ -12,6 +12,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Auto<DisposableImageView>[] _attachments;
|
private readonly Auto<DisposableImageView>[] _attachments;
|
||||||
private readonly TextureView[] _colors;
|
private readonly TextureView[] _colors;
|
||||||
private readonly TextureView _depthStencil;
|
private readonly TextureView _depthStencil;
|
||||||
|
private readonly TextureView[] _colorsCanonical;
|
||||||
|
private readonly TextureView _baseAttachment;
|
||||||
private readonly uint _validColorAttachments;
|
private readonly uint _validColorAttachments;
|
||||||
|
|
||||||
public uint Width { get; }
|
public uint Width { get; }
|
||||||
@@ -28,25 +30,31 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public bool HasDepthStencil { get; }
|
public bool HasDepthStencil { get; }
|
||||||
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
|
public int ColorAttachmentsCount => AttachmentsCount - (HasDepthStencil ? 1 : 0);
|
||||||
|
|
||||||
public FramebufferParams(
|
public FramebufferParams(Device device, TextureView view, uint width, uint height)
|
||||||
Device device,
|
|
||||||
Auto<DisposableImageView> view,
|
|
||||||
uint width,
|
|
||||||
uint height,
|
|
||||||
uint samples,
|
|
||||||
bool isDepthStencil,
|
|
||||||
VkFormat format)
|
|
||||||
{
|
{
|
||||||
|
bool isDepthStencil = view.Info.Format.IsDepthOrStencil();
|
||||||
|
|
||||||
_device = device;
|
_device = device;
|
||||||
_attachments = new[] { view };
|
_attachments = new[] { view.GetImageViewForAttachment() };
|
||||||
_validColorAttachments = isDepthStencil ? 0u : 1u;
|
_validColorAttachments = isDepthStencil ? 0u : 1u;
|
||||||
|
_baseAttachment = view;
|
||||||
|
|
||||||
|
if (isDepthStencil)
|
||||||
|
{
|
||||||
|
_depthStencil = view;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_colors = new TextureView[] { view };
|
||||||
|
_colorsCanonical = _colors;
|
||||||
|
}
|
||||||
|
|
||||||
Width = width;
|
Width = width;
|
||||||
Height = height;
|
Height = height;
|
||||||
Layers = 1;
|
Layers = 1;
|
||||||
|
|
||||||
AttachmentSamples = new[] { samples };
|
AttachmentSamples = new[] { (uint)view.Info.Samples };
|
||||||
AttachmentFormats = new[] { format };
|
AttachmentFormats = new[] { view.VkFormat };
|
||||||
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
|
AttachmentIndices = isDepthStencil ? Array.Empty<int>() : new[] { 0 };
|
||||||
|
|
||||||
AttachmentsCount = 1;
|
AttachmentsCount = 1;
|
||||||
@@ -64,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_attachments = new Auto<DisposableImageView>[count];
|
_attachments = new Auto<DisposableImageView>[count];
|
||||||
_colors = new TextureView[colorsCount];
|
_colors = new TextureView[colorsCount];
|
||||||
|
_colorsCanonical = colors.Select(color => color is TextureView view && view.Valid ? view : null).ToArray();
|
||||||
|
|
||||||
AttachmentSamples = new uint[count];
|
AttachmentSamples = new uint[count];
|
||||||
AttachmentFormats = new VkFormat[count];
|
AttachmentFormats = new VkFormat[count];
|
||||||
@@ -86,6 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_attachments[index] = texture.GetImageViewForAttachment();
|
_attachments[index] = texture.GetImageViewForAttachment();
|
||||||
_colors[index] = texture;
|
_colors[index] = texture;
|
||||||
_validColorAttachments |= 1u << bindIndex;
|
_validColorAttachments |= 1u << bindIndex;
|
||||||
|
_baseAttachment = texture;
|
||||||
|
|
||||||
AttachmentSamples[index] = (uint)texture.Info.Samples;
|
AttachmentSamples[index] = (uint)texture.Info.Samples;
|
||||||
AttachmentFormats[index] = texture.VkFormat;
|
AttachmentFormats[index] = texture.VkFormat;
|
||||||
@@ -115,6 +125,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
||||||
_depthStencil = dsTexture;
|
_depthStencil = dsTexture;
|
||||||
|
_baseAttachment ??= dsTexture;
|
||||||
|
|
||||||
AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
|
AttachmentSamples[count - 1] = (uint)dsTexture.Info.Samples;
|
||||||
AttachmentFormats[count - 1] = dsTexture.VkFormat;
|
AttachmentFormats[count - 1] = dsTexture.VkFormat;
|
||||||
@@ -251,19 +262,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
|
||||||
{
|
{
|
||||||
if (_colors != null)
|
_colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
|
||||||
{
|
cbs,
|
||||||
int realIndex = Array.IndexOf(AttachmentIndices, index);
|
AccessFlags.ColorAttachmentWriteBit,
|
||||||
|
PipelineStageFlags.ColorAttachmentOutputBit,
|
||||||
if (realIndex != -1)
|
insideRenderPass: true);
|
||||||
{
|
|
||||||
_colors[realIndex].Storage?.InsertReadToWriteBarrier(
|
|
||||||
cbs,
|
|
||||||
AccessFlags.ColorAttachmentWriteBit,
|
|
||||||
PipelineStageFlags.ColorAttachmentOutputBit,
|
|
||||||
insideRenderPass: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
public void InsertClearBarrierDS(CommandBufferScoped cbs)
|
||||||
@@ -274,5 +277,61 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
PipelineStageFlags.LateFragmentTestsBit,
|
PipelineStageFlags.LateFragmentTestsBit,
|
||||||
insideRenderPass: true);
|
insideRenderPass: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextureView[] GetAttachmentViews()
|
||||||
|
{
|
||||||
|
var result = new TextureView[_attachments.Length];
|
||||||
|
|
||||||
|
_colors?.CopyTo(result, 0);
|
||||||
|
|
||||||
|
if (_depthStencil != null)
|
||||||
|
{
|
||||||
|
result[^1] = _depthStencil;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderPassCacheKey GetRenderPassCacheKey()
|
||||||
|
{
|
||||||
|
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertLoadOpBarriers(CommandBufferScoped cbs)
|
||||||
|
{
|
||||||
|
if (_colors != null)
|
||||||
|
{
|
||||||
|
foreach (var color in _colors)
|
||||||
|
{
|
||||||
|
// If Clear or DontCare were used, this would need to be write bit.
|
||||||
|
color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||||
|
color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_depthStencil != null)
|
||||||
|
{
|
||||||
|
_depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
|
||||||
|
_depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
|
VulkanRenderer gd,
|
||||||
|
Device device,
|
||||||
|
CommandBufferScoped cbs)
|
||||||
|
{
|
||||||
|
return _baseAttachment.GetPassAndFramebuffer(gd, device, cbs, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureView GetColorView(int index)
|
||||||
|
{
|
||||||
|
return _colorsCanonical[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextureView GetDepthStencilView()
|
||||||
|
{
|
||||||
|
return _depthStencil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
@@ -20,20 +21,29 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public TValue Value;
|
public TValue Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Entry[][] _hashTable = new Entry[TotalBuckets][];
|
private struct Bucket
|
||||||
|
{
|
||||||
|
public int Length;
|
||||||
|
public Entry[] Entries;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly Span<Entry> AsSpan()
|
||||||
|
{
|
||||||
|
return Entries == null ? Span<Entry>.Empty : Entries.AsSpan(0, Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Bucket[] _hashTable = new Bucket[TotalBuckets];
|
||||||
|
|
||||||
public IEnumerable<TKey> Keys
|
public IEnumerable<TKey> Keys
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
foreach (Entry[] bucket in _hashTable)
|
foreach (Bucket bucket in _hashTable)
|
||||||
{
|
{
|
||||||
if (bucket != null)
|
for (int i = 0; i < bucket.Length; i++)
|
||||||
{
|
{
|
||||||
foreach (Entry entry in bucket)
|
yield return bucket.Entries[i].Key;
|
||||||
{
|
|
||||||
yield return entry.Key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,14 +53,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
foreach (Entry[] bucket in _hashTable)
|
foreach (Bucket bucket in _hashTable)
|
||||||
{
|
{
|
||||||
if (bucket != null)
|
for (int i = 0; i < bucket.Length; i++)
|
||||||
{
|
{
|
||||||
foreach (Entry entry in bucket)
|
yield return bucket.Entries[i].Value;
|
||||||
{
|
|
||||||
yield return entry.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,40 +75,64 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
int hashCode = key.GetHashCode();
|
int hashCode = key.GetHashCode();
|
||||||
int bucketIndex = hashCode & TotalBucketsMask;
|
int bucketIndex = hashCode & TotalBucketsMask;
|
||||||
|
|
||||||
var bucket = _hashTable[bucketIndex];
|
ref var bucket = ref _hashTable[bucketIndex];
|
||||||
if (bucket != null)
|
if (bucket.Entries != null)
|
||||||
{
|
{
|
||||||
int index = bucket.Length;
|
int index = bucket.Length;
|
||||||
|
|
||||||
Array.Resize(ref _hashTable[bucketIndex], index + 1);
|
if (index >= bucket.Entries.Length)
|
||||||
|
{
|
||||||
|
Array.Resize(ref bucket.Entries, index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
_hashTable[bucketIndex][index] = entry;
|
bucket.Entries[index] = entry;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_hashTable[bucketIndex] = new[]
|
bucket.Entries = new[]
|
||||||
{
|
{
|
||||||
entry,
|
entry,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bucket.Length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(ref TKey key)
|
||||||
|
{
|
||||||
|
int hashCode = key.GetHashCode();
|
||||||
|
|
||||||
|
ref var bucket = ref _hashTable[hashCode & TotalBucketsMask];
|
||||||
|
var entries = bucket.AsSpan();
|
||||||
|
for (int i = 0; i < entries.Length; i++)
|
||||||
|
{
|
||||||
|
ref var entry = ref entries[i];
|
||||||
|
|
||||||
|
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
|
||||||
|
{
|
||||||
|
entries[(i + 1)..].CopyTo(entries[i..]);
|
||||||
|
bucket.Length--;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue(ref TKey key, out TValue value)
|
public bool TryGetValue(ref TKey key, out TValue value)
|
||||||
{
|
{
|
||||||
int hashCode = key.GetHashCode();
|
int hashCode = key.GetHashCode();
|
||||||
|
|
||||||
var bucket = _hashTable[hashCode & TotalBucketsMask];
|
var entries = _hashTable[hashCode & TotalBucketsMask].AsSpan();
|
||||||
if (bucket != null)
|
for (int i = 0; i < entries.Length; i++)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < bucket.Length; i++)
|
ref var entry = ref entries[i];
|
||||||
{
|
|
||||||
ref var entry = ref bucket[i];
|
|
||||||
|
|
||||||
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
|
if (entry.Hash == hashCode && entry.Key.Equals(ref key))
|
||||||
{
|
{
|
||||||
value = entry.Value;
|
value = entry.Value;
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -256,17 +256,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
using var cbs = gd.CommandBufferPool.Rent();
|
using var cbs = gd.CommandBufferPool.Rent();
|
||||||
|
|
||||||
var dstFormat = dst.VkFormat;
|
|
||||||
var dstSamples = dst.Info.Samples;
|
|
||||||
|
|
||||||
for (int l = 0; l < levels; l++)
|
for (int l = 0; l < levels; l++)
|
||||||
{
|
{
|
||||||
int srcWidth = Math.Max(1, src.Width >> l);
|
|
||||||
int srcHeight = Math.Max(1, src.Height >> l);
|
|
||||||
|
|
||||||
int dstWidth = Math.Max(1, dst.Width >> l);
|
|
||||||
int dstHeight = Math.Max(1, dst.Height >> l);
|
|
||||||
|
|
||||||
var mipSrcRegion = new Extents2D(
|
var mipSrcRegion = new Extents2D(
|
||||||
srcRegion.X1 >> l,
|
srcRegion.X1 >> l,
|
||||||
srcRegion.Y1 >> l,
|
srcRegion.Y1 >> l,
|
||||||
@@ -290,11 +281,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
gd,
|
gd,
|
||||||
cbs,
|
cbs,
|
||||||
srcView,
|
srcView,
|
||||||
dst.GetImageViewForAttachment(),
|
dstView,
|
||||||
dstWidth,
|
|
||||||
dstHeight,
|
|
||||||
dstSamples,
|
|
||||||
dstFormat,
|
|
||||||
mipSrcRegion,
|
mipSrcRegion,
|
||||||
mipDstRegion);
|
mipDstRegion);
|
||||||
}
|
}
|
||||||
@@ -304,12 +291,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
gd,
|
gd,
|
||||||
cbs,
|
cbs,
|
||||||
srcView,
|
srcView,
|
||||||
dst.GetImageViewForAttachment(),
|
dstView,
|
||||||
dstWidth,
|
|
||||||
dstHeight,
|
|
||||||
dstSamples,
|
|
||||||
dstFormat,
|
|
||||||
false,
|
|
||||||
mipSrcRegion,
|
mipSrcRegion,
|
||||||
mipDstRegion,
|
mipDstRegion,
|
||||||
linearFilter,
|
linearFilter,
|
||||||
@@ -367,12 +349,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
gd,
|
gd,
|
||||||
cbs,
|
cbs,
|
||||||
srcView,
|
srcView,
|
||||||
dstView.GetImageViewForAttachment(),
|
dstView,
|
||||||
dstView.Width,
|
|
||||||
dstView.Height,
|
|
||||||
dstView.Info.Samples,
|
|
||||||
dstView.VkFormat,
|
|
||||||
dstView.Info.Format.IsDepthOrStencil(),
|
|
||||||
extents,
|
extents,
|
||||||
extents,
|
extents,
|
||||||
false);
|
false);
|
||||||
@@ -394,12 +371,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
TextureView src,
|
TextureView src,
|
||||||
Auto<DisposableImageView> dst,
|
TextureView dst,
|
||||||
int dstWidth,
|
|
||||||
int dstHeight,
|
|
||||||
int dstSamples,
|
|
||||||
VkFormat dstFormat,
|
|
||||||
bool dstIsDepthOrStencil,
|
|
||||||
Extents2D srcRegion,
|
Extents2D srcRegion,
|
||||||
Extents2D dstRegion,
|
Extents2D dstRegion,
|
||||||
bool linearFilter,
|
bool linearFilter,
|
||||||
@@ -453,6 +425,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
0f,
|
0f,
|
||||||
1f);
|
1f);
|
||||||
|
|
||||||
|
bool dstIsDepthOrStencil = dst.Info.Format.IsDepthOrStencil();
|
||||||
|
|
||||||
if (dstIsDepthOrStencil)
|
if (dstIsDepthOrStencil)
|
||||||
{
|
{
|
||||||
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
|
_pipeline.SetProgram(src.Info.Target.IsMultisample() ? _programDepthBlitMs : _programDepthBlit);
|
||||||
@@ -471,7 +445,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_pipeline.SetProgram(_programColorBlit);
|
_pipeline.SetProgram(_programColorBlit);
|
||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, dstIsDepthOrStencil, dstFormat);
|
int dstWidth = dst.Width;
|
||||||
|
int dstHeight = dst.Height;
|
||||||
|
|
||||||
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
|
||||||
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
_pipeline.SetRenderTargetColorMasks(new uint[] { 0xf });
|
||||||
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
|
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
|
||||||
|
|
||||||
@@ -496,11 +473,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
CommandBufferScoped cbs,
|
CommandBufferScoped cbs,
|
||||||
TextureView src,
|
TextureView src,
|
||||||
Auto<DisposableImageView> dst,
|
TextureView dst,
|
||||||
int dstWidth,
|
|
||||||
int dstHeight,
|
|
||||||
int dstSamples,
|
|
||||||
VkFormat dstFormat,
|
|
||||||
Extents2D srcRegion,
|
Extents2D srcRegion,
|
||||||
Extents2D dstRegion)
|
Extents2D dstRegion)
|
||||||
{
|
{
|
||||||
@@ -548,7 +521,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
0f,
|
0f,
|
||||||
1f);
|
1f);
|
||||||
|
|
||||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, (uint)dstSamples, true, dstFormat);
|
int dstWidth = dst.Width;
|
||||||
|
int dstHeight = dst.Height;
|
||||||
|
|
||||||
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
|
||||||
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
|
_pipeline.SetScissors(stackalloc Rectangle<int>[] { new Rectangle<int>(0, 0, dstWidth, dstHeight) });
|
||||||
_pipeline.SetViewports(viewports);
|
_pipeline.SetViewports(viewports);
|
||||||
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||||
@@ -660,12 +636,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void Clear(
|
public void Clear(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Auto<DisposableImageView> dst,
|
TextureView dst,
|
||||||
ReadOnlySpan<float> clearColor,
|
ReadOnlySpan<float> clearColor,
|
||||||
uint componentMask,
|
uint componentMask,
|
||||||
int dstWidth,
|
int dstWidth,
|
||||||
int dstHeight,
|
int dstHeight,
|
||||||
VkFormat dstFormat,
|
|
||||||
ComponentType type,
|
ComponentType type,
|
||||||
Rectangle<int> scissor)
|
Rectangle<int> scissor)
|
||||||
{
|
{
|
||||||
@@ -710,7 +685,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.SetProgram(program);
|
_pipeline.SetProgram(program);
|
||||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, false, dstFormat);
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
|
||||||
_pipeline.SetRenderTargetColorMasks(new[] { componentMask });
|
_pipeline.SetRenderTargetColorMasks(new[] { componentMask });
|
||||||
_pipeline.SetViewports(viewports);
|
_pipeline.SetViewports(viewports);
|
||||||
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
|
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
|
||||||
@@ -721,7 +696,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void Clear(
|
public void Clear(
|
||||||
VulkanRenderer gd,
|
VulkanRenderer gd,
|
||||||
Auto<DisposableImageView> dst,
|
TextureView dst,
|
||||||
float depthValue,
|
float depthValue,
|
||||||
bool depthMask,
|
bool depthMask,
|
||||||
int stencilValue,
|
int stencilValue,
|
||||||
@@ -757,7 +732,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
1f);
|
1f);
|
||||||
|
|
||||||
_pipeline.SetProgram(_programDepthStencilClear);
|
_pipeline.SetProgram(_programDepthStencilClear);
|
||||||
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight, true, dstFormat);
|
_pipeline.SetRenderTarget(dst, (uint)dstWidth, (uint)dstHeight);
|
||||||
_pipeline.SetViewports(viewports);
|
_pipeline.SetViewports(viewports);
|
||||||
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
|
_pipeline.SetScissors(stackalloc Rectangle<int>[] { scissor });
|
||||||
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
_pipeline.SetPrimitiveTopology(PrimitiveTopology.TriangleStrip);
|
||||||
@@ -1163,12 +1138,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var srcView = Create2DLayerView(src, srcLayer + z, 0);
|
var srcView = Create2DLayerView(src, srcLayer + z, 0);
|
||||||
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
||||||
|
|
||||||
_pipeline.SetRenderTarget(
|
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
|
||||||
dstView.GetImageViewForAttachment(),
|
|
||||||
(uint)dst.Width,
|
|
||||||
(uint)dst.Height,
|
|
||||||
true,
|
|
||||||
dst.VkFormat);
|
|
||||||
|
|
||||||
CopyMSDraw(srcView, aspectFlags, fromMS: true);
|
CopyMSDraw(srcView, aspectFlags, fromMS: true);
|
||||||
|
|
||||||
@@ -1294,13 +1264,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var srcView = Create2DLayerView(src, srcLayer + z, 0);
|
var srcView = Create2DLayerView(src, srcLayer + z, 0);
|
||||||
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
||||||
|
|
||||||
_pipeline.SetRenderTarget(
|
_pipeline.SetRenderTarget(dstView, (uint)dst.Width, (uint)dst.Height);
|
||||||
dstView.GetImageViewForAttachment(),
|
|
||||||
(uint)dst.Width,
|
|
||||||
(uint)dst.Height,
|
|
||||||
(uint)samples,
|
|
||||||
true,
|
|
||||||
dst.VkFormat);
|
|
||||||
|
|
||||||
CopyMSDraw(srcView, aspectFlags, fromMS: false);
|
CopyMSDraw(srcView, aspectFlags, fromMS: false);
|
||||||
|
|
||||||
@@ -1328,13 +1292,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
|
||||||
|
|
||||||
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null);
|
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Fragment, 0, srcView, null);
|
||||||
_pipeline.SetRenderTarget(
|
_pipeline.SetRenderTarget(dstView.GetView(format), (uint)dst.Width, (uint)dst.Height);
|
||||||
dstView.GetView(format).GetImageViewForAttachment(),
|
|
||||||
(uint)dst.Width,
|
|
||||||
(uint)dst.Height,
|
|
||||||
(uint)samples,
|
|
||||||
false,
|
|
||||||
vkFormat);
|
|
||||||
|
|
||||||
_pipeline.Draw(4, 1, 0, 0);
|
_pipeline.Draw(4, 1, 0, 0);
|
||||||
|
|
||||||
@@ -1471,9 +1429,9 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
};
|
};
|
||||||
|
|
||||||
var info = new TextureCreateInfo(
|
var info = new TextureCreateInfo(
|
||||||
from.Info.Width,
|
Math.Max(1, from.Info.Width >> level),
|
||||||
from.Info.Height,
|
Math.Max(1, from.Info.Height >> level),
|
||||||
from.Info.Depth,
|
1,
|
||||||
1,
|
1,
|
||||||
from.Info.Samples,
|
from.Info.Samples,
|
||||||
from.Info.BlockWidth,
|
from.Info.BlockWidth,
|
||||||
|
@@ -55,6 +55,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
protected FramebufferParams FramebufferParams;
|
protected FramebufferParams FramebufferParams;
|
||||||
private Auto<DisposableFramebuffer> _framebuffer;
|
private Auto<DisposableFramebuffer> _framebuffer;
|
||||||
private Auto<DisposableRenderPass> _renderPass;
|
private Auto<DisposableRenderPass> _renderPass;
|
||||||
|
private RenderPassHolder _nullRenderPass;
|
||||||
private int _writtenAttachmentCount;
|
private int _writtenAttachmentCount;
|
||||||
|
|
||||||
private bool _framebufferUsingColorWriteMask;
|
private bool _framebufferUsingColorWriteMask;
|
||||||
@@ -1488,98 +1489,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
protected unsafe void CreateRenderPass()
|
protected unsafe void CreateRenderPass()
|
||||||
{
|
{
|
||||||
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
|
||||||
|
|
||||||
AttachmentDescription[] attachmentDescs = null;
|
|
||||||
|
|
||||||
var subpass = new SubpassDescription
|
|
||||||
{
|
|
||||||
PipelineBindPoint = PipelineBindPoint.Graphics,
|
|
||||||
};
|
|
||||||
|
|
||||||
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
|
|
||||||
|
|
||||||
var hasFramebuffer = FramebufferParams != null;
|
var hasFramebuffer = FramebufferParams != null;
|
||||||
|
|
||||||
if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0)
|
|
||||||
{
|
|
||||||
attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount];
|
|
||||||
|
|
||||||
for (int i = 0; i < FramebufferParams.AttachmentsCount; i++)
|
|
||||||
{
|
|
||||||
attachmentDescs[i] = new AttachmentDescription(
|
|
||||||
0,
|
|
||||||
FramebufferParams.AttachmentFormats[i],
|
|
||||||
TextureStorage.ConvertToSampleCountFlags(Gd.Capabilities.SupportedSampleCounts, FramebufferParams.AttachmentSamples[i]),
|
|
||||||
AttachmentLoadOp.Load,
|
|
||||||
AttachmentStoreOp.Store,
|
|
||||||
AttachmentLoadOp.Load,
|
|
||||||
AttachmentStoreOp.Store,
|
|
||||||
ImageLayout.General,
|
|
||||||
ImageLayout.General);
|
|
||||||
}
|
|
||||||
|
|
||||||
int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount;
|
|
||||||
|
|
||||||
if (colorAttachmentsCount > MaxAttachments - 1)
|
|
||||||
{
|
|
||||||
colorAttachmentsCount = MaxAttachments - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colorAttachmentsCount != 0)
|
|
||||||
{
|
|
||||||
int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex;
|
|
||||||
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
|
|
||||||
subpass.PColorAttachments = &attachmentReferences[0];
|
|
||||||
|
|
||||||
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
|
|
||||||
for (int i = 0; i <= maxAttachmentIndex; i++)
|
|
||||||
{
|
|
||||||
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < colorAttachmentsCount; i++)
|
|
||||||
{
|
|
||||||
int bindIndex = FramebufferParams.AttachmentIndices[i];
|
|
||||||
|
|
||||||
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FramebufferParams.HasDepthStencil)
|
|
||||||
{
|
|
||||||
uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1;
|
|
||||||
|
|
||||||
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
|
|
||||||
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
|
||||||
|
|
||||||
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
|
||||||
{
|
|
||||||
var renderPassCreateInfo = new RenderPassCreateInfo
|
|
||||||
{
|
|
||||||
SType = StructureType.RenderPassCreateInfo,
|
|
||||||
PAttachments = pAttachmentDescs,
|
|
||||||
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
|
|
||||||
PSubpasses = &subpass,
|
|
||||||
SubpassCount = 1,
|
|
||||||
PDependencies = &subpassDependency,
|
|
||||||
DependencyCount = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
|
||||||
|
|
||||||
_renderPass?.Dispose();
|
|
||||||
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(Gd.Api, Device, renderPass));
|
|
||||||
}
|
|
||||||
|
|
||||||
EndRenderPass();
|
EndRenderPass();
|
||||||
|
|
||||||
_framebuffer?.Dispose();
|
if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0)
|
||||||
_framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null;
|
{
|
||||||
|
// Use the null framebuffer.
|
||||||
|
_nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
|
||||||
|
|
||||||
|
_renderPass = _nullRenderPass.GetRenderPass();
|
||||||
|
_framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(_renderPass, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SignalStateChange()
|
protected void SignalStateChange()
|
||||||
@@ -1770,8 +1695,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_renderPass?.Dispose();
|
_nullRenderPass?.Dispose();
|
||||||
_framebuffer?.Dispose();
|
|
||||||
_newState.Dispose();
|
_newState.Dispose();
|
||||||
_descriptorSetUpdater.Dispose();
|
_descriptorSetUpdater.Dispose();
|
||||||
_vertexBufferUpdater.Dispose();
|
_vertexBufferUpdater.Dispose();
|
||||||
|
@@ -51,7 +51,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not writing all components,
|
// We can't use CmdClearAttachments if not writing all components,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
var dstTexture = FramebufferParams.GetAttachment(index);
|
var dstTexture = FramebufferParams.GetColorView(index);
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -71,7 +71,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
componentMask,
|
componentMask,
|
||||||
(int)FramebufferParams.Width,
|
(int)FramebufferParams.Width,
|
||||||
(int)FramebufferParams.Height,
|
(int)FramebufferParams.Height,
|
||||||
FramebufferParams.AttachmentFormats[index],
|
|
||||||
FramebufferParams.GetAttachmentComponentType(index),
|
FramebufferParams.GetAttachmentComponentType(index),
|
||||||
ClearScissor);
|
ClearScissor);
|
||||||
}
|
}
|
||||||
@@ -92,7 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
// We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits,
|
||||||
// because on Vulkan, the pipeline state does not affect clears.
|
// because on Vulkan, the pipeline state does not affect clears.
|
||||||
var dstTexture = FramebufferParams.GetDepthStencilAttachment();
|
var dstTexture = FramebufferParams.GetDepthStencilView();
|
||||||
if (dstTexture == null)
|
if (dstTexture == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
@@ -9,21 +9,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, bool isDepthStencil, VkFormat format)
|
public void SetRenderTarget(TextureView view, uint width, uint height)
|
||||||
{
|
{
|
||||||
SetRenderTarget(view, width, height, 1u, isDepthStencil, format);
|
CreateFramebuffer(view, width, height);
|
||||||
}
|
|
||||||
|
|
||||||
public void SetRenderTarget(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
|
|
||||||
{
|
|
||||||
CreateFramebuffer(view, width, height, samples, isDepthStencil, format);
|
|
||||||
CreateRenderPass();
|
CreateRenderPass();
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateFramebuffer(Auto<DisposableImageView> view, uint width, uint height, uint samples, bool isDepthStencil, VkFormat format)
|
private void CreateFramebuffer(TextureView view, uint width, uint height)
|
||||||
{
|
{
|
||||||
FramebufferParams = new FramebufferParams(Device, view, width, height, samples, isDepthStencil, format);
|
FramebufferParams = new FramebufferParams(Device, view, width, height);
|
||||||
UpdatePipelineAttachmentFormats();
|
UpdatePipelineAttachmentFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs
Normal file
43
src/Ryujinx.Graphics.Vulkan/RenderPassCacheKey.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal readonly struct RenderPassCacheKey : IRefEquatable<RenderPassCacheKey>
|
||||||
|
{
|
||||||
|
private readonly TextureView _depthStencil;
|
||||||
|
private readonly TextureView[] _colors;
|
||||||
|
|
||||||
|
public RenderPassCacheKey(TextureView depthStencil, TextureView[] colors)
|
||||||
|
{
|
||||||
|
_depthStencil = depthStencil;
|
||||||
|
_colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
HashCode hc = new();
|
||||||
|
|
||||||
|
hc.Add(_depthStencil);
|
||||||
|
|
||||||
|
if (_colors != null)
|
||||||
|
{
|
||||||
|
foreach (var color in _colors)
|
||||||
|
{
|
||||||
|
hc.Add(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hc.ToHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ref RenderPassCacheKey other)
|
||||||
|
{
|
||||||
|
bool colorsNull = _colors == null;
|
||||||
|
bool otherNull = other._colors == null;
|
||||||
|
return other._depthStencil == _depthStencil &&
|
||||||
|
colorsNull == otherNull &&
|
||||||
|
(colorsNull || other._colors.SequenceEqual(_colors));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
180
src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
Normal file
180
src/Ryujinx.Graphics.Vulkan/RenderPassHolder.cs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal class RenderPassHolder
|
||||||
|
{
|
||||||
|
private readonly struct FramebufferCacheKey : IRefEquatable<FramebufferCacheKey>
|
||||||
|
{
|
||||||
|
private readonly uint _width;
|
||||||
|
private readonly uint _height;
|
||||||
|
private readonly uint _layers;
|
||||||
|
|
||||||
|
public FramebufferCacheKey(uint width, uint height, uint layers)
|
||||||
|
{
|
||||||
|
_width = width;
|
||||||
|
_height = height;
|
||||||
|
_layers = layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(_width, _height, _layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(ref FramebufferCacheKey other)
|
||||||
|
{
|
||||||
|
return other._width == _width && other._height == _height && other._layers == _layers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly TextureView[] _textures;
|
||||||
|
private readonly Auto<DisposableRenderPass> _renderPass;
|
||||||
|
private readonly HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>> _framebuffers;
|
||||||
|
private readonly RenderPassCacheKey _key;
|
||||||
|
|
||||||
|
public unsafe RenderPassHolder(VulkanRenderer gd, Device device, RenderPassCacheKey key, FramebufferParams fb)
|
||||||
|
{
|
||||||
|
// Create render pass using framebuffer params.
|
||||||
|
|
||||||
|
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
||||||
|
|
||||||
|
AttachmentDescription[] attachmentDescs = null;
|
||||||
|
|
||||||
|
var subpass = new SubpassDescription
|
||||||
|
{
|
||||||
|
PipelineBindPoint = PipelineBindPoint.Graphics,
|
||||||
|
};
|
||||||
|
|
||||||
|
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
|
||||||
|
|
||||||
|
var hasFramebuffer = fb != null;
|
||||||
|
|
||||||
|
if (hasFramebuffer && fb.AttachmentsCount != 0)
|
||||||
|
{
|
||||||
|
attachmentDescs = new AttachmentDescription[fb.AttachmentsCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < fb.AttachmentsCount; i++)
|
||||||
|
{
|
||||||
|
attachmentDescs[i] = new AttachmentDescription(
|
||||||
|
0,
|
||||||
|
fb.AttachmentFormats[i],
|
||||||
|
TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, fb.AttachmentSamples[i]),
|
||||||
|
AttachmentLoadOp.Load,
|
||||||
|
AttachmentStoreOp.Store,
|
||||||
|
AttachmentLoadOp.Load,
|
||||||
|
AttachmentStoreOp.Store,
|
||||||
|
ImageLayout.General,
|
||||||
|
ImageLayout.General);
|
||||||
|
}
|
||||||
|
|
||||||
|
int colorAttachmentsCount = fb.ColorAttachmentsCount;
|
||||||
|
|
||||||
|
if (colorAttachmentsCount > MaxAttachments - 1)
|
||||||
|
{
|
||||||
|
colorAttachmentsCount = MaxAttachments - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorAttachmentsCount != 0)
|
||||||
|
{
|
||||||
|
int maxAttachmentIndex = fb.MaxColorAttachmentIndex;
|
||||||
|
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
|
||||||
|
subpass.PColorAttachments = &attachmentReferences[0];
|
||||||
|
|
||||||
|
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
|
||||||
|
for (int i = 0; i <= maxAttachmentIndex; i++)
|
||||||
|
{
|
||||||
|
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < colorAttachmentsCount; i++)
|
||||||
|
{
|
||||||
|
int bindIndex = fb.AttachmentIndices[i];
|
||||||
|
|
||||||
|
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fb.HasDepthStencil)
|
||||||
|
{
|
||||||
|
uint dsIndex = (uint)fb.AttachmentsCount - 1;
|
||||||
|
|
||||||
|
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
|
||||||
|
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
||||||
|
|
||||||
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
||||||
|
{
|
||||||
|
var renderPassCreateInfo = new RenderPassCreateInfo
|
||||||
|
{
|
||||||
|
SType = StructureType.RenderPassCreateInfo,
|
||||||
|
PAttachments = pAttachmentDescs,
|
||||||
|
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
|
||||||
|
PSubpasses = &subpass,
|
||||||
|
SubpassCount = 1,
|
||||||
|
PDependencies = &subpassDependency,
|
||||||
|
DependencyCount = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
gd.Api.CreateRenderPass(device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
||||||
|
|
||||||
|
_renderPass?.Dispose();
|
||||||
|
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(gd.Api, device, renderPass));
|
||||||
|
}
|
||||||
|
|
||||||
|
_framebuffers = new HashTableSlim<FramebufferCacheKey, Auto<DisposableFramebuffer>>();
|
||||||
|
|
||||||
|
// Register this render pass with all render target views.
|
||||||
|
|
||||||
|
var textures = fb.GetAttachmentViews();
|
||||||
|
|
||||||
|
foreach (var texture in textures)
|
||||||
|
{
|
||||||
|
texture.AddRenderPass(key, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
_textures = textures;
|
||||||
|
_key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Auto<DisposableFramebuffer> GetFramebuffer(VulkanRenderer gd, CommandBufferScoped cbs, FramebufferParams fb)
|
||||||
|
{
|
||||||
|
var key = new FramebufferCacheKey(fb.Width, fb.Height, fb.Layers);
|
||||||
|
|
||||||
|
if (!_framebuffers.TryGetValue(ref key, out Auto<DisposableFramebuffer> result))
|
||||||
|
{
|
||||||
|
result = fb.Create(gd.Api, cbs, _renderPass);
|
||||||
|
|
||||||
|
_framebuffers.Add(ref key, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Auto<DisposableRenderPass> GetRenderPass()
|
||||||
|
{
|
||||||
|
return _renderPass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Dispose all framebuffers
|
||||||
|
|
||||||
|
foreach (var fb in _framebuffers.Values)
|
||||||
|
{
|
||||||
|
fb.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all texture views that this render pass has been disposed.
|
||||||
|
|
||||||
|
foreach (var texture in _textures)
|
||||||
|
{
|
||||||
|
texture.RemoveRenderPass(_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ using Ryujinx.Graphics.GAL;
|
|||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using Format = Ryujinx.Graphics.GAL.Format;
|
using Format = Ryujinx.Graphics.GAL.Format;
|
||||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
@@ -23,6 +24,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private readonly TextureCreateInfo _info;
|
private readonly TextureCreateInfo _info;
|
||||||
|
|
||||||
|
private HashTableSlim<RenderPassCacheKey, RenderPassHolder> _renderPasses;
|
||||||
|
|
||||||
public TextureCreateInfo Info => _info;
|
public TextureCreateInfo Info => _info;
|
||||||
|
|
||||||
public TextureStorage Storage { get; }
|
public TextureStorage Storage { get; }
|
||||||
@@ -158,6 +161,26 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Valid = true;
|
Valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a texture view for an existing swapchain image view.
|
||||||
|
/// Does not set storage, so only appropriate for swapchain use.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Do not use this for normal textures, and make sure uses do not try to read storage.</remarks>
|
||||||
|
public TextureView(VulkanRenderer gd, Device device, DisposableImageView view, TextureCreateInfo info, VkFormat format)
|
||||||
|
{
|
||||||
|
_gd = gd;
|
||||||
|
_device = device;
|
||||||
|
|
||||||
|
_imageView = new Auto<DisposableImageView>(view);
|
||||||
|
_imageViewDraw = _imageView;
|
||||||
|
_imageViewIdentity = _imageView;
|
||||||
|
_info = info;
|
||||||
|
|
||||||
|
VkFormat = format;
|
||||||
|
|
||||||
|
Valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
public Auto<DisposableImage> GetImage()
|
public Auto<DisposableImage> GetImage()
|
||||||
{
|
{
|
||||||
return Storage.GetImage();
|
return Storage.GetImage();
|
||||||
@@ -939,6 +962,34 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(
|
||||||
|
VulkanRenderer gd,
|
||||||
|
Device device,
|
||||||
|
CommandBufferScoped cbs,
|
||||||
|
FramebufferParams fb)
|
||||||
|
{
|
||||||
|
var key = fb.GetRenderPassCacheKey();
|
||||||
|
|
||||||
|
if (_renderPasses == null || !_renderPasses.TryGetValue(ref key, out RenderPassHolder rpHolder))
|
||||||
|
{
|
||||||
|
rpHolder = new RenderPassHolder(gd, device, key, fb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rpHolder.GetRenderPass(), rpHolder.GetFramebuffer(gd, cbs, fb));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRenderPass(RenderPassCacheKey key, RenderPassHolder renderPass)
|
||||||
|
{
|
||||||
|
_renderPasses ??= new HashTableSlim<RenderPassCacheKey, RenderPassHolder>();
|
||||||
|
|
||||||
|
_renderPasses.Add(ref key, renderPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveRenderPass(RenderPassCacheKey key)
|
||||||
|
{
|
||||||
|
_renderPasses.Remove(ref key);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
@@ -948,15 +999,29 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
if (_gd.Textures.Remove(this))
|
if (_gd.Textures.Remove(this))
|
||||||
{
|
{
|
||||||
_imageView.Dispose();
|
_imageView.Dispose();
|
||||||
_imageViewIdentity.Dispose();
|
|
||||||
_imageView2dArray?.Dispose();
|
_imageView2dArray?.Dispose();
|
||||||
|
|
||||||
|
if (_imageViewIdentity != _imageView)
|
||||||
|
{
|
||||||
|
_imageViewIdentity.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
if (_imageViewDraw != _imageViewIdentity)
|
if (_imageViewDraw != _imageViewIdentity)
|
||||||
{
|
{
|
||||||
_imageViewDraw.Dispose();
|
_imageViewDraw.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.DecrementViewsCount();
|
Storage.DecrementViewsCount();
|
||||||
|
|
||||||
|
if (_renderPasses != null)
|
||||||
|
{
|
||||||
|
var renderPasses = _renderPasses.Values.ToArray();
|
||||||
|
|
||||||
|
foreach (var pass in renderPasses)
|
||||||
|
{
|
||||||
|
pass.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private SwapchainKHR _swapchain;
|
private SwapchainKHR _swapchain;
|
||||||
|
|
||||||
private Image[] _swapchainImages;
|
private Image[] _swapchainImages;
|
||||||
private Auto<DisposableImageView>[] _swapchainImageViews;
|
private TextureView[] _swapchainImageViews;
|
||||||
|
|
||||||
private Semaphore[] _imageAvailableSemaphores;
|
private Semaphore[] _imageAvailableSemaphores;
|
||||||
private Semaphore[] _renderFinishedSemaphores;
|
private Semaphore[] _renderFinishedSemaphores;
|
||||||
@@ -143,6 +143,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Clipped = true,
|
Clipped = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var textureCreateInfo = new TextureCreateInfo(
|
||||||
|
_width,
|
||||||
|
_height,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
FormatTable.GetFormat(surfaceFormat.Format),
|
||||||
|
DepthStencilMode.Depth,
|
||||||
|
Target.Texture2D,
|
||||||
|
SwizzleComponent.Red,
|
||||||
|
SwizzleComponent.Green,
|
||||||
|
SwizzleComponent.Blue,
|
||||||
|
SwizzleComponent.Alpha);
|
||||||
|
|
||||||
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
_gd.SwapchainApi.CreateSwapchain(_device, swapchainCreateInfo, null, out _swapchain).ThrowOnError();
|
||||||
|
|
||||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, null);
|
||||||
@@ -154,11 +171,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
|
_gd.SwapchainApi.GetSwapchainImages(_device, _swapchain, &imageCount, pSwapchainImages);
|
||||||
}
|
}
|
||||||
|
|
||||||
_swapchainImageViews = new Auto<DisposableImageView>[imageCount];
|
_swapchainImageViews = new TextureView[imageCount];
|
||||||
|
|
||||||
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
for (int i = 0; i < _swapchainImageViews.Length; i++)
|
||||||
{
|
{
|
||||||
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
|
_swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format, textureCreateInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
var semaphoreCreateInfo = new SemaphoreCreateInfo
|
||||||
@@ -181,7 +198,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe Auto<DisposableImageView> CreateSwapchainImageView(Image swapchainImage, VkFormat format)
|
private unsafe TextureView CreateSwapchainImageView(Image swapchainImage, VkFormat format, TextureCreateInfo info)
|
||||||
{
|
{
|
||||||
var componentMapping = new ComponentMapping(
|
var componentMapping = new ComponentMapping(
|
||||||
ComponentSwizzle.R,
|
ComponentSwizzle.R,
|
||||||
@@ -204,7 +221,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
};
|
};
|
||||||
|
|
||||||
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
_gd.Api.CreateImageView(_device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||||
return new Auto<DisposableImageView>(new DisposableImageView(_gd.Api, _device, imageView));
|
|
||||||
|
return new TextureView(_gd, _device, new DisposableImageView(_gd.Api, _device, imageView), info, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
|
private static SurfaceFormatKHR ChooseSwapSurfaceFormat(SurfaceFormatKHR[] availableFormats, bool colorSpacePassthroughEnabled)
|
||||||
@@ -406,7 +424,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_scalingFilter.Run(
|
_scalingFilter.Run(
|
||||||
view,
|
view,
|
||||||
cbs,
|
cbs,
|
||||||
_swapchainImageViews[nextImage],
|
_swapchainImageViews[nextImage].GetImageViewForAttachment(),
|
||||||
_format,
|
_format,
|
||||||
_width,
|
_width,
|
||||||
_height,
|
_height,
|
||||||
@@ -421,11 +439,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
cbs,
|
cbs,
|
||||||
view,
|
view,
|
||||||
_swapchainImageViews[nextImage],
|
_swapchainImageViews[nextImage],
|
||||||
_width,
|
|
||||||
_height,
|
|
||||||
1,
|
|
||||||
_format,
|
|
||||||
false,
|
|
||||||
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
new Extents2D(srcX0, srcY0, srcX1, srcY1),
|
||||||
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
new Extents2D(dstX0, dstY1, dstX1, dstY0),
|
||||||
_isLinear,
|
_isLinear,
|
||||||
|
@@ -330,7 +330,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
HorizonFsClient fsClient = new(this);
|
HorizonFsClient fsClient = new(this);
|
||||||
|
|
||||||
ServiceTable = new ServiceTable();
|
ServiceTable = new ServiceTable();
|
||||||
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient));
|
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager));
|
||||||
|
|
||||||
foreach (var service in services)
|
foreach (var service in services)
|
||||||
{
|
{
|
||||||
|
@@ -18,6 +18,7 @@ using System.Collections.Specialized;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using LazyFile = Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy.LazyFile;
|
||||||
using Path = System.IO.Path;
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS
|
namespace Ryujinx.HLE.HOS
|
||||||
@@ -512,7 +513,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName))
|
using (IFileSystem fs = new LocalFileSystem(mod.Path.FullName))
|
||||||
{
|
{
|
||||||
AddFiles(fs, mod.Name, fileSet, builder);
|
AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder);
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -528,7 +529,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}");
|
Logger.Info?.Print(LogClass.ModLoader, $"Found 'romfs.bin' for Application {applicationId:X16}");
|
||||||
using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage()))
|
using (IFileSystem fs = new RomFsFileSystem(mod.Path.OpenRead().AsStorage()))
|
||||||
{
|
{
|
||||||
AddFiles(fs, mod.Name, fileSet, builder);
|
AddFiles(fs, mod.Name, mod.Path.FullName, fileSet, builder);
|
||||||
}
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -561,18 +562,18 @@ namespace Ryujinx.HLE.HOS
|
|||||||
return newStorage;
|
return newStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddFiles(IFileSystem fs, string modName, ISet<string> fileSet, RomFsBuilder builder)
|
private static void AddFiles(IFileSystem fs, string modName, string rootPath, ISet<string> fileSet, RomFsBuilder builder)
|
||||||
{
|
{
|
||||||
foreach (var entry in fs.EnumerateEntries()
|
foreach (var entry in fs.EnumerateEntries()
|
||||||
|
.AsParallel()
|
||||||
.Where(f => f.Type == DirectoryEntryType.File)
|
.Where(f => f.Type == DirectoryEntryType.File)
|
||||||
.OrderBy(f => f.FullPath, StringComparer.Ordinal))
|
.OrderBy(f => f.FullPath, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
using var file = new UniqueRef<IFile>();
|
var file = new LazyFile(entry.FullPath, rootPath, fs);
|
||||||
|
|
||||||
fs.OpenFile(ref file.Ref, entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
if (fileSet.Add(entry.FullPath))
|
if (fileSet.Add(entry.FullPath))
|
||||||
{
|
{
|
||||||
builder.AddFile(entry.FullPath, file.Release());
|
builder.AddFile(entry.FullPath, file);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -4,6 +4,7 @@ using LibHac.Fs;
|
|||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -11,7 +12,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
||||||
{
|
{
|
||||||
public class AccountManager
|
public class AccountManager : IEmulatorAccountManager
|
||||||
{
|
{
|
||||||
public static readonly UserId DefaultUserId = new("00000000000000010000000000000000");
|
public static readonly UserId DefaultUserId = new("00000000000000010000000000000000");
|
||||||
|
|
||||||
@@ -106,6 +107,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
_accountSaveDataManager.Save(_profiles);
|
_accountSaveDataManager.Save(_profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenUserOnlinePlay(Uid userId)
|
||||||
|
{
|
||||||
|
OpenUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
|
||||||
|
}
|
||||||
|
|
||||||
public void OpenUserOnlinePlay(UserId userId)
|
public void OpenUserOnlinePlay(UserId userId)
|
||||||
{
|
{
|
||||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
@@ -127,6 +133,11 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
|
|||||||
_accountSaveDataManager.Save(_profiles);
|
_accountSaveDataManager.Save(_profiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CloseUserOnlinePlay(Uid userId)
|
||||||
|
{
|
||||||
|
CloseUserOnlinePlay(new UserId((long)userId.Low, (long)userId.High));
|
||||||
|
}
|
||||||
|
|
||||||
public void CloseUserOnlinePlay(UserId userId)
|
public void CloseUserOnlinePlay(UserId userId)
|
||||||
{
|
{
|
||||||
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
if (_profiles.TryGetValue(userId.ToString(), out UserProfile profile))
|
||||||
|
@@ -22,6 +22,9 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||||||
private readonly UserId _userId;
|
private readonly UserId _userId;
|
||||||
#pragma warning restore IDE0052
|
#pragma warning restore IDE0052
|
||||||
|
|
||||||
|
private byte[] _cachedTokenData;
|
||||||
|
private DateTime _cachedTokenExpiry;
|
||||||
|
|
||||||
public ManagerServer(UserId userId)
|
public ManagerServer(UserId userId)
|
||||||
{
|
{
|
||||||
_userId = userId;
|
_userId = userId;
|
||||||
@@ -144,7 +147,13 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc.AccountService
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
byte[] tokenData = Encoding.ASCII.GetBytes(GenerateIdToken());
|
if (_cachedTokenData == null || DateTime.UtcNow > _cachedTokenExpiry)
|
||||||
|
{
|
||||||
|
_cachedTokenExpiry = DateTime.UtcNow + TimeSpan.FromHours(3);
|
||||||
|
_cachedTokenData = Encoding.ASCII.GetBytes(GenerateIdToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] tokenData = _cachedTokenData;
|
||||||
|
|
||||||
context.Memory.Write(bufferPosition, tokenData);
|
context.Memory.Write(bufferPosition, tokenData);
|
||||||
context.ResponseData.Write(tokenData.Length);
|
context.ResponseData.Write(tokenData.Length);
|
||||||
|
@@ -1,55 +0,0 @@
|
|||||||
using Ryujinx.Common;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend
|
|
||||||
{
|
|
||||||
[Service("friend:a", FriendServicePermissionLevel.Administrator)]
|
|
||||||
[Service("friend:m", FriendServicePermissionLevel.Manager)]
|
|
||||||
[Service("friend:s", FriendServicePermissionLevel.System)]
|
|
||||||
[Service("friend:u", FriendServicePermissionLevel.User)]
|
|
||||||
[Service("friend:v", FriendServicePermissionLevel.Viewer)]
|
|
||||||
class IServiceCreator : IpcService
|
|
||||||
{
|
|
||||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
|
||||||
|
|
||||||
public IServiceCreator(ServiceCtx context, FriendServicePermissionLevel permissionLevel)
|
|
||||||
{
|
|
||||||
_permissionLevel = permissionLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(0)]
|
|
||||||
// CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService>
|
|
||||||
public ResultCode CreateFriendService(ServiceCtx context)
|
|
||||||
{
|
|
||||||
MakeObject(context, new IFriendService(_permissionLevel));
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(1)] // 2.0.0+
|
|
||||||
// CreateNotificationService(nn::account::Uid userId) -> object<nn::friends::detail::ipc::INotificationService>
|
|
||||||
public ResultCode CreateNotificationService(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
MakeObject(context, new INotificationService(context, userId, _permissionLevel));
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(2)] // 4.0.0+
|
|
||||||
// CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService>
|
|
||||||
public ResultCode CreateDaemonSuspendSessionService(ServiceCtx context)
|
|
||||||
{
|
|
||||||
MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel));
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Friend
|
|
||||||
{
|
|
||||||
enum ResultCode
|
|
||||||
{
|
|
||||||
ModuleId = 121,
|
|
||||||
ErrorCodeShift = 9,
|
|
||||||
|
|
||||||
Success = 0,
|
|
||||||
|
|
||||||
InvalidArgument = (2 << ErrorCodeShift) | ModuleId,
|
|
||||||
InternetRequestDenied = (6 << ErrorCodeShift) | ModuleId,
|
|
||||||
NotificationQueueEmpty = (15 << ErrorCodeShift) | ModuleId,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)]
|
|
||||||
struct Friend
|
|
||||||
{
|
|
||||||
public UserId UserId;
|
|
||||||
public long NetworkUserId;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)]
|
|
||||||
public string Nickname;
|
|
||||||
|
|
||||||
public UserPresence presence;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsFavourite;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsNew;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)]
|
|
||||||
readonly char[] Unknown;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsValid;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
struct FriendFilter
|
|
||||||
{
|
|
||||||
public PresenceStatusFilter PresenceStatus;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsFavoriteOnly;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsSameAppPresenceOnly;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsSameAppPlayedOnly;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool IsArbitraryAppPlayedOnly;
|
|
||||||
|
|
||||||
public long PresenceGroupId;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 0x8)]
|
|
||||||
struct UserPresence
|
|
||||||
{
|
|
||||||
public UserId UserId;
|
|
||||||
public long LastTimeOnlineTimestamp;
|
|
||||||
public PresenceStatus Status;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.I1)]
|
|
||||||
public bool SamePresenceGroupApplication;
|
|
||||||
|
|
||||||
public Array3<byte> Unknown;
|
|
||||||
private AppKeyValueStorageHolder _appKeyValueStorage;
|
|
||||||
|
|
||||||
public Span<byte> AppKeyValueStorage => MemoryMarshal.Cast<AppKeyValueStorageHolder, byte>(MemoryMarshal.CreateSpan(ref _appKeyValueStorage, AppKeyValueStorageHolder.Size));
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = Size)]
|
|
||||||
private struct AppKeyValueStorageHolder
|
|
||||||
{
|
|
||||||
public const int Size = 0xC0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly override string ToString()
|
|
||||||
{
|
|
||||||
return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status} }}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|
||||||
{
|
|
||||||
class IDaemonSuspendSessionService : IpcService
|
|
||||||
{
|
|
||||||
#pragma warning disable IDE0052 // Remove unread private member
|
|
||||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
|
||||||
#pragma warning restore IDE0052
|
|
||||||
|
|
||||||
public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
|
|
||||||
{
|
|
||||||
_permissionLevel = permissionLevel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,374 +0,0 @@
|
|||||||
using LibHac.Ns;
|
|
||||||
using Ryujinx.Common;
|
|
||||||
using Ryujinx.Common.Logging;
|
|
||||||
using Ryujinx.Common.Memory;
|
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.FriendService;
|
|
||||||
using Ryujinx.Horizon.Common;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|
||||||
{
|
|
||||||
class IFriendService : IpcService
|
|
||||||
{
|
|
||||||
#pragma warning disable IDE0052 // Remove unread private member
|
|
||||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
|
||||||
#pragma warning restore IDE0052
|
|
||||||
private KEvent _completionEvent;
|
|
||||||
|
|
||||||
public IFriendService(FriendServicePermissionLevel permissionLevel)
|
|
||||||
{
|
|
||||||
_permissionLevel = permissionLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(0)]
|
|
||||||
// GetCompletionEvent() -> handle<copy>
|
|
||||||
public ResultCode GetCompletionEvent(ServiceCtx context)
|
|
||||||
{
|
|
||||||
_completionEvent ??= new KEvent(context.Device.System.KernelContext);
|
|
||||||
|
|
||||||
if (context.Process.HandleTable.GenerateHandle(_completionEvent.ReadableEvent, out int completionEventHandle) != Result.Success)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Out of handles!");
|
|
||||||
}
|
|
||||||
|
|
||||||
_completionEvent.WritableEvent.Signal();
|
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(completionEventHandle);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(1)]
|
|
||||||
// nn::friends::Cancel()
|
|
||||||
public ResultCode Cancel(ServiceCtx context)
|
|
||||||
{
|
|
||||||
// TODO: Original service sets an internal field to 1 here. Determine usage.
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10100)]
|
|
||||||
// nn::friends::GetFriendListIds(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
|
||||||
// -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
|
|
||||||
public ResultCode GetFriendListIds(ServiceCtx context)
|
|
||||||
{
|
|
||||||
int offset = context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
// Padding
|
|
||||||
context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
|
||||||
|
|
||||||
// Pid placeholder
|
|
||||||
context.RequestData.ReadInt64();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
|
||||||
context.ResponseData.Write(0);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
|
|
||||||
{
|
|
||||||
UserId = userId.ToString(),
|
|
||||||
offset,
|
|
||||||
filter.PresenceStatus,
|
|
||||||
filter.IsFavoriteOnly,
|
|
||||||
filter.IsSameAppPresenceOnly,
|
|
||||||
filter.IsSameAppPlayedOnly,
|
|
||||||
filter.IsArbitraryAppPlayedOnly,
|
|
||||||
filter.PresenceGroupId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10101)]
|
|
||||||
// nn::friends::GetFriendList(int offset, nn::account::Uid userId, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid)
|
|
||||||
// -> int outCount, array<nn::friends::detail::FriendImpl, 0x6>
|
|
||||||
public ResultCode GetFriendList(ServiceCtx context)
|
|
||||||
{
|
|
||||||
int offset = context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
// Padding
|
|
||||||
context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
|
|
||||||
|
|
||||||
// Pid placeholder
|
|
||||||
context.RequestData.ReadInt64();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
|
||||||
context.ResponseData.Write(0);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new
|
|
||||||
{
|
|
||||||
UserId = userId.ToString(),
|
|
||||||
offset,
|
|
||||||
filter.PresenceStatus,
|
|
||||||
filter.IsFavoriteOnly,
|
|
||||||
filter.IsSameAppPresenceOnly,
|
|
||||||
filter.IsSameAppPlayedOnly,
|
|
||||||
filter.IsArbitraryAppPlayedOnly,
|
|
||||||
filter.PresenceGroupId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10120)] // 10.0.0+
|
|
||||||
// nn::friends::IsFriendListCacheAvailable(nn::account::Uid userId) -> bool
|
|
||||||
public ResultCode IsFriendListCacheAvailable(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Service mount the friends:/ system savedata and try to load friend.cache file, returns true if exists, false otherwise.
|
|
||||||
// NOTE: If no cache is available, guest then calls nn::friends::EnsureFriendListAvailable, we can avoid that by faking the cache check.
|
|
||||||
context.ResponseData.Write(true);
|
|
||||||
|
|
||||||
// TODO: Since we don't support friend features, it's fine to stub it for now.
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10121)] // 10.0.0+
|
|
||||||
// nn::friends::EnsureFriendListAvailable(nn::account::Uid userId)
|
|
||||||
public ResultCode EnsureFriendListAvailable(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Service mount the friends:/ system savedata and create a friend.cache file for the given user id.
|
|
||||||
// Since we don't support friend features, it's fine to stub it for now.
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10400)]
|
|
||||||
// nn::friends::GetBlockedUserListIds(int offset, nn::account::Uid userId) -> (u32, buffer<nn::account::NetworkServiceAccountId, 0xa>)
|
|
||||||
public ResultCode GetBlockedUserListIds(ServiceCtx context)
|
|
||||||
{
|
|
||||||
int offset = context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
// Padding
|
|
||||||
context.RequestData.ReadInt32();
|
|
||||||
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
// There are no friends blocked, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
|
|
||||||
context.ResponseData.Write(0);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { offset, UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10420)]
|
|
||||||
// nn::friends::CheckBlockedUserListAvailability(nn::account::Uid userId) -> bool
|
|
||||||
public ResultCode CheckBlockedUserListAvailability(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
// Yes, it is available.
|
|
||||||
context.ResponseData.Write(true);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10600)]
|
|
||||||
// nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid userId)
|
|
||||||
public ResultCode DeclareOpenOnlinePlaySession(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Device.System.AccountManager.OpenUserOnlinePlay(userId);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10601)]
|
|
||||||
// nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid userId)
|
|
||||||
public ResultCode DeclareCloseOnlinePlaySession(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Device.System.AccountManager.CloseUserOnlinePlay(userId);
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = userId.ToString() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10610)]
|
|
||||||
// nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
|
|
||||||
public ResultCode UpdateUserPresence(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId uuid = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
// Pid placeholder
|
|
||||||
context.RequestData.ReadInt64();
|
|
||||||
|
|
||||||
ulong position = context.Request.PtrBuff[0].Position;
|
|
||||||
ulong size = context.Request.PtrBuff[0].Size;
|
|
||||||
|
|
||||||
ReadOnlySpan<UserPresence> userPresenceInputArray = MemoryMarshal.Cast<byte, UserPresence>(context.Memory.GetSpan(position, (int)size));
|
|
||||||
|
|
||||||
if (uuid.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray = userPresenceInputArray.ToArray() });
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10700)]
|
|
||||||
// nn::friends::GetPlayHistoryRegistrationKey(b8 unknown, nn::account::Uid) -> buffer<nn::friends::PlayHistoryRegistrationKey, 0x1a>
|
|
||||||
public ResultCode GetPlayHistoryRegistrationKey(ServiceCtx context)
|
|
||||||
{
|
|
||||||
bool unknownBool = context.RequestData.ReadBoolean();
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(0x40UL);
|
|
||||||
|
|
||||||
ulong bufferPosition = context.Request.RecvListBuff[0].Position;
|
|
||||||
|
|
||||||
if (userId.IsNull)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Calls nn::friends::detail::service::core::PlayHistoryManager::GetInstance and stores the instance.
|
|
||||||
|
|
||||||
byte[] randomBytes = new byte[8];
|
|
||||||
|
|
||||||
Random.Shared.NextBytes(randomBytes);
|
|
||||||
|
|
||||||
// NOTE: Calls nn::friends::detail::service::core::UuidManager::GetInstance and stores the instance.
|
|
||||||
// Then call nn::friends::detail::service::core::AccountStorageManager::GetInstance and store the instance.
|
|
||||||
// Then it checks if an Uuid is already stored for the UserId, if not it generates a random Uuid.
|
|
||||||
// And store it in the savedata 8000000000000080 in the friends:/uid.bin file.
|
|
||||||
|
|
||||||
Array16<byte> randomGuid = new();
|
|
||||||
|
|
||||||
Guid.NewGuid().ToByteArray().AsSpan().CopyTo(randomGuid.AsSpan());
|
|
||||||
|
|
||||||
PlayHistoryRegistrationKey playHistoryRegistrationKey = new()
|
|
||||||
{
|
|
||||||
Type = 0x101,
|
|
||||||
KeyIndex = (byte)(randomBytes[0] & 7),
|
|
||||||
UserIdBool = 0, // TODO: Find it.
|
|
||||||
UnknownBool = (byte)(unknownBool ? 1 : 0), // TODO: Find it.
|
|
||||||
Reserved = new Array11<byte>(),
|
|
||||||
Uuid = randomGuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> playHistoryRegistrationKeyBuffer = SpanHelpers.AsByteSpan(ref playHistoryRegistrationKey);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
NOTE: The service uses the KeyIndex to get a random key from a keys buffer (since the key index is stored in the returned buffer).
|
|
||||||
We currently don't support play history and online services so we can use a blank key for now.
|
|
||||||
Code for reference:
|
|
||||||
|
|
||||||
byte[] hmacKey = new byte[0x20];
|
|
||||||
|
|
||||||
HMACSHA256 hmacSha256 = new HMACSHA256(hmacKey);
|
|
||||||
byte[] hmacHash = hmacSha256.ComputeHash(playHistoryRegistrationKeyBuffer);
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
context.Memory.Write(bufferPosition, playHistoryRegistrationKeyBuffer);
|
|
||||||
context.Memory.Write(bufferPosition + 0x20, new byte[0x20]); // HmacHash
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(10702)]
|
|
||||||
// nn::friends::AddPlayHistory(nn::account::Uid, u64, pid, buffer<nn::friends::PlayHistoryRegistrationKey, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>, buffer<nn::friends::InAppScreenName, 0x19>)
|
|
||||||
public ResultCode AddPlayHistory(ServiceCtx context)
|
|
||||||
{
|
|
||||||
UserId userId = context.RequestData.ReadStruct<UserId>();
|
|
||||||
|
|
||||||
// Pid placeholder
|
|
||||||
context.RequestData.ReadInt64();
|
|
||||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
|
||||||
ulong pid = context.Request.HandleDesc.PId;
|
|
||||||
|
|
||||||
ulong playHistoryRegistrationKeyPosition = context.Request.PtrBuff[0].Position;
|
|
||||||
ulong playHistoryRegistrationKeySize = context.Request.PtrBuff[0].Size;
|
|
||||||
|
|
||||||
ulong inAppScreenName1Position = context.Request.PtrBuff[1].Position;
|
|
||||||
#pragma warning restore IDE0059
|
|
||||||
ulong inAppScreenName1Size = context.Request.PtrBuff[1].Size;
|
|
||||||
|
|
||||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
|
||||||
ulong inAppScreenName2Position = context.Request.PtrBuff[2].Position;
|
|
||||||
#pragma warning restore IDE0059
|
|
||||||
ulong inAppScreenName2Size = context.Request.PtrBuff[2].Size;
|
|
||||||
|
|
||||||
if (userId.IsNull || inAppScreenName1Size > 0x48 || inAppScreenName2Size > 0x48)
|
|
||||||
{
|
|
||||||
return ResultCode.InvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
|
||||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
|
||||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
|
||||||
#pragma warning restore IDE0059
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
NOTE: The service calls nn::friends::detail::service::core::PlayHistoryManager to store informations using the registration key computed in GetPlayHistoryRegistrationKey.
|
|
||||||
Then calls nn::friends::detail::service::core::FriendListManager to update informations on the friend list.
|
|
||||||
We currently don't support play history and online services so it's fine to do nothing.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
Logger.Stub?.PrintStub(LogClass.ServiceFriend);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,178 +0,0 @@
|
|||||||
using Ryujinx.Common;
|
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService;
|
|
||||||
using Ryujinx.Horizon.Common;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator
|
|
||||||
{
|
|
||||||
class INotificationService : DisposableIpcService
|
|
||||||
{
|
|
||||||
private readonly UserId _userId;
|
|
||||||
private readonly FriendServicePermissionLevel _permissionLevel;
|
|
||||||
|
|
||||||
private readonly object _lock = new();
|
|
||||||
|
|
||||||
private readonly KEvent _notificationEvent;
|
|
||||||
private int _notificationEventHandle = 0;
|
|
||||||
|
|
||||||
private readonly LinkedList<NotificationInfo> _notifications;
|
|
||||||
|
|
||||||
private bool _hasNewFriendRequest;
|
|
||||||
private bool _hasFriendListUpdate;
|
|
||||||
|
|
||||||
public INotificationService(ServiceCtx context, UserId userId, FriendServicePermissionLevel permissionLevel)
|
|
||||||
{
|
|
||||||
_userId = userId;
|
|
||||||
_permissionLevel = permissionLevel;
|
|
||||||
_notifications = new LinkedList<NotificationInfo>();
|
|
||||||
_notificationEvent = new KEvent(context.Device.System.KernelContext);
|
|
||||||
|
|
||||||
_hasNewFriendRequest = false;
|
|
||||||
_hasFriendListUpdate = false;
|
|
||||||
|
|
||||||
NotificationEventHandler.Instance.RegisterNotificationService(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(0)] //2.0.0+
|
|
||||||
// nn::friends::detail::ipc::INotificationService::GetEvent() -> handle<copy>
|
|
||||||
public ResultCode GetEvent(ServiceCtx context)
|
|
||||||
{
|
|
||||||
if (_notificationEventHandle == 0)
|
|
||||||
{
|
|
||||||
if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != Result.Success)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Out of handles!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_notificationEventHandle);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(1)] //2.0.0+
|
|
||||||
// nn::friends::detail::ipc::INotificationService::Clear()
|
|
||||||
public ResultCode Clear(ServiceCtx context)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_hasNewFriendRequest = false;
|
|
||||||
_hasFriendListUpdate = false;
|
|
||||||
|
|
||||||
_notifications.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
[CommandCmif(2)] // 2.0.0+
|
|
||||||
// nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo
|
|
||||||
public ResultCode Pop(ServiceCtx context)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_notifications.Count >= 1)
|
|
||||||
{
|
|
||||||
NotificationInfo notificationInfo = _notifications.First.Value;
|
|
||||||
_notifications.RemoveFirst();
|
|
||||||
|
|
||||||
if (notificationInfo.Type == NotificationEventType.FriendListUpdate)
|
|
||||||
{
|
|
||||||
_hasFriendListUpdate = false;
|
|
||||||
}
|
|
||||||
else if (notificationInfo.Type == NotificationEventType.NewFriendRequest)
|
|
||||||
{
|
|
||||||
_hasNewFriendRequest = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ResponseData.WriteStruct(notificationInfo);
|
|
||||||
|
|
||||||
return ResultCode.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultCode.NotificationQueueEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SignalFriendListUpdate(UserId targetId)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (_userId == targetId)
|
|
||||||
{
|
|
||||||
if (!_hasFriendListUpdate)
|
|
||||||
{
|
|
||||||
NotificationInfo friendListNotification = new();
|
|
||||||
|
|
||||||
if (_notifications.Count != 0)
|
|
||||||
{
|
|
||||||
friendListNotification = _notifications.First.Value;
|
|
||||||
_notifications.RemoveFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
friendListNotification.Type = NotificationEventType.FriendListUpdate;
|
|
||||||
_hasFriendListUpdate = true;
|
|
||||||
|
|
||||||
if (_hasNewFriendRequest)
|
|
||||||
{
|
|
||||||
NotificationInfo newFriendRequestNotification = new();
|
|
||||||
|
|
||||||
if (_notifications.Count != 0)
|
|
||||||
{
|
|
||||||
newFriendRequestNotification = _notifications.First.Value;
|
|
||||||
_notifications.RemoveFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest;
|
|
||||||
_notifications.AddFirst(newFriendRequestNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We defer this to make sure we are on top of the queue.
|
|
||||||
_notifications.AddFirst(friendListNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
_notificationEvent.ReadableEvent.Signal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SignalNewFriendRequest(UserId targetId)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if ((_permissionLevel & FriendServicePermissionLevel.ViewerMask) != 0 && _userId == targetId)
|
|
||||||
{
|
|
||||||
if (!_hasNewFriendRequest)
|
|
||||||
{
|
|
||||||
if (_notifications.Count == 100)
|
|
||||||
{
|
|
||||||
SignalFriendListUpdate(targetId);
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationInfo newFriendRequestNotification = new()
|
|
||||||
{
|
|
||||||
Type = NotificationEventType.NewFriendRequest,
|
|
||||||
};
|
|
||||||
|
|
||||||
_notifications.AddLast(newFriendRequestNotification);
|
|
||||||
_hasNewFriendRequest = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_notificationEvent.ReadableEvent.Signal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
if (isDisposing)
|
|
||||||
{
|
|
||||||
NotificationEventHandler.Instance.UnregisterNotificationService(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,74 +0,0 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
|
||||||
{
|
|
||||||
public sealed class NotificationEventHandler
|
|
||||||
{
|
|
||||||
private static NotificationEventHandler _instance;
|
|
||||||
private static readonly object _instanceLock = new();
|
|
||||||
|
|
||||||
private readonly INotificationService[] _registry;
|
|
||||||
|
|
||||||
public static NotificationEventHandler Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (_instanceLock)
|
|
||||||
{
|
|
||||||
_instance ??= new NotificationEventHandler();
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationEventHandler()
|
|
||||||
{
|
|
||||||
_registry = new INotificationService[0x20];
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void RegisterNotificationService(INotificationService service)
|
|
||||||
{
|
|
||||||
// NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors.
|
|
||||||
for (int i = 0; i < _registry.Length; i++)
|
|
||||||
{
|
|
||||||
if (_registry[i] == null)
|
|
||||||
{
|
|
||||||
_registry[i] = service;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void UnregisterNotificationService(INotificationService service)
|
|
||||||
{
|
|
||||||
// NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors.
|
|
||||||
for (int i = 0; i < _registry.Length; i++)
|
|
||||||
{
|
|
||||||
if (_registry[i] == service)
|
|
||||||
{
|
|
||||||
_registry[i] = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use this when we will have enough things to go online.
|
|
||||||
public void SignalFriendListUpdate(UserId targetId)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _registry.Length; i++)
|
|
||||||
{
|
|
||||||
_registry[i]?.SignalFriendListUpdate(targetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use this when we will have enough things to go online.
|
|
||||||
public void SignalNewFriendRequest(UserId targetId)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _registry.Length; i++)
|
|
||||||
{
|
|
||||||
_registry[i]?.SignalNewFriendRequest(targetId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
using Ryujinx.Common.Memory;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Friend.ServiceCreator.NotificationService
|
|
||||||
{
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
|
|
||||||
struct NotificationInfo
|
|
||||||
{
|
|
||||||
public NotificationEventType Type;
|
|
||||||
private Array4<byte> _padding;
|
|
||||||
public long NetworkUserIdPlaceholder;
|
|
||||||
}
|
|
||||||
}
|
|
65
src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs
Normal file
65
src/Ryujinx.HLE/HOS/Services/Fs/FileSystemProxy/LazyFile.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using LibHac;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||||
|
{
|
||||||
|
class LazyFile : LibHac.Fs.Fsa.IFile
|
||||||
|
{
|
||||||
|
private readonly LibHac.Fs.Fsa.IFileSystem _fs;
|
||||||
|
private readonly string _filePath;
|
||||||
|
private readonly UniqueRef<LibHac.Fs.Fsa.IFile> _fileReference = new();
|
||||||
|
private readonly FileInfo _fileInfo;
|
||||||
|
|
||||||
|
public LazyFile(string filePath, string prefix, LibHac.Fs.Fsa.IFileSystem fs)
|
||||||
|
{
|
||||||
|
_fs = fs;
|
||||||
|
_filePath = filePath;
|
||||||
|
_fileInfo = new FileInfo(prefix + "/" + filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrepareFile()
|
||||||
|
{
|
||||||
|
if (_fileReference.Get == null)
|
||||||
|
{
|
||||||
|
_fs.OpenFile(ref _fileReference.Ref, _filePath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
||||||
|
{
|
||||||
|
PrepareFile();
|
||||||
|
|
||||||
|
return _fileReference.Get!.Read(out bytesRead, offset, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoFlush()
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoSetSize(long size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoGetSize(out long size)
|
||||||
|
{
|
||||||
|
size = _fileInfo.Length;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -287,6 +287,10 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
_wakeEvent.WritableEvent.Clear();
|
_wakeEvent.WritableEvent.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (rc == KernelResult.PortRemoteClosed && signaledIndex >= 0 && SmObjectFactory != null)
|
||||||
|
{
|
||||||
|
DestroySession(handles[signaledIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
_selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||||
@@ -299,6 +303,16 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DestroySession(int serverSessionHandle)
|
||||||
|
{
|
||||||
|
_context.Syscall.CloseHandle(serverSessionHandle);
|
||||||
|
|
||||||
|
if (RemoveSessionObj(serverSessionHandle, out var session))
|
||||||
|
{
|
||||||
|
(session as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool Process(int serverSessionHandle, ulong recvListAddr)
|
private bool Process(int serverSessionHandle, ulong recvListAddr)
|
||||||
{
|
{
|
||||||
IpcMessage request = ReadRequest();
|
IpcMessage request = ReadRequest();
|
||||||
@@ -360,7 +374,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
response.RawData = _responseDataStream.ToArray();
|
response.RawData = _responseDataStream.ToArray();
|
||||||
}
|
}
|
||||||
else if (request.Type == IpcMessageType.CmifControl ||
|
else if (request.Type == IpcMessageType.CmifControl ||
|
||||||
request.Type == IpcMessageType.CmifControlWithContext)
|
request.Type == IpcMessageType.CmifControlWithContext)
|
||||||
{
|
{
|
||||||
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
#pragma warning disable IDE0059 // Remove unnecessary value assignment
|
||||||
uint magic = (uint)_requestDataReader.ReadUInt64();
|
uint magic = (uint)_requestDataReader.ReadUInt64();
|
||||||
@@ -412,11 +426,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
}
|
}
|
||||||
else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
else if (request.Type == IpcMessageType.CmifCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
||||||
{
|
{
|
||||||
_context.Syscall.CloseHandle(serverSessionHandle);
|
DestroySession(serverSessionHandle);
|
||||||
if (RemoveSessionObj(serverSessionHandle, out var session))
|
|
||||||
{
|
|
||||||
(session as IDisposable)?.Dispose();
|
|
||||||
}
|
|
||||||
shouldReply = false;
|
shouldReply = false;
|
||||||
}
|
}
|
||||||
// If the type is past 0xF, we are using TIPC
|
// If the type is past 0xF, we are using TIPC
|
||||||
|
@@ -96,6 +96,8 @@ namespace Ryujinx.Headless.SDL2.OpenGL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasContext() => SDL_GL_GetCurrentContext() != IntPtr.Zero;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
SDL_GL_DeleteContext(_context);
|
SDL_GL_DeleteContext(_context);
|
||||||
|
@@ -61,7 +61,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
Version = ReleaseInformation.GetVersion();
|
Version = ReleaseInformation.Version;
|
||||||
|
|
||||||
// Make process DPI aware for proper window sizing on high-res screens.
|
// Make process DPI aware for proper window sizing on high-res screens.
|
||||||
ForceDpiAware.Windows();
|
ForceDpiAware.Windows();
|
||||||
@@ -427,11 +427,26 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
if (!option.DisableFileLog)
|
if (!option.DisableFileLog)
|
||||||
{
|
{
|
||||||
Logger.AddTarget(new AsyncLogTargetWrapper(
|
FileStream logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs"));
|
||||||
new FileLogTarget(ReleaseInformation.GetBaseApplicationDirectory(), "file"),
|
|
||||||
1000,
|
if (logFile == null)
|
||||||
AsyncLogTargetOverflowAction.Block
|
{
|
||||||
));
|
logFile = FileLogTarget.PrepareLogFile(Path.Combine(AppDataManager.BaseDirPath, "Logs"));
|
||||||
|
|
||||||
|
if (logFile == null)
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Application, "No writable log directory available. Make sure either the application directory or the Ryujinx directory is writable.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logFile != null)
|
||||||
|
{
|
||||||
|
Logger.AddTarget(new AsyncLogTargetWrapper(
|
||||||
|
new FileLogTarget("file", logFile),
|
||||||
|
1000,
|
||||||
|
AsyncLogTargetOverflowAction.Block
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup graphics configuration
|
// Setup graphics configuration
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" />
|
<PackageReference Include="CommandLineParser" />
|
||||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
|
||||||
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
49
src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
Normal file
49
src/Ryujinx.Horizon/Friends/FriendsIpcServer.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Friends
|
||||||
|
{
|
||||||
|
class FriendsIpcServer
|
||||||
|
{
|
||||||
|
private const int MaxSessionsCount = 8;
|
||||||
|
private const int TotalMaxSessionsCount = MaxSessionsCount * 5;
|
||||||
|
|
||||||
|
private const int PointerBufferSize = 0xA00;
|
||||||
|
private const int MaxDomains = 64;
|
||||||
|
private const int MaxDomainObjects = 16;
|
||||||
|
private const int MaxPortsCount = 5;
|
||||||
|
|
||||||
|
private static readonly ManagerOptions _managerOptions = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
|
||||||
|
|
||||||
|
private SmApi _sm;
|
||||||
|
private FriendsServerManager _serverManager;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
HeapAllocator allocator = new();
|
||||||
|
|
||||||
|
_sm = new SmApi();
|
||||||
|
_sm.Initialize().AbortOnFailure();
|
||||||
|
|
||||||
|
_serverManager = new FriendsServerManager(allocator, _sm, MaxPortsCount, _managerOptions, TotalMaxSessionsCount);
|
||||||
|
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
|
_serverManager.RegisterServer((int)FriendsPortIndex.Admin, ServiceName.Encode("friend:a"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterServer((int)FriendsPortIndex.User, ServiceName.Encode("friend:u"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterServer((int)FriendsPortIndex.Viewer, ServiceName.Encode("friend:v"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterServer((int)FriendsPortIndex.Manager, ServiceName.Encode("friend:m"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterServer((int)FriendsPortIndex.System, ServiceName.Encode("friend:s"), MaxSessionsCount);
|
||||||
|
#pragma warning restore IDE0055
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ServiceRequests()
|
||||||
|
{
|
||||||
|
_serverManager.ServiceRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown()
|
||||||
|
{
|
||||||
|
_serverManager.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/Ryujinx.Horizon/Friends/FriendsMain.cs
Normal file
17
src/Ryujinx.Horizon/Friends/FriendsMain.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Ryujinx.Horizon.Friends
|
||||||
|
{
|
||||||
|
class FriendsMain : IService
|
||||||
|
{
|
||||||
|
public static void Main(ServiceTable serviceTable)
|
||||||
|
{
|
||||||
|
FriendsIpcServer ipcServer = new();
|
||||||
|
|
||||||
|
ipcServer.Initialize();
|
||||||
|
|
||||||
|
serviceTable.SignalServiceReady();
|
||||||
|
|
||||||
|
ipcServer.ServiceRequests();
|
||||||
|
ipcServer.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs
Normal file
11
src/Ryujinx.Horizon/Friends/FriendsPortIndex.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Ryujinx.Horizon.Friends
|
||||||
|
{
|
||||||
|
enum FriendsPortIndex
|
||||||
|
{
|
||||||
|
Admin,
|
||||||
|
User,
|
||||||
|
Viewer,
|
||||||
|
Manager,
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
}
|
36
src/Ryujinx.Horizon/Friends/FriendsServerManager.cs
Normal file
36
src/Ryujinx.Horizon/Friends/FriendsServerManager.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
|
using Ryujinx.Horizon.Sdk.Friends.Detail.Ipc;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Friends
|
||||||
|
{
|
||||||
|
class FriendsServerManager : ServerManager
|
||||||
|
{
|
||||||
|
private readonly IEmulatorAccountManager _accountManager;
|
||||||
|
private readonly NotificationEventHandler _notificationEventHandler;
|
||||||
|
|
||||||
|
public FriendsServerManager(HeapAllocator allocator, SmApi sm, int maxPorts, ManagerOptions options, int maxSessions) : base(allocator, sm, maxPorts, options, maxSessions)
|
||||||
|
{
|
||||||
|
_accountManager = HorizonStatic.Options.AccountManager;
|
||||||
|
_notificationEventHandler = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result OnNeedsToAccept(int portIndex, Server server)
|
||||||
|
{
|
||||||
|
return (FriendsPortIndex)portIndex switch
|
||||||
|
{
|
||||||
|
#pragma warning disable IDE0055 // Disable formatting
|
||||||
|
FriendsPortIndex.Admin => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Admin)),
|
||||||
|
FriendsPortIndex.User => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.User)),
|
||||||
|
FriendsPortIndex.Viewer => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Viewer)),
|
||||||
|
FriendsPortIndex.Manager => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.Manager)),
|
||||||
|
FriendsPortIndex.System => AcceptImpl(server, new ServiceCreator(_accountManager, _notificationEventHandler, FriendsServicePermissionLevel.System)),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(portIndex)),
|
||||||
|
#pragma warning restore IDE0055
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using LibHac;
|
using LibHac;
|
||||||
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
using Ryujinx.Horizon.Sdk.Fs;
|
using Ryujinx.Horizon.Sdk.Fs;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon
|
namespace Ryujinx.Horizon
|
||||||
@@ -10,13 +11,15 @@ namespace Ryujinx.Horizon
|
|||||||
|
|
||||||
public HorizonClient BcatClient { get; }
|
public HorizonClient BcatClient { get; }
|
||||||
public IFsClient FsClient { get; }
|
public IFsClient FsClient { get; }
|
||||||
|
public IEmulatorAccountManager AccountManager { get; }
|
||||||
|
|
||||||
public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient)
|
public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager)
|
||||||
{
|
{
|
||||||
IgnoreMissingServices = ignoreMissingServices;
|
IgnoreMissingServices = ignoreMissingServices;
|
||||||
ThrowOnInvalidCommandIds = true;
|
ThrowOnInvalidCommandIds = true;
|
||||||
BcatClient = bcatClient;
|
BcatClient = bcatClient;
|
||||||
FsClient = fsClient;
|
FsClient = fsClient;
|
||||||
|
AccountManager = accountManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Ryujinx.Horizon.Sdk.Account
|
||||||
|
{
|
||||||
|
public interface IEmulatorAccountManager
|
||||||
|
{
|
||||||
|
void OpenUserOnlinePlay(Uid userId);
|
||||||
|
void CloseUserOnlinePlay(Uid userId);
|
||||||
|
}
|
||||||
|
}
|
20
src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs
Normal file
20
src/Ryujinx.Horizon/Sdk/Account/NetworkServiceAccountId.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Account
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
|
||||||
|
readonly record struct NetworkServiceAccountId
|
||||||
|
{
|
||||||
|
public readonly ulong Id;
|
||||||
|
|
||||||
|
public NetworkServiceAccountId(ulong id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return Id.ToString("x16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/Ryujinx.Horizon/Sdk/Account/Nickname.cs
Normal file
29
src/Ryujinx.Horizon/Sdk/Account/Nickname.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Account
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x21, Pack = 0x1)]
|
||||||
|
readonly struct Nickname
|
||||||
|
{
|
||||||
|
public readonly Array33<byte> Name;
|
||||||
|
|
||||||
|
public Nickname(in Array33<byte> name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
int length = ((ReadOnlySpan<byte>)Name.AsSpan()).IndexOf((byte)0);
|
||||||
|
if (length < 0)
|
||||||
|
{
|
||||||
|
length = 33;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(Name.AsSpan()[..length]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -6,16 +6,16 @@ using System.Runtime.InteropServices;
|
|||||||
namespace Ryujinx.Horizon.Sdk.Account
|
namespace Ryujinx.Horizon.Sdk.Account
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
readonly record struct Uid
|
public readonly record struct Uid
|
||||||
{
|
{
|
||||||
public readonly long High;
|
public readonly ulong High;
|
||||||
public readonly long Low;
|
public readonly ulong Low;
|
||||||
|
|
||||||
public bool IsNull => (Low | High) == 0;
|
public bool IsNull => (Low | High) == 0;
|
||||||
|
|
||||||
public static Uid Null => new(0, 0);
|
public static Uid Null => new(0, 0);
|
||||||
|
|
||||||
public Uid(long low, long high)
|
public Uid(ulong low, ulong high)
|
||||||
{
|
{
|
||||||
Low = low;
|
Low = low;
|
||||||
High = high;
|
High = high;
|
||||||
@@ -23,8 +23,8 @@ namespace Ryujinx.Horizon.Sdk.Account
|
|||||||
|
|
||||||
public Uid(byte[] bytes)
|
public Uid(byte[] bytes)
|
||||||
{
|
{
|
||||||
High = BitConverter.ToInt64(bytes, 0);
|
High = BitConverter.ToUInt64(bytes, 0);
|
||||||
Low = BitConverter.ToInt64(bytes, 8);
|
Low = BitConverter.ToUInt64(bytes, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uid(string hex)
|
public Uid(string hex)
|
||||||
@@ -34,8 +34,8 @@ namespace Ryujinx.Horizon.Sdk.Account
|
|||||||
throw new ArgumentException("Invalid Hex value!", nameof(hex));
|
throw new ArgumentException("Invalid Hex value!", nameof(hex));
|
||||||
}
|
}
|
||||||
|
|
||||||
Low = Convert.ToInt64(hex[16..], 16);
|
Low = Convert.ToUInt64(hex[16..], 16);
|
||||||
High = Convert.ToInt64(hex[..16], 16);
|
High = Convert.ToUInt64(hex[..16], 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(BinaryWriter binaryWriter)
|
public void Write(BinaryWriter binaryWriter)
|
||||||
|
12
src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Friends/ApplicationInfo.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Ryujinx.Horizon.Sdk.Ncm;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
|
||||||
|
struct ApplicationInfo
|
||||||
|
{
|
||||||
|
public ApplicationId ApplicationId;
|
||||||
|
public ulong PresenceGroupId;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
struct BlockedUserImpl
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
struct FriendCandidateImpl
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x800)]
|
||||||
|
struct FriendDetailedInfoImpl
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
19
src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs
Normal file
19
src/Ryujinx.Horizon/Sdk/Friends/Detail/FriendImpl.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x200, Pack = 0x8)]
|
||||||
|
struct FriendImpl
|
||||||
|
{
|
||||||
|
public Uid UserId;
|
||||||
|
public NetworkServiceAccountId NetworkUserId;
|
||||||
|
public Nickname Nickname;
|
||||||
|
public UserPresenceImpl Presence;
|
||||||
|
public bool IsFavourite;
|
||||||
|
public bool IsNew;
|
||||||
|
public Array6<byte> Unknown;
|
||||||
|
public bool IsValid;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
struct FriendInvitationForViewerImpl
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Friends.Detail
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x1400)]
|
||||||
|
struct FriendInvitationGroupImpl
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user