Compare commits

...

19 Commits

Author SHA1 Message Date
TheToid
4831965404 Add ability to trim and untrim XCI files from the application context menu AND in Bulk (#105) 2024-11-06 17:37:30 -06:00
Luke Warner
47b8145809 Fix fullscreen when using 'Show Title Bar' (#150) 2024-11-06 17:36:30 -06:00
James Duarte
20cc21add6 fix minor grammatical issues in en_US.json (#183) 2024-11-06 17:36:02 -06:00
Evan Husted
683baec1af OOPSIE!!!!!!!!! 2024-11-06 17:04:20 -06:00
Evan Husted
f4957d2a09 Didn't realize you could compare tags and not just releases although that should have been obvious 2024-11-06 17:00:16 -06:00
Evan Husted
3e1182af22 Specify what is a canary tag 2024-11-06 16:55:17 -06:00
Evan Husted
6664ed1b11 Merge remote-tracking branch 'origin/master' 2024-11-06 16:48:33 -06:00
Evan Husted
0c88b9eff7 Canary & Release separation. 2024-11-06 16:48:20 -06:00
LotP1
8a064bcd7e Update README.md (#187) 2024-11-05 12:34:38 -06:00
Daniel Zauner
5ff962be37 fix index out of range check in GetCoefficientAtIndex (#164) 2024-11-03 13:58:27 -06:00
agnostk
d9c8b7d937 feat: add DebugMouse HID (#163) 2024-11-03 13:58:14 -06:00
Otozinclus
feeeafe8fe Update MacOS distribution .icns (#139) 2024-11-02 19:37:42 -05:00
Evan Husted
04f014c777 hotfix: Locale formatting 2024-11-01 16:17:08 -05:00
Evan Husted
4a677deb50 infra: Add build/release workflows to solution items, remove jitsupport dylib from linux & windows, pack native libraries into Ryujinx executable. 2024-11-01 16:04:32 -05:00
Evan Husted
1c07bf3dd0 misc: Abstract repeated logic in markup extensions & move Updater into the base of the Avalonia project. 2024-11-01 15:48:25 -05:00
Evan Husted
4c83794254 Avalonia: Move LocaleExtension & IconExtension into one namespace to simplify the usage sites in the markup. 2024-11-01 13:00:56 -05:00
Evan Husted
6911e288bc misc: Code cleanups. 2024-11-01 12:00:07 -05:00
Evan Husted
67ab54e2bb misc: Remove custom themes in config. 2024-11-01 11:58:58 -05:00
Evan Husted
139c195eb7 misc: Replace "" with string.Empty. 2024-11-01 11:57:23 -05:00
137 changed files with 4891 additions and 2456 deletions

View File

@@ -61,11 +61,11 @@ jobs:
if: matrix.platform.name != 'linux-arm64' if: matrix.platform.name != 'linux-arm64'
- name: Publish Ryujinx - name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Headless.SDL2 - name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_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
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit - name: Set executable bit

254
.github/workflows/canary.yml vendored Normal file
View File

@@ -0,0 +1,254 @@
name: Canary release job
on:
workflow_dispatch:
inputs: {}
push:
branches: [ master ]
paths-ignore:
- '.github/**'
- 'docs/**'
- 'assets/**'
- '*.yml'
- '*.json'
- '*.config'
- '*.md'
concurrency: release
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "canary"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx-Canary"
RELEASE: 1
jobs:
tag:
name: Create tag
runs-on: ubuntu-20.04
steps:
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
shell: bash
- name: Create tag
uses: actions/github-script@v7
with:
script: |
github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/tags/Canary-${{ steps.version_info.outputs.build_version }}',
sha: context.sha
})
- name: Create release
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
tag: ${{ steps.version_info.outputs.build_version }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release:
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
run: "mkdir release_output"
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained -p:IncludeNativeLibrariesForSelfExtract=true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained -p:IncludeNativeLibrariesForSelfExtract=true
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish_ava
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash
- name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish_ava
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash
#- name: Build AppImage (Linux)
# if: matrix.platform.os == 'ubuntu-latest'
# run: |
# BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
# PLATFORM_NAME="${{ matrix.platform.name }}"
# sudo apt install -y zsync desktop-file-utils appstream
# mkdir -p tools
# export PATH="$PATH:$(readlink -f tools)"
# Setup appimagetool
# wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
# chmod +x tools/appimagetool
# chmod +x distribution/linux/appimage/build-appimage.sh
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
# if [ "$PLATFORM_NAME" = "linux-x64" ]; then
# ARCH_NAME=x64
# export ARCH=x86_64
# elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
# ARCH_NAME=arm64
# export ARCH=aarch64
# else
# echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
# exit 1
# fi
# export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
# BUILDDIR=publish_ava OUTDIR=publish_ava_appimage distribution/linux/appimage/build-appimage.sh
# Add to release output
# pushd publish_ava_appimage
# mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
# mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
# popd
# shell: bash
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip"
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Setup LLVM 15
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
- name: Install rcodesign
run: |
mkdir -p $HOME/.bin
gh release download -R indygreg/apple-platform-rs -O apple-codesign.tar.gz -p 'apple-codesign-*-x86_64-unknown-linux-musl.tar.gz'
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
rm apple-codesign.tar.gz
mv rcodesign $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Configure for release
run: |
sed -r --in-place 's/\%\%RYUJINX_BUILD_VERSION\%\%/${{ steps.version_info.outputs.build_version }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Pushing new release
uses: ncipollo/release-action@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: {} inputs: {}
push: push:
branches: [ master ] branches: [ release ]
paths-ignore: paths-ignore:
- '.github/**' - '.github/**'
- 'docs/**' - 'docs/**'
@@ -20,7 +20,7 @@ env:
POWERSHELL_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2" RYUJINX_BASE_VERSION: "1.2"
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master" RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "release"
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev" RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "GreemDev"
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx" RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "Ryujinx"
RELEASE: 1 RELEASE: 1
@@ -101,17 +101,19 @@ jobs:
- name: Publish - name: Publish
run: | run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained -p:IncludeNativeLibrariesForSelfExtract=true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained 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 -p:IncludeNativeLibrariesForSelfExtract=true
- name: Packing Windows builds - name: Packing Windows builds
if: matrix.platform.os == 'windows-latest' if: matrix.platform.os == 'windows-latest'
run: | run: |
pushd publish_ava pushd publish_ava
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
pushd publish_sdl2_headless pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
shell: bash shell: bash
@@ -120,11 +122,13 @@ jobs:
if: matrix.platform.os == 'ubuntu-latest' if: matrix.platform.os == 'ubuntu-latest'
run: | run: |
pushd publish_ava pushd publish_ava
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd
pushd publish_sdl2_headless pushd publish_sdl2_headless
rm publish/libarmeilleure-jitsupport.dylib
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2 chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd

View File

@@ -61,9 +61,9 @@ Use the search function to see if a game has been tested already!
To run this emulator, your PC must be equipped with at least 8GiB of RAM; To run this emulator, your PC must be equipped with at least 8GiB of RAM;
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes. failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
## Latest build ## Latest release
These builds are compiled automatically for each commit on the master branch. Releases are compiled automatically for each commit on the master branch.
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
You can find the latest release [here](https://github.com/GreemDev/Ryujinx/releases/latest). You can find the latest release [here](https://github.com/GreemDev/Ryujinx/releases/latest).
@@ -74,6 +74,7 @@ If you are planning to contribute or just want to learn more about this project
## Building ## Building
Building the project is for advanced users.
If you wish to build the emulator yourself, follow these steps: If you wish to build the emulator yourself, follow these steps:
### Step 1 ### Step 1

View File

@@ -29,12 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "s
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "src\Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Memory", "src\Ryujinx.Memory\Ryujinx.Memory.csproj", "{A5E6C691-9E22-4263-8F40-42F002CE66BE}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Memory", "src\Ryujinx.Tests.Memory\Ryujinx.Tests.Memory.csproj", "{D1CC5322-7325-4F6B-9625-194B30BE1296}"
@@ -87,6 +81,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Gene
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
Directory.Packages.props = Directory.Packages.props
.github/workflows/release.yml = .github/workflows/release.yml
.github/workflows/canary.yml = .github/workflows/canary.yml
.github/workflows/build.yml = .github/workflows/build.yml
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

Binary file not shown.

View File

@@ -855,6 +855,7 @@ namespace ARMeilleure.Translation.PTC
Thread thread = new(TranslateFuncs) Thread thread = new(TranslateFuncs)
{ {
IsBackground = true, IsBackground = true,
Name = "Ptc.TranslateThread." + i
}; };
threads.Add(thread); threads.Add(thread);
@@ -885,6 +886,7 @@ namespace ARMeilleure.Translation.PTC
Thread preSaveThread = new(PreSave) Thread preSaveThread = new(PreSave)
{ {
IsBackground = true, IsBackground = true,
Name = "Ptc.DiskWriter"
}; };
preSaveThread.Start(); preSaveThread.Start();
} }

View File

@@ -41,7 +41,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
public OpenALHardwareDeviceDriver() public OpenALHardwareDeviceDriver()
{ {
_device = ALC.OpenDevice(""); _device = ALC.OpenDevice(string.Empty);
_context = ALC.CreateContext(_device, new ALContextAttributes()); _context = ALC.CreateContext(_device, new ALContextAttributes());
_updateRequiredEvent = new ManualResetEvent(false); _updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true); _pauseEvent = new ManualResetEvent(true);

View File

@@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index) private static short GetCoefficientAtIndex(ReadOnlySpan<short> coefficients, int index)
{ {
if ((uint)index > (uint)coefficients.Length) if ((uint)index >= (uint)coefficients.Length)
{ {
Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}"); Logger.Error?.Print(LogClass.AudioRenderer, $"Out of bound read for coefficient at index {index}");

View File

@@ -119,7 +119,7 @@ namespace Ryujinx.Common.Configuration
private static string SetUpLogsDir() private static string SetUpLogsDir()
{ {
string logDir = ""; string logDir = string.Empty;
if (Mode == LaunchMode.Portable) if (Mode == LaunchMode.Portable)
{ {
@@ -148,7 +148,7 @@ namespace Ryujinx.Common.Configuration
catch catch
{ {
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = ""; logDir = string.Empty;
} }
if (string.IsNullOrEmpty(logDir)) if (string.IsNullOrEmpty(logDir))
@@ -179,7 +179,7 @@ namespace Ryujinx.Common.Configuration
catch catch
{ {
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'"); Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}'");
logDir = ""; logDir = string.Empty;
} }
if (string.IsNullOrEmpty(logDir)) if (string.IsNullOrEmpty(logDir))

View File

@@ -121,8 +121,8 @@ namespace Ryujinx.Common.GraphicsDriver
}; };
application.AppName.Set("Ryujinx.exe"); application.AppName.Set("Ryujinx.exe");
application.UserFriendlyName.Set("Ryujinx"); application.UserFriendlyName.Set("Ryujinx");
application.Launcher.Set(""); application.Launcher.Set(string.Empty);
application.FileInFolder.Set(""); application.FileInFolder.Set(string.Empty);
Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application)); Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application));
} }

View File

@@ -72,5 +72,6 @@ namespace Ryujinx.Common.Logging
TamperMachine, TamperMachine,
UI, UI,
Vic, Vic,
XCIFileTrimmer
} }
} }

View File

@@ -38,7 +38,7 @@ namespace Ryujinx.Common.Logging
{ {
if (_enabledClasses[(int)logClass]) if (_enabledClasses[(int)logClass])
{ {
Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message))); Updated?.Invoke(null, new LogEventArgs(Level, _time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, string.Empty, message)));
} }
} }

View File

@@ -0,0 +1,30 @@
using Ryujinx.Common.Utilities;
namespace Ryujinx.Common.Logging
{
public class XCIFileTrimmerLog : XCIFileTrimmer.ILog
{
public virtual void Progress(long current, long total, string text, bool complete)
{
}
public void Write(XCIFileTrimmer.LogType logType, string text)
{
switch (logType)
{
case XCIFileTrimmer.LogType.Info:
Logger.Notice.Print(LogClass.XCIFileTrimmer, text);
break;
case XCIFileTrimmer.LogType.Warn:
Logger.Warning?.Print(LogClass.XCIFileTrimmer, text);
break;
case XCIFileTrimmer.LogType.Error:
Logger.Error?.Print(LogClass.XCIFileTrimmer, text);
break;
case XCIFileTrimmer.LogType.Progress:
Logger.Info?.Print(LogClass.XCIFileTrimmer, text);
break;
}
}
}
}

View File

@@ -5,7 +5,9 @@ namespace Ryujinx.Common
// DO NOT EDIT, filled by CI // DO NOT EDIT, filled by CI
public static class ReleaseInformation public static class ReleaseInformation
{ {
private const string FlatHubChannelOwner = "flathub"; private const string FlatHubChannel = "flathub";
private const string CanaryChannel = "canary";
private const string ReleaseChannel = "release";
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%"; private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%"; public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
@@ -24,7 +26,11 @@ namespace Ryujinx.Common
!ReleaseChannelRepo.StartsWith("%%") && !ReleaseChannelRepo.StartsWith("%%") &&
!ConfigFileName.StartsWith("%%"); !ConfigFileName.StartsWith("%%");
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner); public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannel);
public static bool IsCanaryBuild => IsValid && ReleaseChannelOwner.Equals(CanaryChannel);
public static bool IsReleaseBuild => IsValid && ReleaseChannelOwner.Equals(ReleaseChannel);
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion; public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
} }

View File

@@ -10,6 +10,7 @@
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
<PackageReference Include="MsgPack.Cli" /> <PackageReference Include="MsgPack.Cli" />
<PackageReference Include="System.Management" /> <PackageReference Include="System.Management" />
<PackageReference Include="Humanizer" />
<PackageReference Include="Gommon" /> <PackageReference Include="Gommon" />
</ItemGroup> </ItemGroup>

View File

@@ -17,29 +17,19 @@ namespace Ryujinx.Common.Utilities
/// It is REQUIRED for you to save returned options statically or as a part of static serializer context /// It is REQUIRED for you to save returned options statically or as a part of static serializer context
/// in order to avoid performance issues. You can safely modify returned options for your case before storing. /// in order to avoid performance issues. You can safely modify returned options for your case before storing.
/// </remarks> /// </remarks>
public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) public static JsonSerializerOptions GetDefaultSerializerOptions(bool indented = true) =>
{ new()
JsonSerializerOptions options = new()
{ {
DictionaryKeyPolicy = _snakeCasePolicy, DictionaryKeyPolicy = _snakeCasePolicy,
PropertyNamingPolicy = _snakeCasePolicy, PropertyNamingPolicy = _snakeCasePolicy,
WriteIndented = indented, WriteIndented = indented,
AllowTrailingCommas = true, AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip, ReadCommentHandling = JsonCommentHandling.Skip
}; };
return options; public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo) => JsonSerializer.Serialize(value, typeInfo);
}
public static string Serialize<T>(T value, JsonTypeInfo<T> typeInfo) public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo) => JsonSerializer.Deserialize(value, typeInfo);
{
return JsonSerializer.Serialize(value, typeInfo);
}
public static T Deserialize<T>(string value, JsonTypeInfo<T> typeInfo)
{
return JsonSerializer.Deserialize(value, typeInfo);
}
public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo) public static void SerializeToFile<T>(string filePath, T value, JsonTypeInfo<T> typeInfo)
{ {
@@ -53,10 +43,7 @@ namespace Ryujinx.Common.Utilities
return JsonSerializer.Deserialize(file, typeInfo); return JsonSerializer.Deserialize(file, typeInfo);
} }
public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo) public static void SerializeToStream<T>(Stream stream, T value, JsonTypeInfo<T> typeInfo) => JsonSerializer.Serialize(stream, value, typeInfo);
{
JsonSerializer.Serialize(stream, value, typeInfo);
}
private class SnakeCaseNamingPolicy : JsonNamingPolicy private class SnakeCaseNamingPolicy : JsonNamingPolicy
{ {

View File

@@ -0,0 +1,524 @@
// Uncomment the line below to ensure XCIFileTrimmer does not modify files
//#define XCI_TRIMMER_READ_ONLY_MODE
using Gommon;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace Ryujinx.Common.Utilities
{
public sealed class XCIFileTrimmer
{
private const long BytesInAMegabyte = 1024 * 1024;
private const int BufferSize = 8 * (int)BytesInAMegabyte;
private const long CartSizeMBinFormattedGB = 952;
private const int CartKeyAreaSize = 0x1000;
private const byte PaddingByte = 0xFF;
private const int HeaderFilePos = 0x100;
private const int CartSizeFilePos = 0x10D;
private const int DataSizeFilePos = 0x118;
private const string HeaderMagicValue = "HEAD";
/// <summary>
/// Cartridge Sizes (ByteIdentifier, SizeInGB)
/// </summary>
private static readonly Dictionary<byte, long> _cartSizesGB = new()
{
{ 0xFA, 1 },
{ 0xF8, 2 },
{ 0xF0, 4 },
{ 0xE0, 8 },
{ 0xE1, 16 },
{ 0xE2, 32 }
};
private static long RecordsToByte(long records)
{
return 512 + (records * 512);
}
public static bool CanTrim(string filename, ILog log = null)
{
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
{
var trimmer = new XCIFileTrimmer(filename, log);
return trimmer.CanBeTrimmed;
}
return false;
}
public static bool CanUntrim(string filename, ILog log = null)
{
if (Path.GetExtension(filename).Equals(".XCI", StringComparison.InvariantCultureIgnoreCase))
{
var trimmer = new XCIFileTrimmer(filename, log);
return trimmer.CanBeUntrimmed;
}
return false;
}
private ILog _log;
private string _filename;
private FileStream _fileStream;
private BinaryReader _binaryReader;
private long _offsetB, _dataSizeB, _cartSizeB, _fileSizeB;
private bool _fileOK = true;
private bool _freeSpaceChecked = false;
private bool _freeSpaceValid = false;
public enum OperationOutcome
{
Undetermined,
InvalidXCIFile,
NoTrimNecessary,
NoUntrimPossible,
FreeSpaceCheckFailed,
FileIOWriteError,
ReadOnlyFileCannotFix,
FileSizeChanged,
Successful,
Cancelled
}
public enum LogType
{
Info,
Warn,
Error,
Progress
}
public interface ILog
{
public void Write(LogType logType, string text);
public void Progress(long current, long total, string text, bool complete);
}
public bool FileOK => _fileOK;
public bool Trimmed => _fileOK && FileSizeB < UntrimmedFileSizeB;
public bool ContainsKeyArea => _offsetB != 0;
public bool CanBeTrimmed => _fileOK && FileSizeB > TrimmedFileSizeB;
public bool CanBeUntrimmed => _fileOK && FileSizeB < UntrimmedFileSizeB;
public bool FreeSpaceChecked => _fileOK && _freeSpaceChecked;
public bool FreeSpaceValid => _fileOK && _freeSpaceValid;
public long DataSizeB => _dataSizeB;
public long CartSizeB => _cartSizeB;
public long FileSizeB => _fileSizeB;
public long DiskSpaceSavedB => CartSizeB - FileSizeB;
public long DiskSpaceSavingsB => CartSizeB - DataSizeB;
public long TrimmedFileSizeB => _offsetB + _dataSizeB;
public long UntrimmedFileSizeB => _offsetB + _cartSizeB;
public ILog Log
{
get => _log;
set => _log = value;
}
public String Filename
{
get => _filename;
set
{
_filename = value;
Reset();
}
}
public long Pos
{
get => _fileStream.Position;
set => _fileStream.Position = value;
}
public XCIFileTrimmer(string path, ILog log = null)
{
Log = log;
Filename = path;
ReadHeader();
}
public void CheckFreeSpace(CancellationToken? cancelToken = null)
{
if (FreeSpaceChecked)
return;
try
{
if (CanBeTrimmed)
{
_freeSpaceValid = false;
OpenReaders();
try
{
Pos = TrimmedFileSizeB;
bool freeSpaceValid = true;
long readSizeB = FileSizeB - TrimmedFileSizeB;
Stopwatch timedSw = Lambda.Timed(() =>
{
freeSpaceValid = CheckPadding(readSizeB, cancelToken);
});
if (timedSw.Elapsed.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Checked at {readSizeB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
}
if (freeSpaceValid)
Log?.Write(LogType.Info, "Free space is valid");
_freeSpaceValid = freeSpaceValid;
}
finally
{
CloseReaders();
}
}
else
{
Log?.Write(LogType.Warn, "There is no free space to check.");
_freeSpaceValid = false;
}
}
finally
{
_freeSpaceChecked = true;
}
}
private bool CheckPadding(long readSizeB, CancellationToken? cancelToken = null)
{
long maxReads = readSizeB / XCIFileTrimmer.BufferSize;
long read = 0;
var buffer = new byte[BufferSize];
while (true)
{
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return false;
}
int bytes = _fileStream.Read(buffer, 0, XCIFileTrimmer.BufferSize);
if (bytes == 0)
break;
Log?.Progress(read, maxReads, "Verifying file can be trimmed", false);
if (buffer.Take(bytes).AsParallel().Any(b => b != XCIFileTrimmer.PaddingByte))
{
Log?.Write(LogType.Warn, "Free space is NOT valid");
return false;
}
read++;
}
return true;
}
private void Reset()
{
_freeSpaceChecked = false;
_freeSpaceValid = false;
ReadHeader();
}
public OperationOutcome Trim(CancellationToken? cancelToken = null)
{
if (!FileOK)
{
return OperationOutcome.InvalidXCIFile;
}
if (!CanBeTrimmed)
{
return OperationOutcome.NoTrimNecessary;
}
if (!FreeSpaceChecked)
{
CheckFreeSpace(cancelToken);
}
if (!FreeSpaceValid)
{
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return OperationOutcome.Cancelled;
}
else
{
return OperationOutcome.FreeSpaceCheckFailed;
}
}
Log?.Write(LogType.Info, "Trimming...");
try
{
var info = new FileInfo(Filename);
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
{
Log?.Write(LogType.Info, "Attempting to remove ReadOnly attribute");
File.SetAttributes(Filename, info.Attributes & ~FileAttributes.ReadOnly);
}
catch (Exception e)
{
Log?.Write(LogType.Error, e.ToString());
return OperationOutcome.ReadOnlyFileCannotFix;
}
}
if (info.Length != FileSizeB)
{
Log?.Write(LogType.Error, "File size has changed, cannot safely trim.");
return OperationOutcome.FileSizeChanged;
}
var outfileStream = new FileStream(_filename, FileMode.Open, FileAccess.Write, FileShare.Write);
try
{
#if !XCI_TRIMMER_READ_ONLY_MODE
outfileStream.SetLength(TrimmedFileSizeB);
#endif
return OperationOutcome.Successful;
}
finally
{
outfileStream.Close();
Reset();
}
}
catch (Exception e)
{
Log?.Write(LogType.Error, e.ToString());
return OperationOutcome.FileIOWriteError;
}
}
public OperationOutcome Untrim(CancellationToken? cancelToken = null)
{
if (!FileOK)
{
return OperationOutcome.InvalidXCIFile;
}
if (!CanBeUntrimmed)
{
return OperationOutcome.NoUntrimPossible;
}
try
{
Log?.Write(LogType.Info, "Untrimming...");
var info = new FileInfo(Filename);
if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
{
try
{
Log?.Write(LogType.Info, "Attempting to remove ReadOnly attribute");
File.SetAttributes(Filename, info.Attributes & ~FileAttributes.ReadOnly);
}
catch (Exception e)
{
Log?.Write(LogType.Error, e.ToString());
return OperationOutcome.ReadOnlyFileCannotFix;
}
}
if (info.Length != FileSizeB)
{
Log?.Write(LogType.Error, "File size has changed, cannot safely untrim.");
return OperationOutcome.FileSizeChanged;
}
var outfileStream = new FileStream(_filename, FileMode.Append, FileAccess.Write, FileShare.Write);
long bytesToWriteB = UntrimmedFileSizeB - FileSizeB;
try
{
Stopwatch timedSw = Lambda.Timed(() =>
{
WritePadding(outfileStream, bytesToWriteB, cancelToken);
});
if (timedSw.Elapsed.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Wrote at {bytesToWriteB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
}
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return OperationOutcome.Cancelled;
}
else
{
return OperationOutcome.Successful;
}
}
finally
{
outfileStream.Close();
Reset();
}
}
catch (Exception e)
{
Log?.Write(LogType.Error, e.ToString());
return OperationOutcome.FileIOWriteError;
}
}
private void WritePadding(FileStream outfileStream, long bytesToWriteB, CancellationToken? cancelToken = null)
{
long bytesLeftToWriteB = bytesToWriteB;
long writes = bytesLeftToWriteB / XCIFileTrimmer.BufferSize;
int write = 0;
try
{
var buffer = new byte[BufferSize];
Array.Fill<byte>(buffer, XCIFileTrimmer.PaddingByte);
while (bytesLeftToWriteB > 0)
{
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return;
}
long bytesToWrite = Math.Min(XCIFileTrimmer.BufferSize, bytesLeftToWriteB);
#if !XCI_TRIMMER_READ_ONLY_MODE
outfileStream.Write(buffer, 0, (int)bytesToWrite);
#endif
bytesLeftToWriteB -= bytesToWrite;
Log?.Progress(write, writes, "Writing padding data...", false);
write++;
}
}
finally
{
Log?.Progress(write, writes, "Writing padding data...", true);
}
}
private void OpenReaders()
{
if (_binaryReader == null)
{
_fileStream = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.Read);
_binaryReader = new BinaryReader(_fileStream);
}
}
private void CloseReaders()
{
if (_binaryReader != null && _binaryReader.BaseStream != null)
_binaryReader.Close();
_binaryReader = null;
_fileStream = null;
GC.Collect();
}
private void ReadHeader()
{
try
{
OpenReaders();
try
{
// Attempt without key area
bool success = CheckAndReadHeader(false);
if (!success)
{
// Attempt with key area
success = CheckAndReadHeader(true);
}
_fileOK = success;
}
finally
{
CloseReaders();
}
}
catch (Exception ex)
{
Log?.Write(LogType.Error, ex.Message);
_fileOK = false;
_dataSizeB = 0;
_cartSizeB = 0;
_fileSizeB = 0;
_offsetB = 0;
}
}
private bool CheckAndReadHeader(bool assumeKeyArea)
{
// Read file size
_fileSizeB = _fileStream.Length;
if (_fileSizeB < 32 * 1024)
{
Log?.Write(LogType.Error, "The source file doesn't look like an XCI file as the data size is too small");
return false;
}
// Setup offset
_offsetB = (long)(assumeKeyArea ? XCIFileTrimmer.CartKeyAreaSize : 0);
// Check header
Pos = _offsetB + XCIFileTrimmer.HeaderFilePos;
string head = System.Text.Encoding.ASCII.GetString(_binaryReader.ReadBytes(4));
if (head != XCIFileTrimmer.HeaderMagicValue)
{
if (!assumeKeyArea)
{
Log?.Write(LogType.Warn, $"Incorrect header found, file mat contain a key area...");
}
else
{
Log?.Write(LogType.Error, "The source file doesn't look like an XCI file as the header is corrupted");
}
return false;
}
// Read Cart Size
Pos = _offsetB + XCIFileTrimmer.CartSizeFilePos;
byte cartSizeId = _binaryReader.ReadByte();
if (!_cartSizesGB.TryGetValue(cartSizeId, out long cartSizeNGB))
{
Log?.Write(LogType.Error, $"The source file doesn't look like an XCI file as the Cartridge Size is incorrect (0x{cartSizeId:X2})");
return false;
}
_cartSizeB = cartSizeNGB * XCIFileTrimmer.CartSizeMBinFormattedGB * XCIFileTrimmer.BytesInAMegabyte;
// Read data size
Pos = _offsetB + XCIFileTrimmer.DataSizeFilePos;
long records = (long)BitConverter.ToUInt32(_binaryReader.ReadBytes(4), 0);
_dataSizeB = RecordsToByte(records);
return true;
}
}
}

View File

@@ -432,7 +432,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
bool colorIsVector = isGather || !isShadow; bool colorIsVector = isGather || !isShadow;
texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : ""); texCall += ")" + (colorIsVector ? GetMaskMultiDest(texOp.Index) : string.Empty);
return texCall; return texCall;
} }

View File

@@ -830,12 +830,12 @@ namespace Ryujinx.Graphics.Shader.Translation
if (use.Node != null) if (use.Node != null)
{ {
Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index})"); Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : string.Empty)}{use.Index})");
PrintTreeNode(use.Node, indentation + (last ? " " : " | ")); PrintTreeNode(use.Node, indentation + (last ? " " : " | "));
} }
else else
{ {
Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index}) NULL"); Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : string.Empty)}{use.Index}) NULL");
} }
} }
} }
@@ -852,12 +852,12 @@ namespace Ryujinx.Graphics.Shader.Translation
if (use.Node != null) if (use.Node != null)
{ {
Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index})"); Console.Write($"{indentation} {separator}- ({(use.Inverted ? "INV " : string.Empty)}{use.Index})");
PrintTreeNode(use.Node, indentation + (last ? " " : " | ")); PrintTreeNode(use.Node, indentation + (last ? " " : " | "));
} }
else else
{ {
Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : "")}{use.Index}) NULL"); Console.WriteLine($"{indentation} {separator}- ({(use.Inverted ? "INV " : string.Empty)}{use.Index}) NULL");
} }
} }
} }

View File

@@ -104,25 +104,27 @@ namespace Ryujinx.Graphics.Vulkan
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured; public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
public VulkanRenderer(Vk api, Func<Instance, Vk, SurfaceKHR> surfaceFunc, Func<string[]> requiredExtensionsFunc, string preferredGpuId) public VulkanRenderer(Vk api, Func<Instance, Vk, SurfaceKHR> getSurface, Func<string[]> requiredExtensionsFunc, string preferredGpuId)
{ {
_getSurface = surfaceFunc; _getSurface = getSurface;
_getRequiredExtensions = requiredExtensionsFunc; _getRequiredExtensions = requiredExtensionsFunc;
_preferredGpuId = preferredGpuId; _preferredGpuId = preferredGpuId;
Api = api; Api = api;
Shaders = new HashSet<ShaderCollection>(); Shaders = [];
Textures = new HashSet<ITexture>(); Textures = [];
Samplers = new HashSet<SamplerHolder>(); Samplers = [];
if (OperatingSystem.IsMacOS()) // Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
{ if (IsMoltenVk = OperatingSystem.IsMacOS())
MVKInitialization.Initialize(); MVKInitialization.Initialize();
// Any device running on MacOS is using MoltenVK, even Intel and AMD vendors.
IsMoltenVk = true;
}
} }
public static VulkanRenderer Create(
string preferredGpuId,
Func<Instance, Vk, SurfaceKHR> getSurface,
Func<string[]> getRequiredExtensions
) => new(Vk.GetApi(), getSurface, getRequiredExtensions, preferredGpuId);
private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex) private unsafe void LoadFeatures(uint maxQueueCount, uint queueFamilyIndex)
{ {
FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice); FormatCapabilities = new FormatCapabilities(Api, _physicalDevice.PhysicalDevice);

View File

@@ -13,6 +13,7 @@ namespace Ryujinx.HLE.Generators
var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver; var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver;
CodeGenerator generator = new CodeGenerator(); CodeGenerator generator = new CodeGenerator();
generator.AppendLine("#nullable enable");
generator.AppendLine("using System;"); generator.AppendLine("using System;");
generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm"); generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm");
generator.EnterScope($"partial class IUserInterface"); generator.EnterScope($"partial class IUserInterface");
@@ -22,7 +23,7 @@ namespace Ryujinx.HLE.Generators
{ {
if (className.Modifiers.Any(SyntaxKind.AbstractKeyword) || className.Modifiers.Any(SyntaxKind.PrivateKeyword) || !className.AttributeLists.Any(x => x.Attributes.Any(y => y.ToString().StartsWith("Service")))) if (className.Modifiers.Any(SyntaxKind.AbstractKeyword) || className.Modifiers.Any(SyntaxKind.PrivateKeyword) || !className.AttributeLists.Any(x => x.Attributes.Any(y => y.ToString().StartsWith("Service"))))
continue; continue;
var name = GetFullName(className, context).Replace("global::", ""); var name = GetFullName(className, context).Replace("global::", string.Empty);
if (!name.StartsWith("Ryujinx.HLE.HOS.Services")) if (!name.StartsWith("Ryujinx.HLE.HOS.Services"))
continue; continue;
var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax).ToArray(); var constructors = className.ChildNodes().Where(x => x.IsKind(SyntaxKind.ConstructorDeclaration)).Select(y => y as ConstructorDeclarationSyntax).ToArray();
@@ -58,6 +59,7 @@ namespace Ryujinx.HLE.Generators
generator.LeaveScope(); generator.LeaveScope();
generator.LeaveScope(); generator.LeaveScope();
generator.AppendLine("#nullable disable");
context.AddSource($"IUserInterface.g.cs", generator.ToString()); context.AddSource($"IUserInterface.g.cs", generator.ToString());
} }

View File

@@ -523,7 +523,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
// Clean up the name and get the NcaId // Clean up the name and get the NcaId
string[] pathComponents = entry.FullName.Replace(".cnmt", "").Split('/'); string[] pathComponents = entry.FullName.Replace(".cnmt", string.Empty).Split('/');
string ncaId = pathComponents[^1]; string ncaId = pathComponents[^1];

View File

@@ -132,7 +132,7 @@ namespace Ryujinx.HLE.FileSystem
if (systemPath.StartsWith(baseSystemPath)) if (systemPath.StartsWith(baseSystemPath))
{ {
string rawPath = systemPath.Replace(baseSystemPath, ""); string rawPath = systemPath.Replace(baseSystemPath, string.Empty);
int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar); int firstSeparatorOffset = rawPath.IndexOf(Path.DirectorySeparatorChar);
if (firstSeparatorOffset == -1) if (firstSeparatorOffset == -1)

View File

@@ -107,7 +107,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
private static string CleanText(string value) private static string CleanText(string value)
{ {
return CleanTextRegex().Replace(value, "").Replace("\0", ""); return CleanTextRegex().Replace(value, string.Empty).Replace("\0", string.Empty);
} }
private string GetMessageText(uint module, uint description, string key) private string GetMessageText(uint module, uint description, string key)
@@ -129,17 +129,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
return CleanText(reader.ReadToEnd()); return CleanText(reader.ReadToEnd());
} }
else
{ return string.Empty;
return "";
}
} }
private string[] GetButtonsText(uint module, uint description, string key) private string[] GetButtonsText(uint module, uint description, string key)
{ {
string buttonsText = GetMessageText(module, description, key); string buttonsText = GetMessageText(module, description, key);
return (buttonsText == "") ? null : buttonsText.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); return (buttonsText == string.Empty) ? null : buttonsText.Split(["\r\n", "\r", "\n"], StringSplitOptions.None);
} }
private void ParseErrorCommonArg() private void ParseErrorCommonArg()
@@ -156,7 +154,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string message = GetMessageText(module, description, "DlgMsg"); string message = GetMessageText(module, description, "DlgMsg");
if (message == "") if (message == string.Empty)
{ {
message = "An error has occured.\n\nPlease try again later."; message = "An error has occured.\n\nPlease try again later.";
} }
@@ -190,7 +188,7 @@ namespace Ryujinx.HLE.HOS.Applets.Error
// TODO: Handle the LanguageCode to return the translated "OK" and "Details". // TODO: Handle the LanguageCode to return the translated "OK" and "Details".
if (detailsText.Trim() != "") if (detailsText.Trim() != string.Empty)
{ {
buttons.Add("Details"); buttons.Add("Details");
} }

View File

@@ -51,7 +51,7 @@ namespace Ryujinx.HLE.HOS.Applets
private byte[] _transferMemory; private byte[] _transferMemory;
private string _textValue = ""; private string _textValue = string.Empty;
private int _cursorBegin = 0; private int _cursorBegin = 0;
private Encoding _encoding = Encoding.Unicode; private Encoding _encoding = Encoding.Unicode;
private KeyboardResult _lastResult = KeyboardResult.NotSet; private KeyboardResult _lastResult = KeyboardResult.NotSet;

View File

@@ -305,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
SKRect bounds = SKRect.Empty; SKRect bounds = SKRect.Empty;
if (text == "") if (text == string.Empty)
{ {
paint.MeasureText(" ", ref bounds); paint.MeasureText(" ", ref bounds);
} }
@@ -321,7 +321,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
{ {
SKRect bounds = SKRect.Empty; SKRect bounds = SKRect.Empty;
if (text == "") if (text == string.Empty)
{ {
paint.MeasureText(" ", ref bounds); paint.MeasureText(" ", ref bounds);
} }

View File

@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
/// </summary> /// </summary>
internal class SoftwareKeyboardUIState internal class SoftwareKeyboardUIState
{ {
public string InputText = ""; public string InputText = string.Empty;
public int CursorBegin = 0; public int CursorBegin = 0;
public int CursorEnd = 0; public int CursorEnd = 0;
public bool AcceptPressed = false; public bool AcceptPressed = false;

View File

@@ -238,7 +238,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
} }
else else
{ {
info.SubName = ""; info.SubName = string.Empty;
} }
info.ImageName = GetGuessedNsoNameFromIndex(imageIndex); info.ImageName = GetGuessedNsoNameFromIndex(imageIndex);

View File

@@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Services.Account.Acc
{ {
if (userId.IsNull) if (userId.IsNull)
{ {
userId = new UserId(Guid.NewGuid().ToString().Replace("-", "")); userId = new UserId(Guid.NewGuid().ToString().Replace("-", string.Empty));
} }
UserProfile profile = new(userId, name, image); UserProfile profile = new(userId, name, image);

View File

@@ -5,6 +5,7 @@ using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Kernel.Memory; using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
@@ -28,6 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public DebugPadDevice DebugPad; public DebugPadDevice DebugPad;
public TouchDevice Touchscreen; public TouchDevice Touchscreen;
public MouseDevice Mouse; public MouseDevice Mouse;
public DebugMouseDevice DebugMouse;
public KeyboardDevice Keyboard; public KeyboardDevice Keyboard;
public NpadDevices Npads; public NpadDevices Npads;
@@ -44,6 +46,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
CheckTypeSizeOrThrow<RingLifo<DebugPadState>>(0x2c8); CheckTypeSizeOrThrow<RingLifo<DebugPadState>>(0x2c8);
CheckTypeSizeOrThrow<RingLifo<TouchScreenState>>(0x2C38); CheckTypeSizeOrThrow<RingLifo<TouchScreenState>>(0x2C38);
CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350); CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350);
CheckTypeSizeOrThrow<RingLifo<DebugMouseState>>(0x350);
CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8); CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8);
CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000); CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000);
CheckTypeSizeOrThrow<SharedMemory>(Horizon.HidSize); CheckTypeSizeOrThrow<SharedMemory>(Horizon.HidSize);
@@ -64,6 +67,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
DebugPad = new DebugPadDevice(_device, true); DebugPad = new DebugPadDevice(_device, true);
Touchscreen = new TouchDevice(_device, true); Touchscreen = new TouchDevice(_device, true);
Mouse = new MouseDevice(_device, false); Mouse = new MouseDevice(_device, false);
DebugMouse = new DebugMouseDevice(_device, false);
Keyboard = new KeyboardDevice(_device, false); Keyboard = new KeyboardDevice(_device, false);
Npads = new NpadDevices(_device, true); Npads = new NpadDevices(_device, true);
} }

View File

@@ -0,0 +1,29 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse;
namespace Ryujinx.HLE.HOS.Services.Hid
{
public class DebugMouseDevice : BaseDevice
{
public DebugMouseDevice(Switch device, bool active) : base(device, active) { }
public void Update()
{
ref RingLifo<DebugMouseState> lifo = ref _device.Hid.SharedMemory.DebugMouse;
ref DebugMouseState previousEntry = ref lifo.GetCurrentEntryRef();
DebugMouseState newState = new()
{
SamplingNumber = previousEntry.SamplingNumber + 1,
};
if (Active)
{
// TODO: This is a debug device only present in dev environment, do we want to support it?
}
lifo.Write(ref newState);
}
}
}

View File

@@ -130,6 +130,26 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return ResultCode.Success; return ResultCode.Success;
} }
[CommandCmif(26)]
// ActivateDebugMouse(nn::applet::AppletResourceUserId)
public ResultCode ActivateDebugMouse(ServiceCtx context)
{
long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.DebugMouse.Active = true;
// Initialize entries to avoid issues with some games.
for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
{
context.Device.Hid.DebugMouse.Update();
}
Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success;
}
[CommandCmif(31)] [CommandCmif(31)]
// ActivateKeyboard(nn::applet::AppletResourceUserId) // ActivateKeyboard(nn::applet::AppletResourceUserId)

View File

@@ -0,0 +1,12 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse
{
[Flags]
enum DebugMouseAttribute : uint
{
None = 0,
Transferable = 1 << 0,
IsConnected = 1 << 1,
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse
{
[Flags]
enum DebugMouseButton : uint
{
None = 0,
Left = 1 << 0,
Right = 1 << 1,
Middle = 1 << 2,
Forward = 1 << 3,
Back = 1 << 4,
}
}

View File

@@ -0,0 +1,19 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct DebugMouseState : ISampledDataStruct
{
public ulong SamplingNumber;
public int X;
public int Y;
public int DeltaX;
public int DeltaY;
public int WheelDeltaX;
public int WheelDeltaY;
public DebugMouseButton Buttons;
public DebugMouseAttribute Attributes;
}
}

View File

@@ -1,5 +1,6 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugMouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
@@ -44,6 +45,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
/// </summary> /// </summary>
[FieldOffset(0x9A00)] [FieldOffset(0x9A00)]
public Array10<NpadState> Npads; public Array10<NpadState> Npads;
/// <summary>
/// Debug mouse.
/// </summary>
[FieldOffset(0x3DC00)]
public RingLifo<DebugMouseState> DebugMouse;
public static SharedMemory Create() public static SharedMemory Create()
{ {

View File

@@ -1,3 +1,4 @@
using Gommon;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
@@ -143,7 +144,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
if (decompressedLdnData.Length != header.DecompressLength) if (decompressedLdnData.Length != header.DecompressLength)
{ {
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})"); Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})");
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{string.Join("", decompressedLdnData.Select(x => (int)x).ToArray())}'"); Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{decompressedLdnData.Select(x => (int)x).JoinToString(string.Empty)}'");
return; return;
} }

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.HLE.HOS.Services.Ngct
ulong bufferSize = context.Request.PtrBuff[0].Size; ulong bufferSize = context.Request.PtrBuff[0].Size;
bool isMatch = false; bool isMatch = false;
string text = ""; string text = string.Empty;
if (bufferSize != 0) if (bufferSize != 0)
{ {
@@ -57,8 +57,8 @@ namespace Ryujinx.HLE.HOS.Services.Ngct
ulong bufferFilteredPosition = context.Request.RecvListBuff[0].Position; ulong bufferFilteredPosition = context.Request.RecvListBuff[0].Position;
string text = ""; string text = string.Empty;
string textFiltered = ""; string textFiltered = string.Empty;
if (bufferSize != 0) if (bufferSize != 0)
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Nsd.Manager
// TODO: Load Environment from the savedata. // TODO: Load Environment from the savedata.
address = address.Replace("%", IManager.NsdSettings.Environment); address = address.Replace("%", IManager.NsdSettings.Environment);
resolvedAddress = ""; resolvedAddress = string.Empty;
if (IManager.NsdSettings == null) if (IManager.NsdSettings == null)
{ {

View File

@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
if (size < 0) if (size < 0)
{ {
return ""; return string.Empty;
} }
ReadOnlySpan<byte> data = ReadInPlace((size + 1) * 2); ReadOnlySpan<byte> data = ReadInPlace((size + 1) * 2);

View File

@@ -166,7 +166,7 @@ namespace Ryujinx.HLE.HOS.Services.Vi.RootService
private ResultCode OpenDisplayImpl(ServiceCtx context, string name) private ResultCode OpenDisplayImpl(ServiceCtx context, string name)
{ {
if (name == "") if (name == string.Empty)
{ {
return ResultCode.InvalidValue; return ResultCode.InvalidValue;
} }

View File

@@ -102,7 +102,7 @@ namespace Ryujinx.HLE.Loaders.Executables
Match fsSdkMatch = FsSdkRegex().Match(rawTextBuffer); Match fsSdkMatch = FsSdkRegex().Match(rawTextBuffer);
if (fsSdkMatch.Success) if (fsSdkMatch.Success)
{ {
stringBuilder.AppendLine($" FS SDK Version: {fsSdkMatch.Value.Replace("sdk_version: ", "")}"); stringBuilder.AppendLine($" FS SDK Version: {fsSdkMatch.Value.Replace("sdk_version: ", string.Empty)}");
} }
MatchCollection sdkMwMatches = SdkMwRegex().Matches(rawTextBuffer); MatchCollection sdkMwMatches = SdkMwRegex().Matches(rawTextBuffer);

View File

@@ -89,7 +89,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
Logger.Warning?.Print(LogClass.Ptc, "Detected unsupported ExeFs modifications. PTC disabled."); Logger.Warning?.Print(LogClass.Ptc, "Detected unsupported ExeFs modifications. PTC disabled.");
} }
string programName = ""; string programName = string.Empty;
if (!isHomebrew && programId > 0x010000000000FFFF) if (!isHomebrew && programId > 0x010000000000FFFF)
{ {

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.HLE.Loaders.Processes
var nacpData = new BlitStruct<ApplicationControlProperty>(1); var nacpData = new BlitStruct<ApplicationControlProperty>(1);
ulong programId = metaLoader.GetProgramId(); ulong programId = metaLoader.GetProgramId();
device.Configuration.VirtualFileSystem.ModLoader.CollectMods(new[] { programId }); device.Configuration.VirtualFileSystem.ModLoader.CollectMods([programId]);
if (programId != 0) if (programId != 0)
{ {

View File

@@ -145,7 +145,7 @@ namespace Ryujinx.HLE.Loaders.Processes
IFileSystem dummyExeFs = null; IFileSystem dummyExeFs = null;
Stream romfsStream = null; Stream romfsStream = null;
string programName = ""; string programName = string.Empty;
ulong programId = 0000000000000000; ulong programId = 0000000000000000;
// Load executable. // Load executable.

View File

@@ -255,7 +255,7 @@ namespace Ryujinx.HLE.Loaders.Processes
{ {
NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()), NsoExecutable nso => Convert.ToHexString(nso.BuildId.ItemsRo.ToArray()),
NroExecutable nro => Convert.ToHexString(nro.Header.BuildId), NroExecutable nro => Convert.ToHexString(nro.Header.BuildId),
_ => "", _ => string.Empty
}).ToUpper()); }).ToUpper());
ulong[] nsoBase = new ulong[executables.Length]; ulong[] nsoBase = new ulong[executables.Length];

View File

@@ -1,3 +1,4 @@
using Humanizer;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
@@ -485,10 +486,10 @@ namespace Ryujinx.Headless.SDL2
{ {
string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}"; string playerCount = args.PlayerCountMin == args.PlayerCountMax ? $"exactly {args.PlayerCountMin}" : $"{args.PlayerCountMin}-{args.PlayerCountMax}";
string message = $"Application requests {playerCount} player(s) with:\n\n" string message = $"Application requests {playerCount} {"player".ToQuantity(args.PlayerCountMin + args.PlayerCountMax, ShowQuantityAs.None)} with:\n\n"
+ $"TYPES: {args.SupportedStyles}\n\n" + $"TYPES: {args.SupportedStyles}\n\n"
+ $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n" + $"PLAYERS: {string.Join(", ", args.SupportedPlayers)}\n\n"
+ (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : "") + (args.IsDocked ? "Docked mode set. Handheld is also invalid.\n\n" : string.Empty)
+ "Please reconfigure Input now and then press OK."; + "Please reconfigure Input now and then press OK.";
return DisplayMessageDialog("Controller Applet", message); return DisplayMessageDialog("Controller Applet", message);

View File

@@ -1235,14 +1235,14 @@ namespace Ryujinx.Horizon.Common
{ 0x412, "NotFound" }, { 0x412, "NotFound" },
{ 0x612, "NotEnoughBuffer" }, { 0x612, "NotEnoughBuffer" },
{ 0xCA12, "Cancelled" }, { 0xCA12, "Cancelled" },
{ 0x7FE12, "" }, { 0x7FE12, string.Empty },
{ 0xFA212, "" }, { 0xFA212, string.Empty },
{ 0xFA612, "InvalidTaskId" }, { 0xFA612, "InvalidTaskId" },
{ 0xFB612, "InvalidSize" }, { 0xFB612, "InvalidSize" },
{ 0xFCA12, "TaskCancelled" }, { 0xFCA12, "TaskCancelled" },
{ 0xFCC12, "TaskNotCompleted" }, { 0xFCC12, "TaskNotCompleted" },
{ 0xFCE12, "TaskQueueNotAvailable" }, { 0xFCE12, "TaskQueueNotAvailable" },
{ 0x106A12, "" }, { 0x106A12, string.Empty },
{ 0x106C12, "OutOfRpcTask" }, { 0x106C12, "OutOfRpcTask" },
{ 0x109612, "InvalidCategory" }, { 0x109612, "InvalidCategory" },
{ 0x214, "OutOfKeyResource" }, { 0x214, "OutOfKeyResource" },

View File

@@ -221,7 +221,7 @@ namespace Ryujinx.Horizon.Sdk.Ngc.Detail
if (includeMultiWord) if (includeMultiWord)
{ {
int lastMultiWordIndex = 0; int lastMultiWordIndex = 0;
string multiWord = ""; string multiWord = string.Empty;
while (_multiWordMap.Has(nodePlainIndex)) while (_multiWordMap.Has(nodePlainIndex))
{ {

View File

@@ -162,7 +162,7 @@ namespace Ryujinx.UI.App.Common
NsoReader reader = new(); NsoReader reader = new();
reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure(); reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure();
return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", "").ToUpper()[..16]; return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", string.Empty).ToUpper()[..16];
} }
} }
} }

View File

@@ -298,16 +298,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary> /// </summary>
public string LanguageCode { get; set; } public string LanguageCode { get; set; }
/// <summary>
/// Enable or disable custom themes in the GUI
/// </summary>
public bool EnableCustomTheme { get; set; }
/// <summary>
/// Path to custom GUI theme
/// </summary>
public string CustomThemePath { get; set; }
/// <summary> /// <summary>
/// Chooses the base style // Not Used /// Chooses the base style // Not Used
/// </summary> /// </summary>

View File

@@ -144,16 +144,6 @@ namespace Ryujinx.UI.Common.Configuration
/// </summary> /// </summary>
public ReactiveObject<string> LanguageCode { get; private set; } public ReactiveObject<string> LanguageCode { get; private set; }
/// <summary>
/// Enable or disable custom themes in the GUI
/// </summary>
public ReactiveObject<bool> EnableCustomTheme { get; private set; }
/// <summary>
/// Path to custom GUI theme
/// </summary>
public ReactiveObject<string> CustomThemePath { get; private set; }
/// <summary> /// <summary>
/// Selects the base style /// Selects the base style
/// </summary> /// </summary>
@@ -202,8 +192,6 @@ namespace Ryujinx.UI.Common.Configuration
AutoloadDirs = new ReactiveObject<List<string>>(); AutoloadDirs = new ReactiveObject<List<string>>();
ShownFileTypes = new ShownFileTypeSettings(); ShownFileTypes = new ShownFileTypeSettings();
WindowStartup = new WindowStartupSettings(); WindowStartup = new WindowStartupSettings();
EnableCustomTheme = new ReactiveObject<bool>();
CustomThemePath = new ReactiveObject<string>();
BaseStyle = new ReactiveObject<string>(); BaseStyle = new ReactiveObject<string>();
StartFullscreen = new ReactiveObject<bool>(); StartFullscreen = new ReactiveObject<bool>();
GameListViewMode = new ReactiveObject<int>(); GameListViewMode = new ReactiveObject<int>();
@@ -777,8 +765,6 @@ namespace Ryujinx.UI.Common.Configuration
WindowMaximized = UI.WindowStartup.WindowMaximized, WindowMaximized = UI.WindowStartup.WindowMaximized,
}, },
LanguageCode = UI.LanguageCode, LanguageCode = UI.LanguageCode,
EnableCustomTheme = UI.EnableCustomTheme,
CustomThemePath = UI.CustomThemePath,
BaseStyle = UI.BaseStyle, BaseStyle = UI.BaseStyle,
GameListViewMode = UI.GameListViewMode, GameListViewMode = UI.GameListViewMode,
ShowNames = UI.ShowNames, ShowNames = UI.ShowNames,
@@ -811,8 +797,8 @@ namespace Ryujinx.UI.Common.Configuration
Graphics.MaxAnisotropy.Value = -1.0f; Graphics.MaxAnisotropy.Value = -1.0f;
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9; Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
Graphics.GraphicsBackend.Value = DefaultGraphicsBackend(); Graphics.GraphicsBackend.Value = DefaultGraphicsBackend();
Graphics.PreferredGpu.Value = ""; Graphics.PreferredGpu.Value = string.Empty;
Graphics.ShadersDumpPath.Value = ""; Graphics.ShadersDumpPath.Value = string.Empty;
Logger.EnableDebug.Value = false; Logger.EnableDebug.Value = false;
Logger.EnableStub.Value = true; Logger.EnableStub.Value = true;
Logger.EnableInfo.Value = true; Logger.EnableInfo.Value = true;
@@ -821,7 +807,7 @@ namespace Ryujinx.UI.Common.Configuration
Logger.EnableTrace.Value = false; Logger.EnableTrace.Value = false;
Logger.EnableGuest.Value = true; Logger.EnableGuest.Value = true;
Logger.EnableFsAccessLog.Value = false; Logger.EnableFsAccessLog.Value = false;
Logger.FilteredClasses.Value = Array.Empty<LogClass>(); Logger.FilteredClasses.Value = [];
Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None; Logger.GraphicsDebugLevel.Value = GraphicsDebugLevel.None;
System.Language.Value = Language.AmericanEnglish; System.Language.Value = Language.AmericanEnglish;
System.Region.Value = Region.USA; System.Region.Value = Region.USA;
@@ -868,17 +854,15 @@ namespace Ryujinx.UI.Common.Configuration
UI.GuiColumns.PathColumn.Value = true; UI.GuiColumns.PathColumn.Value = true;
UI.ColumnSort.SortColumnId.Value = 0; UI.ColumnSort.SortColumnId.Value = 0;
UI.ColumnSort.SortAscending.Value = false; UI.ColumnSort.SortAscending.Value = false;
UI.GameDirs.Value = new List<string>(); UI.GameDirs.Value = [];
UI.AutoloadDirs.Value = new List<string>(); UI.AutoloadDirs.Value = [];
UI.ShownFileTypes.NSP.Value = true; UI.ShownFileTypes.NSP.Value = true;
UI.ShownFileTypes.PFS0.Value = true; UI.ShownFileTypes.PFS0.Value = true;
UI.ShownFileTypes.XCI.Value = true; UI.ShownFileTypes.XCI.Value = true;
UI.ShownFileTypes.NCA.Value = true; UI.ShownFileTypes.NCA.Value = true;
UI.ShownFileTypes.NRO.Value = true; UI.ShownFileTypes.NRO.Value = true;
UI.ShownFileTypes.NSO.Value = true; UI.ShownFileTypes.NSO.Value = true;
UI.EnableCustomTheme.Value = true;
UI.LanguageCode.Value = "en_US"; UI.LanguageCode.Value = "en_US";
UI.CustomThemePath.Value = "";
UI.BaseStyle.Value = "Dark"; UI.BaseStyle.Value = "Dark";
UI.GameListViewMode.Value = 0; UI.GameListViewMode.Value = 0;
UI.ShowNames.Value = true; UI.ShowNames.Value = true;
@@ -1628,9 +1612,7 @@ namespace Ryujinx.UI.Common.Configuration
UI.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA; UI.ShownFileTypes.NCA.Value = configurationFileFormat.ShownFileTypes.NCA;
UI.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO; UI.ShownFileTypes.NRO.Value = configurationFileFormat.ShownFileTypes.NRO;
UI.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO; UI.ShownFileTypes.NSO.Value = configurationFileFormat.ShownFileTypes.NSO;
UI.EnableCustomTheme.Value = configurationFileFormat.EnableCustomTheme;
UI.LanguageCode.Value = configurationFileFormat.LanguageCode; UI.LanguageCode.Value = configurationFileFormat.LanguageCode;
UI.CustomThemePath.Value = configurationFileFormat.CustomThemePath;
UI.BaseStyle.Value = configurationFileFormat.BaseStyle; UI.BaseStyle.Value = configurationFileFormat.BaseStyle;
UI.GameListViewMode.Value = configurationFileFormat.GameListViewMode; UI.GameListViewMode.Value = configurationFileFormat.GameListViewMode;
UI.ShowNames.Value = configurationFileFormat.ShowNames; UI.ShowNames.Value = configurationFileFormat.ShowNames;

View File

@@ -35,7 +35,7 @@ namespace Ryujinx.UI.Common.Helper
if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux())) if ((uninstall && AreMimeTypesRegisteredLinux()) || (!uninstall && !AreMimeTypesRegisteredLinux()))
{ {
string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml"); string mimeTypesFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mime", "Ryujinx.xml");
string additionalArgs = !uninstall ? "--novendor" : ""; string additionalArgs = !uninstall ? "--novendor" : string.Empty;
using Process mimeProcess = new(); using Process mimeProcess = new();
@@ -83,7 +83,7 @@ namespace Ryujinx.UI.Common.Helper
var openCmd = key.OpenSubKey(@"shell\open\command"); var openCmd = key.OpenSubKey(@"shell\open\command");
string keyValue = (string)openCmd.GetValue(""); string keyValue = (string)openCmd.GetValue(string.Empty);
return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName)); return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
} }

View File

@@ -53,7 +53,7 @@ namespace Ryujinx.UI.Common.Helper
{ {
var titleUpdateWindowData = new TitleUpdateMetadata var titleUpdateWindowData = new TitleUpdateMetadata
{ {
Selected = "", Selected = string.Empty,
Paths = [], Paths = [],
}; };

View File

@@ -0,0 +1,55 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.App.Common;
namespace Ryujinx.UI.Common.Models
{
public record XCITrimmerFileModel(
string Name,
string Path,
bool Trimmable,
bool Untrimmable,
long PotentialSavingsB,
long CurrentSavingsB,
int? PercentageProgress,
XCIFileTrimmer.OperationOutcome ProcessingOutcome)
{
public static XCITrimmerFileModel FromApplicationData(ApplicationData applicationData, XCIFileTrimmerLog logger)
{
var trimmer = new XCIFileTrimmer(applicationData.Path, logger);
return new XCITrimmerFileModel(
applicationData.Name,
applicationData.Path,
trimmer.CanBeTrimmed,
trimmer.CanBeUntrimmed,
trimmer.DiskSpaceSavingsB,
trimmer.DiskSpaceSavedB,
null,
XCIFileTrimmer.OperationOutcome.Undetermined
);
}
public bool IsFailed
{
get
{
return ProcessingOutcome != XCIFileTrimmer.OperationOutcome.Undetermined &&
ProcessingOutcome != XCIFileTrimmer.OperationOutcome.Successful;
}
}
public virtual bool Equals(XCITrimmerFileModel obj)
{
if (obj == null)
return false;
else
return this.Path == obj.Path;
}
public override int GetHashCode()
{
return this.Path.GetHashCode();
}
}
}

View File

@@ -51,7 +51,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscordRichPresence" /> <PackageReference Include="DiscordRichPresence" />
<PackageReference Include="DynamicData" /> <PackageReference Include="DynamicData" />
<PackageReference Include="Humanizer" />
<PackageReference Include="securifybv.ShellLink" /> <PackageReference Include="securifybv.ShellLink" />
</ItemGroup> </ItemGroup>

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.UI.LocaleGenerator
context.RegisterSourceOutput(contents, (spc, content) => context.RegisterSourceOutput(contents, (spc, content) =>
{ {
var lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"")).Select(x => x.Split(':')[0].Trim().Replace("\"", "")); var lines = content.Split('\n').Where(x => x.Trim().StartsWith("\"")).Select(x => x.Split(':')[0].Trim().Replace("\"", string.Empty));
StringBuilder enumSourceBuilder = new(); StringBuilder enumSourceBuilder = new();
enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;"); enumSourceBuilder.AppendLine("namespace Ryujinx.Ava.Common.Locale;");
enumSourceBuilder.AppendLine("internal enum LocaleKeys"); enumSourceBuilder.AppendLine("internal enum LocaleKeys");

View File

@@ -58,11 +58,9 @@ namespace Ryujinx.Ava
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
ApplyConfiguredTheme(); ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event; ConfigurationState.Instance.UI.BaseStyle.Event += ThemeChanged_Event;
ConfigurationState.Instance.UI.CustomThemePath.Event += ThemeChanged_Event;
ConfigurationState.Instance.UI.EnableCustomTheme.Event += CustomThemeChanged_Event;
} }
} }
@@ -88,17 +86,13 @@ namespace Ryujinx.Ava
} }
}); });
} }
private void CustomThemeChanged_Event(object _, ReactiveEventArgs<bool> __) => ApplyConfiguredTheme();
private void ThemeChanged_Event(object _, ReactiveEventArgs<string> __) => ApplyConfiguredTheme(); private void ThemeChanged_Event(object _, ReactiveEventArgs<string> rArgs) => ApplyConfiguredTheme(rArgs.NewValue);
public void ApplyConfiguredTheme() public void ApplyConfiguredTheme(string baseStyle)
{ {
try try
{ {
string baseStyle = ConfigurationState.Instance.UI.BaseStyle;
if (string.IsNullOrWhiteSpace(baseStyle)) if (string.IsNullOrWhiteSpace(baseStyle))
{ {
ConfigurationState.Instance.UI.BaseStyle.Value = "Auto"; ConfigurationState.Instance.UI.BaseStyle.Value = "Auto";

View File

@@ -604,61 +604,59 @@ namespace Ryujinx.Ava
SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion(); SystemVersion firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
{ {
if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError)) if (!SetupValidator.CanStartApplication(ContentManager, ApplicationPath, out UserError userError))
{ {
if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion))
{ {
if (SetupValidator.CanFixStartApplication(ContentManager, ApplicationPath, userError, out firmwareVersion)) if (userError is UserError.NoFirmware)
{ {
if (userError == UserError.NoFirmware) UserResult result = await ContentDialogHelper.CreateConfirmationDialog(
{ LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage],
UserResult result = await ContentDialogHelper.CreateConfirmationDialog( LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.DialogFirmwareNoFirmwareInstalledMessage], LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedMessage, firmwareVersion.VersionString), LocaleManager.Instance[LocaleKeys.InputDialogNo],
LocaleManager.Instance[LocaleKeys.InputDialogYes], string.Empty);
LocaleManager.Instance[LocaleKeys.InputDialogNo],
"");
if (result != UserResult.Yes) if (result != UserResult.Yes)
{
await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose();
return false;
}
}
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{ {
await UserErrorDialog.ShowUserErrorDialog(userError); await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose(); Device.Dispose();
return false; return false;
} }
// Tell the user that we installed a firmware for them.
if (userError == UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
"",
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
} }
else
if (!SetupValidator.TryFixStartApplication(ContentManager, ApplicationPath, userError, out _))
{ {
await UserErrorDialog.ShowUserErrorDialog(userError); await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose(); Device.Dispose();
return false; return false;
} }
// Tell the user that we installed a firmware for them.
if (userError is UserError.NoFirmware)
{
firmwareVersion = ContentManager.GetCurrentFirmwareVersion();
_viewModel.RefreshFirmwareStatus();
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstalledMessage, firmwareVersion.VersionString),
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallEmbeddedSuccessMessage, firmwareVersion.VersionString),
LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
else
{
await UserErrorDialog.ShowUserErrorDialog(userError);
Device.Dispose();
return false;
} }
} }
} }
@@ -820,20 +818,12 @@ namespace Ryujinx.Ava
VirtualFileSystem.ReloadKeySet(); VirtualFileSystem.ReloadKeySet();
// Initialize Renderer. // Initialize Renderer.
IRenderer renderer; IRenderer renderer = ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl
? new OpenGLRenderer()
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan) : VulkanRenderer.Create(
{ ConfigurationState.Instance.Graphics.PreferredGpu,
renderer = new VulkanRenderer(
Vk.GetApi(),
(RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface, (RendererHost.EmbeddedWindow as EmbeddedWindowVulkan)!.CreateSurface,
VulkanHelper.GetRequiredInstanceExtensions, VulkanHelper.GetRequiredInstanceExtensions);
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
}
else
{
renderer = new OpenGLRenderer();
}
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;

View File

@@ -33,6 +33,7 @@
"MenuBarToolsManageFileTypes": "Manage file types", "MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types", "MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types", "MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarToolsXCITrimmer": "Trim XCI Files",
"MenuBarView": "_View", "MenuBarView": "_View",
"MenuBarViewWindow": "Window Size", "MenuBarViewWindow": "Window Size",
"MenuBarViewWindow720": "720p", "MenuBarViewWindow720": "720p",
@@ -84,8 +85,11 @@
"GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods", "GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods",
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory", "GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.", "GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
"GameListContextMenuTrimXCI": "Check and Trim XCI File",
"GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded", "StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}", "StatusBarSystemVersion": "System Version: {0}",
"StatusBarXCIFileTrimming": "Trimming XCI File '{0}'",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
"LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}", "LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.", "LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
@@ -400,6 +404,8 @@
"InputDialogTitle": "Input Dialog", "InputDialogTitle": "Input Dialog",
"InputDialogOk": "OK", "InputDialogOk": "OK",
"InputDialogCancel": "Cancel", "InputDialogCancel": "Cancel",
"InputDialogCancelling": "Cancelling",
"InputDialogClose": "Close",
"InputDialogAddNewProfileTitle": "Choose the Profile Name", "InputDialogAddNewProfileTitle": "Choose the Profile Name",
"InputDialogAddNewProfileHeader": "Please Enter a Profile Name", "InputDialogAddNewProfileHeader": "Please Enter a Profile Name",
"InputDialogAddNewProfileSubtext": "(Max Length: {0})", "InputDialogAddNewProfileSubtext": "(Max Length: {0})",
@@ -439,13 +445,13 @@
"DialogNcaExtractionMessage": "Extracting {0} section from {1}...", "DialogNcaExtractionMessage": "Extracting {0} section from {1}...",
"DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor", "DialogNcaExtractionTitle": "Ryujinx - NCA Section Extractor",
"DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.", "DialogNcaExtractionMainNcaNotFoundErrorMessage": "Extraction failure. The main NCA was not present in the selected file.",
"DialogNcaExtractionCheckLogErrorMessage": "Extraction failure. Read the log file for further information.", "DialogNcaExtractionCheckLogErrorMessage": "Extraction failed. Please check the log file for more details.",
"DialogNcaExtractionSuccessMessage": "Extraction completed successfully.", "DialogNcaExtractionSuccessMessage": "Extraction completed successfully.",
"DialogUpdaterConvertFailedMessage": "Failed to convert the current Ryujinx version.", "DialogUpdaterConvertFailedMessage": "Unable to convert the current Ryujinx version.",
"DialogUpdaterCancelUpdateMessage": "Cancelling Update!", "DialogUpdaterCancelUpdateMessage": "Update canceled!",
"DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the most updated version of Ryujinx!", "DialogUpdaterAlreadyOnLatestVersionMessage": "You are already using the latest version of Ryujinx!",
"DialogUpdaterFailedToGetVersionMessage": "An error has occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.", "DialogUpdaterFailedToGetVersionMessage": "An error occurred while trying to retrieve release information from GitHub. This may happen if a new release is currently being compiled by GitHub Actions. Please try again in a few minutes.",
"DialogUpdaterConvertFailedGithubMessage": "Failed to convert the received Ryujinx version from Github Release.", "DialogUpdaterConvertFailedGithubMessage": "Failed to convert the Ryujinx version received from GitHub."
"DialogUpdaterDownloadingMessage": "Downloading Update...", "DialogUpdaterDownloadingMessage": "Downloading Update...",
"DialogUpdaterExtractionMessage": "Extracting Update...", "DialogUpdaterExtractionMessage": "Extracting Update...",
"DialogUpdaterRenamingMessage": "Renaming Update...", "DialogUpdaterRenamingMessage": "Renaming Update...",
@@ -455,7 +461,7 @@
"DialogUpdaterNoInternetMessage": "You are not connected to the Internet!", "DialogUpdaterNoInternetMessage": "You are not connected to the Internet!",
"DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!", "DialogUpdaterNoInternetSubMessage": "Please verify that you have a working Internet connection!",
"DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!", "DialogUpdaterDirtyBuildMessage": "You Cannot update a Dirty build of Ryujinx!",
"DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://https://github.com/GreemDev/Ryujinx/releases/ if you are looking for a supported version.", "DialogUpdaterDirtyBuildSubMessage": "Please download Ryujinx at https://github.com/GreemDev/Ryujinx/releases/ if you are looking for a supported version.",
"DialogRestartRequiredMessage": "Restart Required", "DialogRestartRequiredMessage": "Restart Required",
"DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.", "DialogThemeRestartMessage": "Theme has been saved. A restart is needed to apply the theme.",
"DialogThemeRestartSubMessage": "Do you want to restart", "DialogThemeRestartSubMessage": "Do you want to restart",
@@ -468,6 +474,7 @@
"DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!", "DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!",
"DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.", "DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.",
"DialogOpenSettingsWindowLabel": "Open Settings Window", "DialogOpenSettingsWindowLabel": "Open Settings Window",
"DialogOpenXCITrimmerWindowLabel": "XCI Trimmer Window",
"DialogControllerAppletTitle": "Controller Applet", "DialogControllerAppletTitle": "Controller Applet",
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}", "DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
"DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}", "DialogSoftwareKeyboardErrorExceptionMessage": "Error displaying Software Keyboard: {0}",
@@ -670,6 +677,12 @@
"TitleUpdateVersionLabel": "Version {0}", "TitleUpdateVersionLabel": "Version {0}",
"TitleBundledUpdateVersionLabel": "Bundled: Version {0}", "TitleBundledUpdateVersionLabel": "Bundled: Version {0}",
"TitleBundledDlcLabel": "Bundled:", "TitleBundledDlcLabel": "Bundled:",
"TitleXCIStatusPartialLabel": "Partial",
"TitleXCIStatusTrimmableLabel": "Untrimmed",
"TitleXCIStatusUntrimmableLabel": "Trimmed",
"TitleXCIStatusFailedLabel": "(Failed)",
"TitleXCICanSaveLabel": "Save {0:n0} Mb",
"TitleXCISavingLabel": "Saved {0:n0} Mb",
"RyujinxInfo": "Ryujinx - Info", "RyujinxInfo": "Ryujinx - Info",
"RyujinxConfirm": "Ryujinx - Confirmation", "RyujinxConfirm": "Ryujinx - Confirmation",
"FileDialogAllTypes": "All types", "FileDialogAllTypes": "All types",
@@ -722,11 +735,37 @@
"SelectDlcDialogTitle": "Select DLC files", "SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files", "SelectUpdateDialogTitle": "Select update files",
"SelectModDialogTitle": "Select mod directory", "SelectModDialogTitle": "Select mod directory",
"TrimXCIFileDialogTitle": "Check and Trim XCI File",
"TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.",
"TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB",
"TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details",
"TrimXCIFileNoUntrimPossible": "XCI File cannot be untrimmed. Check logs for further details",
"TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details",
"TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.",
"TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim",
"TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details",
"TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details",
"TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed",
"TrimXCIFileCancelled": "The operation was cancelled",
"TrimXCIFileFileUndertermined": "No operation was performed",
"UserProfileWindowTitle": "User Profiles Manager", "UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager", "CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})", "DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
"ModWindowTitle": "Manage Mods for {0} ({1})", "ModWindowTitle": "Manage Mods for {0} ({1})",
"UpdateWindowTitle": "Title Update Manager", "UpdateWindowTitle": "Title Update Manager",
"XCITrimmerWindowTitle": "XCI File Trimmer",
"XCITrimmerTitleStatusCount": "{0} of {1} Title(s) Selected",
"XCITrimmerTitleStatusCountWithFilter": "{0} of {1} Title(s) Selected ({2} displayed)",
"XCITrimmerTitleStatusTrimming": "Trimming {0} Title(s)...",
"XCITrimmerTitleStatusUntrimming": "Untrimming {0} Title(s)...",
"XCITrimmerTitleStatusFailed": "Failed",
"XCITrimmerPotentialSavings": "Potential Savings",
"XCITrimmerActualSavings": "Actual Savings",
"XCITrimmerSavingsMb": "{0:n0} Mb",
"XCITrimmerSelectDisplayed": "Select Shown",
"XCITrimmerDeselectDisplayed": "Deselect Shown",
"XCITrimmerSortName": "Title",
"XCITrimmerSortSaved": "Space Savings",
"UpdateWindowUpdateAddedMessage": "{0} new update(s) added", "UpdateWindowUpdateAddedMessage": "{0} new update(s) added",
"UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.", "UpdateWindowBundledContentNotice": "Bundled updates cannot be removed, only disabled.",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
@@ -740,6 +779,7 @@
"AutoloadUpdateRemovedMessage": "{0} missing update(s) removed", "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed",
"ModWindowHeading": "{0} Mod(s)", "ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Continue": "Continue",
"Cancel": "Cancel", "Cancel": "Cancel",
"Save": "Save", "Save": "Save",
"Discard": "Discard", "Discard": "Discard",

View File

@@ -43,6 +43,10 @@
</StackPanel> </StackPanel>
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="DropDownButton">
<Setter Property="FontSize"
Value="12" />
</Style>
<Style Selector="Border.small"> <Style Selector="Border.small">
<Setter Property="Width" <Setter Property="Width"
Value="100" /> Value="100" />
@@ -231,6 +235,14 @@
<Setter Property="MinWidth" <Setter Property="MinWidth"
Value="80" /> Value="80" />
</Style> </Style>
<Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
</Style>
<Style Selector="ProgressBar:vertical">
<Setter Property="MinWidth" Value="0"/>
<Setter Property="MinHeight" Value="0"/>
</Style>
<Style Selector="ProgressBar /template/ Border#ProgressBarTrack"> <Style Selector="ProgressBar /template/ Border#ProgressBarTrack">
<Setter Property="IsVisible" <Setter Property="IsVisible"
Value="False" /> Value="False" />
@@ -389,7 +401,7 @@
<x:Double x:Key="ControlContentThemeFontSize">13</x:Double> <x:Double x:Key="ControlContentThemeFontSize">13</x:Double>
<x:Double x:Key="MenuItemHeight">26</x:Double> <x:Double x:Key="MenuItemHeight">26</x:Double>
<x:Double x:Key="TabItemMinHeight">28</x:Double> <x:Double x:Key="TabItemMinHeight">28</x:Double>
<x:Double x:Key="ContentDialogMaxWidth">600</x:Double> <x:Double x:Key="ContentDialogMaxWidth">700</x:Double>
<x:Double x:Key="ContentDialogMaxHeight">756</x:Double> <x:Double x:Key="ContentDialogMaxHeight">756</x:Double>
</Styles.Resources> </Styles.Resources>
</Styles> </Styles>

View File

@@ -1,28 +0,0 @@
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using System;
namespace Ryujinx.Ava.Common.Locale
{
internal class LocaleExtension(LocaleKeys key) : MarkupExtension
{
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => LocaleManager.Instance[key],
null,
typeof(string)
);
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
)
{ Source = LocaleManager.Instance }
.ProvideValue(serviceProvider);
}
}

View File

@@ -1,21 +1,15 @@
using Avalonia.Data.Core; using Avalonia.Data.Core;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions; using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using System; using System;
namespace Ryujinx.Ava.Common.Icon namespace Ryujinx.Ava.Common.Markup
{ {
internal class IconExtension(string iconString) : MarkupExtension internal abstract class BasicMarkupExtension : MarkupExtension
{ {
private ClrPropertyInfo PropertyInfo protected abstract ClrPropertyInfo PropertyInfo { get; }
=> new(
"Item",
_ => new Projektanker.Icons.Avalonia.Icon { Value = iconString },
null,
typeof(Projektanker.Icons.Avalonia.Icon)
);
public override object ProvideValue(IServiceProvider serviceProvider) => public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension( new CompiledBindingExtension(
new CompiledBindingPathBuilder() new CompiledBindingPathBuilder()

View File

@@ -0,0 +1,51 @@
using Avalonia.Data.Core;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using System;
namespace Ryujinx.Ava.Common.Markup
{
internal class IconExtension(string iconString) : BasicMarkupExtension
{
protected override ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Icon { Value = iconString },
null,
typeof(Icon)
);
}
internal class SpinningIconExtension(string iconString) : BasicMarkupExtension
{
protected override ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => new Icon { Value = iconString, Animation = IconAnimation.Spin },
null,
typeof(Icon)
);
}
internal class LocaleExtension(LocaleKeys key) : MarkupExtension
{
private ClrPropertyInfo PropertyInfo
=> new(
"Item",
_ => LocaleManager.Instance[key],
null,
typeof(string)
);
public override object ProvideValue(IServiceProvider serviceProvider) =>
new CompiledBindingExtension(
new CompiledBindingPathBuilder()
.Property(PropertyInfo, PropertyInfoAccessorFactory.CreateInpcPropertyAccessor)
.Build()
) { Source = LocaleManager.Instance }
.ProvideValue(serviceProvider);
}
}

View File

@@ -0,0 +1,24 @@
using Avalonia.Threading;
using Ryujinx.Ava.UI.ViewModels;
namespace Ryujinx.Ava.Common
{
internal class XCIFileTrimmerMainWindowLog : Ryujinx.Common.Logging.XCIFileTrimmerLog
{
private readonly MainWindowViewModel _viewModel;
public XCIFileTrimmerMainWindowLog(MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public override void Progress(long current, long total, string text, bool complete)
{
Dispatcher.UIThread.Post(() =>
{
_viewModel.StatusBarProgressMaximum = (int)(total);
_viewModel.StatusBarProgressValue = (int)(current);
});
}
}
}

View File

@@ -0,0 +1,23 @@
using Avalonia.Threading;
using Ryujinx.Ava.UI.ViewModels;
namespace Ryujinx.Ava.Common
{
internal class XCIFileTrimmerWindowLog : Ryujinx.Common.Logging.XCIFileTrimmerLog
{
private readonly XCITrimmerViewModel _viewModel;
public XCIFileTrimmerWindowLog(XCITrimmerViewModel viewModel)
{
_viewModel = viewModel;
}
public override void Progress(long current, long total, string text, bool complete)
{
Dispatcher.UIThread.Post(() =>
{
_viewModel.SetProgress((int)(current), (int)(total));
});
}
}
}

View File

@@ -14,7 +14,6 @@ using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using Ryujinx.Graphics.Vulkan.MoltenVK; using Ryujinx.Graphics.Vulkan.MoltenVK;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common; using Ryujinx.SDL2.Common;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
@@ -46,8 +45,7 @@ namespace Ryujinx.Ava
public static int Main(string[] args) public static int Main(string[] args)
{ {
Version = ReleaseInformation.Version; Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{ {
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); _ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 177 KiB

View File

@@ -120,7 +120,7 @@ namespace Ryujinx.Ava.UI.Applet
bool okPressed = false; bool okPressed = false;
bool error = false; bool error = false;
string inputText = args.InitialText ?? ""; string inputText = args.InitialText ?? string.Empty;
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {

View File

@@ -4,7 +4,7 @@
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:applet="using:Ryujinx.Ava.UI.Applet" xmlns:applet="using:Ryujinx.Ava.UI.Applet"
mc:Ignorable="d" mc:Ignorable="d"
Width="400" Width="400"
@@ -25,11 +25,11 @@
Spacing="10" Spacing="10"
Margin="10"> Margin="10">
<TextBlock <TextBlock
Text="{locale:Locale ControllerAppletDescription}" /> Text="{ext:Locale ControllerAppletDescription}" />
<TextBlock <TextBlock
IsVisible="{Binding IsDocked}" IsVisible="{Binding IsDocked}"
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale ControllerAppletDocked}" /> Text="{ext:Locale ControllerAppletDocked}" />
</StackPanel> </StackPanel>
</Border> </Border>
<Border <Border
@@ -48,7 +48,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" TextAlignment="Center"
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale ControllerAppletControllers}" /> Text="{ext:Locale ControllerAppletControllers}" />
<StackPanel <StackPanel
Spacing="10" Spacing="10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -96,7 +96,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
TextAlignment="Center" TextAlignment="Center"
FontWeight="Bold" FontWeight="Bold"
Text="{locale:Locale ControllerAppletPlayers}" /> Text="{ext:Locale ControllerAppletPlayers}" />
<Border Height="50"> <Border Height="50">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -121,13 +121,13 @@
Name="SaveButton" Name="SaveButton"
MinWidth="90" MinWidth="90"
Command="{Binding OpenSettingsWindow}"> Command="{Binding OpenSettingsWindow}">
<TextBlock Text="{locale:Locale DialogOpenSettingsWindowLabel}" /> <TextBlock Text="{ext:Locale DialogOpenSettingsWindowLabel}" />
</Button> </Button>
<Button <Button
Name="CancelButton" Name="CancelButton"
MinWidth="90" MinWidth="90"
Command="{Binding Close}"> Command="{Binding Close}">
<TextBlock Text="{locale:Locale SettingsButtonClose}" /> <TextBlock Text="{ext:Locale SettingsButtonClose}" />
</Button> </Button>
</StackPanel> </StackPanel>
</Panel> </Panel>

View File

@@ -26,7 +26,7 @@ namespace Ryujinx.Ava.UI.Applet
public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource); public static SvgImage JoyconLeftImage => GetResource(JoyConLeftResource);
public static SvgImage JoyconRightImage => GetResource(JoyConRightResource); public static SvgImage JoyconRightImage => GetResource(JoyConRightResource);
public string PlayerCount { get; set; } = ""; public string PlayerCount { get; set; } = string.Empty;
public bool SupportsProController { get; set; } public bool SupportsProController { get; set; }
public bool SupportsLeftJoycon { get; set; } public bool SupportsLeftJoycon { get; set; }
public bool SupportsRightJoycon { get; set; } public bool SupportsRightJoycon { get; set; }

View File

@@ -3,9 +3,9 @@
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="{locale:Locale ErrorWindowTitle}" Title="{ext:Locale ErrorWindowTitle}"
xmlns:views="using:Ryujinx.Ava.UI.Applet" xmlns:views="using:Ryujinx.Ava.UI.Applet"
Width="450" Width="450"
Height="340" Height="340"

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Ava.UI.Controls
{ {
MainText = mainText; MainText = mainText;
SecondaryText = secondaryText; SecondaryText = secondaryText;
Message = message ?? ""; Message = message ?? string.Empty;
DataContext = this; DataContext = this;
_placeholder = placeholder; _placeholder = placeholder;
InitializeComponent(); InitializeComponent();
@@ -55,9 +55,9 @@ namespace Ryujinx.Ava.UI.Controls
Input.Focus(); Input.Focus();
} }
public string Message { get; set; } = ""; public string Message { get; set; } = string.Empty;
public string MainText { get; set; } = ""; public string MainText { get; set; } = string.Empty;
public string SecondaryText { get; set; } = ""; public string SecondaryText { get; set; } = string.Empty;
public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args) public static async Task<(UserResult Result, string Input)> ShowInputDialog(string title, SoftwareKeyboardUIArgs args)
{ {
@@ -76,7 +76,7 @@ namespace Ryujinx.Ava.UI.Controls
contentDialog.Title = title; contentDialog.Title = title;
contentDialog.PrimaryButtonText = args.SubmitText; contentDialog.PrimaryButtonText = args.SubmitText;
contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length); contentDialog.IsPrimaryButtonEnabled = content._checkLength(content.Message.Length);
contentDialog.SecondaryButtonText = ""; contentDialog.SecondaryButtonText = string.Empty;
contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel]; contentDialog.CloseButtonText = LocaleManager.Instance[LocaleKeys.InputDialogCancel];
contentDialog.Content = content; contentDialog.Content = content;
@@ -110,13 +110,13 @@ namespace Ryujinx.Ava.UI.Controls
Error.IsVisible = false; Error.IsVisible = false;
Error.FontStyle = FontStyle.Italic; Error.FontStyle = FontStyle.Italic;
string validationInfoText = ""; string validationInfoText = string.Empty;
if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable. if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
{ {
Error.IsVisible = false; Error.IsVisible = false;
_checkLength = length => true; _checkLength = _ => true;
} }
else if (_inputMin > 0 && _inputMax == int.MaxValue) else if (_inputMin > 0 && _inputMax == int.MaxValue)
{ {

View File

@@ -2,109 +2,114 @@
x:Class="Ryujinx.Ava.UI.Controls.ApplicationContextMenu" x:Class="Ryujinx.Ava.UI.Controls.ApplicationContextMenu"
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:icon="clr-namespace:Ryujinx.Ava.Common.Icon"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
x:DataType="viewModels:MainWindowViewModel"> x:DataType="viewModels:MainWindowViewModel">
<MenuItem <MenuItem
Click="RunApplication_Click" Click="RunApplication_Click"
Header="{locale:Locale GameListContextMenuRunApplication}" Header="{ext:Locale GameListContextMenuRunApplication}"
Icon="{icon:Icon fa-solid fa-play}"/> Icon="{ext:Icon fa-solid fa-play}"/>
<MenuItem <MenuItem
Click="ToggleFavorite_Click" Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{ext:Locale GameListContextMenuToggleFavorite}"
Icon="{icon:Icon fa-solid fa-star}" Icon="{ext:Icon fa-solid fa-star}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuToggleFavoriteToolTip}" />
<MenuItem <MenuItem
Click="CreateApplicationShortcut_Click" Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}" Header="{ext:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}" IsEnabled="{Binding CreateShortcutEnabled}"
Icon="{icon:Icon fa-solid fa-bookmark}" Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" /> ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenUserSaveDirectory_Click" Click="OpenUserSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}" Header="{ext:Locale GameListContextMenuOpenUserSaveDirectory}"
Icon="{icon:Icon mdi-folder-account}" Icon="{ext:Icon mdi-folder-account}"
IsEnabled="{Binding OpenUserSaveDirectoryEnabled}" IsEnabled="{Binding OpenUserSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenDeviceSaveDirectory_Click" Click="OpenDeviceSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenDeviceSaveDirectory}" Header="{ext:Locale GameListContextMenuOpenDeviceSaveDirectory}"
IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}" IsEnabled="{Binding OpenDeviceSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuOpenDeviceSaveDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenBcatSaveDirectory_Click" Click="OpenBcatSaveDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenBcatSaveDirectory}" Header="{ext:Locale GameListContextMenuOpenBcatSaveDirectory}"
IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}" IsEnabled="{Binding OpenBcatSaveDirectoryEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuOpenBcatSaveDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenTitleUpdateManager_Click" Click="OpenTitleUpdateManager_Click"
Header="{locale:Locale GameListContextMenuManageTitleUpdates}" Header="{ext:Locale GameListContextMenuManageTitleUpdates}"
Icon="{icon:Icon fa-solid fa-code-compare}" Icon="{ext:Icon fa-solid fa-code-compare}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
<MenuItem <MenuItem
Click="OpenDownloadableContentManager_Click" Click="OpenDownloadableContentManager_Click"
Header="{locale:Locale GameListContextMenuManageDlc}" Header="{ext:Locale GameListContextMenuManageDlc}"
Icon="{icon:Icon fa-solid fa-download}" Icon="{ext:Icon fa-solid fa-download}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuManageDlcToolTip}" />
<MenuItem <MenuItem
Click="OpenCheatManager_Click" Click="OpenCheatManager_Click"
Header="{locale:Locale GameListContextMenuManageCheat}" Header="{ext:Locale GameListContextMenuManageCheat}"
Icon="{icon:Icon fa-solid fa-code}" Icon="{ext:Icon fa-solid fa-code}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuManageCheatToolTip}" />
<MenuItem <MenuItem
Click="OpenModManager_Click" Click="OpenModManager_Click"
Header="{locale:Locale GameListContextMenuManageMod}" Header="{ext:Locale GameListContextMenuManageMod}"
Icon="{icon:Icon mdi-view-module}" Icon="{ext:Icon mdi-view-module}"
ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuManageModToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenModsDirectory_Click" Click="OpenModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenModsDirectory}" Header="{ext:Locale GameListContextMenuOpenModsDirectory}"
Icon="{icon:Icon mdi-folder-file}" Icon="{ext:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenSdModsDirectory_Click" Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}" Header="{ext:Locale GameListContextMenuOpenSdModsDirectory}"
Icon="{icon:Icon mdi-folder-file}" Icon="{ext:Icon mdi-folder-file}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator /> <Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}" Icon="{icon:Icon mdi-cached}"> <MenuItem
Click="TrimXCI_Click"
Header="{ext:Locale GameListContextMenuTrimXCI}"
IsEnabled="{Binding TrimXCIEnabled}"
ToolTip.Tip="{ext:Locale GameListContextMenuTrimXCIToolTip}" />
<Separator />
<MenuItem Header="{ext:Locale GameListContextMenuCacheManagement}" Icon="{ext:Icon mdi-cached}">
<MenuItem <MenuItem
Click="PurgePtcCache_Click" Click="PurgePtcCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}" Header="{ext:Locale GameListContextMenuCacheManagementPurgePptc}"
Icon="{icon:Icon mdi-refresh}" Icon="{ext:Icon mdi-refresh}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
<MenuItem <MenuItem
Click="PurgeShaderCache_Click" Click="PurgeShaderCache_Click"
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}" Header="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
Icon="{icon:Icon mdi-delete-alert}" Icon="{ext:Icon mdi-delete-alert}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
<MenuItem <MenuItem
Click="OpenPtcDirectory_Click" Click="OpenPtcDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}" Header="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}" Icon="{ext:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
<MenuItem <MenuItem
Click="OpenShaderCacheDirectory_Click" Click="OpenShaderCacheDirectory_Click"
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}" Header="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
Icon="{icon:Icon mdi-folder-arrow-up-down}" Icon="{ext:Icon mdi-folder-arrow-up-down}"
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
</MenuItem> </MenuItem>
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}"> <MenuItem Header="{ext:Locale GameListContextMenuExtractData}">
<MenuItem <MenuItem
Click="ExtractApplicationExeFs_Click" Click="ExtractApplicationExeFs_Click"
Header="{locale:Locale GameListContextMenuExtractDataExeFS}" Header="{ext:Locale GameListContextMenuExtractDataExeFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataExeFSToolTip}" />
<MenuItem <MenuItem
Click="ExtractApplicationRomFs_Click" Click="ExtractApplicationRomFs_Click"
Header="{locale:Locale GameListContextMenuExtractDataRomFS}" Header="{ext:Locale GameListContextMenuExtractDataRomFS}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataRomFSToolTip}" />
<MenuItem <MenuItem
Click="ExtractApplicationLogo_Click" Click="ExtractApplicationLogo_Click"
Header="{locale:Locale GameListContextMenuExtractDataLogo}" Header="{ext:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> ToolTip.Tip="{ext:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem> </MenuItem>
</MenuFlyout> </MenuFlyout>

View File

@@ -2,6 +2,7 @@ using Avalonia.Controls;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Threading;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common; using Ryujinx.Ava.Common;
@@ -17,6 +18,8 @@ using SkiaSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.Controls namespace Ryujinx.Ava.UI.Controls
@@ -323,5 +326,15 @@ namespace Ryujinx.Ava.UI.Controls
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel }) if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await viewModel.LoadApplication(viewModel.SelectedApplication); await viewModel.LoadApplication(viewModel.SelectedApplication);
} }
public async void TrimXCI_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
}
}
} }
} }

View File

@@ -55,14 +55,7 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
ClipToBounds="True" ClipToBounds="True"
CornerRadius="5"> CornerRadius="5">
<Grid> <Grid ColumnDefinitions="Auto,10,*,150,100">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="150" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Image <Image
Grid.RowSpan="3" Grid.RowSpan="3"
Grid.Column="0" Grid.Column="0"

View File

@@ -138,7 +138,7 @@ namespace Ryujinx.Ava.UI.Controls
foreach (var account in lostAccounts) foreach (var account in lostAccounts)
{ {
ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, "", null), this)); ViewModel.LostProfiles.Add(new UserProfile(new HLE.HOS.Services.Account.Acc.UserProfile(account, string.Empty, null), this));
} }
ViewModel.Profiles.Add(new BaseModel()); ViewModel.Profiles.Add(new BaseModel());
@@ -155,14 +155,11 @@ namespace Ryujinx.Ava.UI.Controls
if (profile == null) if (profile == null)
{ {
Dispatcher.UIThread.Post(Action); _ = Dispatcher.UIThread.InvokeAsync(async ()
=> await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]));
return; return;
static async void Action()
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionWarningMessage]);
}
} }
AccountManager.OpenUser(profile.UserId); AccountManager.OpenUser(profile.UserId);
@@ -170,10 +167,10 @@ namespace Ryujinx.Ava.UI.Controls
var result = await ContentDialogHelper.CreateConfirmationDialog( var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage], LocaleManager.Instance[LocaleKeys.DialogUserProfileDeletionConfirmMessage],
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogYes],
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
""); string.Empty);
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {

View File

@@ -0,0 +1,62 @@
using Avalonia.Collections;
using System.Collections.Generic;
namespace Ryujinx.Ava.UI.Helpers
{
public static class AvaloniaListExtensions
{
/// <summary>
/// Adds or Replaces an item in an AvaloniaList irrespective of whether the item already exists
/// </summary>
/// <typeparam name="T">The type of the element in the AvaoloniaList</typeparam>
/// <param name="list">The list containing the item to replace</param>
/// <param name="item">The item to replace</param>
/// <param name="addIfNotFound">True to add the item if its not found</param>
/// <returns>True if the item was found and replaced, false if it was addded</returns>
/// <remarks>
/// The indexes on the AvaloniaList will only replace if the item does not match,
/// this causes the items to not be replaced if the Equality is customised on the
/// items. This method will instead find, remove and add the item to ensure it is
/// replaced correctly.
/// </remarks>
public static bool ReplaceWith<T>(this AvaloniaList<T> list, T item, bool addIfNotFound = true)
{
var index = list.IndexOf(item);
if (index != -1)
{
list.RemoveAt(index);
list.Insert(index, item);
return true;
}
else
{
list.Add(item);
return false;
}
}
/// <summary>
/// Adds or Replaces items in an AvaloniaList from another list irrespective of whether the item already exists
/// </summary>
/// <typeparam name="T">The type of the element in the AvaoloniaList</typeparam>
/// <param name="list">The list containing the item to replace</param>
/// <param name="sourceList">The list of items to be actually added to `list`</param>
/// <param name="matchingList">The items to use as matching records to search for in the `sourceList', if not found this item will be added instead</params>
public static void AddOrReplaceMatching<T>(this AvaloniaList<T> list, IList<T> sourceList, IList<T> matchingList)
{
foreach (var match in matchingList)
{
var index = sourceList.IndexOf(match);
if (index != -1)
{
list.ReplaceWith(sourceList[index]);
}
else
{
list.ReplaceWith(match);
}
}
}
}
}

View File

@@ -205,7 +205,7 @@ namespace Ryujinx.Ava.UI.Helpers
primary, primary,
secondaryText, secondaryText,
acceptButton, acceptButton,
"", string.Empty,
closeButton, closeButton,
(int)Symbol.Important); (int)Symbol.Important);
@@ -221,7 +221,7 @@ namespace Ryujinx.Ava.UI.Helpers
primaryText, primaryText,
secondaryText, secondaryText,
acceptButtonText, acceptButtonText,
"", string.Empty,
cancelButtonText, cancelButtonText,
(int)Symbol.Help, (int)Symbol.Help,
primaryButtonResult); primaryButtonResult);
@@ -239,8 +239,8 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterTitle],
primary, primary,
secondaryText, secondaryText,
"", string.Empty,
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important); (int)Symbol.Important);
@@ -249,8 +249,8 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.DialogWarningTitle], LocaleManager.Instance[LocaleKeys.DialogWarningTitle],
primary, primary,
secondaryText, secondaryText,
"", string.Empty,
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Important); (int)Symbol.Important);
@@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.Helpers
LocaleManager.Instance[LocaleKeys.DialogErrorMessage], LocaleManager.Instance[LocaleKeys.DialogErrorMessage],
errorMessage, errorMessage,
secondaryErrorMessage, secondaryErrorMessage,
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Dismiss); (int)Symbol.Dismiss);
} }
@@ -282,7 +282,7 @@ namespace Ryujinx.Ava.UI.Helpers
primary, primary,
secondaryText, secondaryText,
LocaleManager.Instance[LocaleKeys.InputDialogYes], LocaleManager.Instance[LocaleKeys.InputDialogYes],
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogNo], LocaleManager.Instance[LocaleKeys.InputDialogNo],
(int)Symbol.Help, (int)Symbol.Help,
UserResult.Yes); UserResult.Yes);

View File

@@ -122,7 +122,7 @@ namespace Ryujinx.Ava.UI.Helpers
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
string keyString = ""; string keyString = string.Empty;
LocaleKeys localeKey; LocaleKeys localeKey;
switch (value) switch (value)

View File

@@ -40,7 +40,7 @@ namespace Ryujinx.Ava.UI.Helpers
await ContentDialogHelper.CreateInfoDialog( await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)), LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogMessage, errorCode, GetErrorTitle(error)),
GetErrorDescription(error), GetErrorDescription(error),
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode)); LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogUserErrorDialogTitle, errorCode));
} }

View File

@@ -0,0 +1,48 @@
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common.Models;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class XCITrimmerFileSpaceSavingsConverter : IValueConverter
{
private const long _bytesPerMB = 1024 * 1024;
public static XCITrimmerFileSpaceSavingsConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is UnsetValueType)
{
return BindingOperations.DoNothing;
}
if (!targetType.IsAssignableFrom(typeof(string)))
{
return null;
}
if (value is not XCITrimmerFileModel app)
{
return null;
}
if (app.CurrentSavingsB < app.PotentialSavingsB)
{
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCICanSaveLabel, (app.PotentialSavingsB - app.CurrentSavingsB) / _bytesPerMB);
}
else
{
return LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TitleXCISavingLabel, app.CurrentSavingsB / _bytesPerMB);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,46 @@
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.UI.Common.Models;
using System;
using System.Globalization;
using static Ryujinx.Common.Utilities.XCIFileTrimmer;
namespace Ryujinx.Ava.UI.Helpers
{
internal class XCITrimmerFileStatusConverter : IValueConverter
{
public static XCITrimmerFileStatusConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is UnsetValueType)
{
return BindingOperations.DoNothing;
}
if (!targetType.IsAssignableFrom(typeof(string)))
{
return null;
}
if (value is not XCITrimmerFileModel app)
{
return null;
}
return app.PercentageProgress != null ? String.Empty :
app.ProcessingOutcome != OperationOutcome.Successful && app.ProcessingOutcome != OperationOutcome.Undetermined ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusFailedLabel] :
app.Trimmable & app.Untrimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusPartialLabel] :
app.Trimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusTrimmableLabel] :
app.Untrimmable ? LocaleManager.Instance[LocaleKeys.TitleXCIStatusUntrimmableLabel] :
String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,42 @@
using Avalonia;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Ryujinx.UI.Common.Models;
using System;
using System.Globalization;
using static Ryujinx.Common.Utilities.XCIFileTrimmer;
namespace Ryujinx.Ava.UI.Helpers
{
internal class XCITrimmerFileStatusDetailConverter : IValueConverter
{
public static XCITrimmerFileStatusDetailConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is UnsetValueType)
{
return BindingOperations.DoNothing;
}
if (!targetType.IsAssignableFrom(typeof(string)))
{
return null;
}
if (value is not XCITrimmerFileModel app)
{
return null;
}
return app.PercentageProgress != null ? null :
app.ProcessingOutcome != OperationOutcome.Successful && app.ProcessingOutcome != OperationOutcome.Undetermined ? app.ProcessingOutcome.ToLocalisedText() :
null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,36 @@
using Ryujinx.Ava.Common.Locale;
using static Ryujinx.Common.Utilities.XCIFileTrimmer;
namespace Ryujinx.Ava.UI.Helpers
{
public static class XCIFileTrimmerOperationOutcomeExtensions
{
public static string ToLocalisedText(this OperationOutcome operationOutcome)
{
switch (operationOutcome)
{
case OperationOutcome.NoTrimNecessary:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary];
case OperationOutcome.NoUntrimPossible:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileNoUntrimPossible];
case OperationOutcome.ReadOnlyFileCannotFix:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileReadOnlyFileCannotFix];
case OperationOutcome.FreeSpaceCheckFailed:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileFreeSpaceCheckFailed];
case OperationOutcome.InvalidXCIFile:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile];
case OperationOutcome.FileIOWriteError:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError];
case OperationOutcome.FileSizeChanged:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged];
case OperationOutcome.Cancelled:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileCancelled];
case OperationOutcome.Undetermined:
return LocaleManager.Instance[LocaleKeys.TrimXCIFileFileUndertermined];
case OperationOutcome.Successful:
default:
return null;
}
}
}
}

View File

@@ -320,7 +320,7 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
if (LastScannedAmiiboId != "") if (LastScannedAmiiboId != string.Empty)
{ {
SelectLastScannedAmiibo(); SelectLastScannedAmiibo();
} }
@@ -421,7 +421,7 @@ namespace Ryujinx.Ava.UI.ViewModels
usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}."); usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}.");
} }
Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}"; Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : string.Empty)} : {usageStringBuilder}";
} }
} }
@@ -480,7 +480,7 @@ namespace Ryujinx.Ava.UI.ViewModels
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage], LocaleManager.Instance[LocaleKeys.DialogAmiiboApiFailFetchMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
"", string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]); LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
return null; return null;
@@ -530,7 +530,7 @@ namespace Ryujinx.Ava.UI.ViewModels
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle], await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage], LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
"", string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]); LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
} }
} }

View File

@@ -287,7 +287,14 @@ namespace Ryujinx.Ava.UI.ViewModels
var msg = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowDlcAddedMessage], numAdded); var msg = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowDlcAddedMessage], numAdded);
return Dispatcher.UIThread.InvokeAsync(async () => return Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark); await ContentDialogHelper.ShowTextDialog(
LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
msg,
string.Empty,
string.Empty,
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Checkmark);
}); });
} }
} }

View File

@@ -22,6 +22,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
@@ -29,7 +30,6 @@ using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.UI; using Ryujinx.HLE.UI;
using Ryujinx.Input.HLE; using Ryujinx.Input.HLE;
using Ryujinx.Modules;
using Ryujinx.UI.App.Common; using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common; using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Common.Configuration;
@@ -85,6 +85,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isAppletMenuActive; private bool _isAppletMenuActive;
private int _statusBarProgressMaximum; private int _statusBarProgressMaximum;
private int _statusBarProgressValue; private int _statusBarProgressValue;
private string _statusBarProgressStatusText;
private bool _statusBarProgressStatusVisible;
private bool _isPaused; private bool _isPaused;
private bool _showContent = true; private bool _showContent = true;
private bool _isLoadingIndeterminate = true; private bool _isLoadingIndeterminate = true;
@@ -392,6 +394,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool TrimXCIEnabled => Ryujinx.Common.Utilities.XCIFileTrimmer.CanTrim(SelectedApplication.Path, new Common.XCIFileTrimmerMainWindowLog(this));
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;
@@ -506,6 +510,28 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
public bool StatusBarProgressStatusVisible
{
get => _statusBarProgressStatusVisible;
set
{
_statusBarProgressStatusVisible = value;
OnPropertyChanged();
}
}
public string StatusBarProgressStatusText
{
get => _statusBarProgressStatusText;
set
{
_statusBarProgressStatusText = value;
OnPropertyChanged();
}
}
public string FifoStatusText public string FifoStatusText
{ {
get => _fifoStatusText; get => _fifoStatusText;
@@ -1092,7 +1118,12 @@ namespace Ryujinx.Ava.UI.ViewModels
string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); await ContentDialogHelper.CreateInfoDialog(
dialogTitle,
message,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
Logger.Info?.Print(LogClass.Application, message); Logger.Info?.Print(LogClass.Application, message);
@@ -1163,7 +1194,7 @@ namespace Ryujinx.Ava.UI.ViewModels
case LoadState.Loaded: case LoadState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = string.Empty;
break; break;
} }
break; break;
@@ -1183,7 +1214,7 @@ namespace Ryujinx.Ava.UI.ViewModels
case ShaderCacheLoadingState.Loaded: case ShaderCacheLoadingState.Loaded:
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = string.Empty;
break; break;
} }
break; break;
@@ -1303,7 +1334,12 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
await ContentDialogHelper.ShowTextDialog( await ContentDialogHelper.ShowTextDialog(
LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo], LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark); msg,
string.Empty,
string.Empty,
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Checkmark);
}); });
} }
} }
@@ -1600,7 +1636,7 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage], LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage],
LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage], LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage],
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
"", string.Empty,
LocaleManager.Instance[LocaleKeys.RyujinxInfo]); LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
return; return;
@@ -1767,6 +1803,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (WindowState is not WindowState.Normal) if (WindowState is not WindowState.Normal)
{ {
WindowState = WindowState.Normal; WindowState = WindowState.Normal;
Window.TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
if (IsGameRunning) if (IsGameRunning)
{ {
@@ -1776,6 +1813,7 @@ namespace Ryujinx.Ava.UI.ViewModels
else else
{ {
WindowState = WindowState.FullScreen; WindowState = WindowState.FullScreen;
Window.TitleBar.ExtendsContentIntoTitleBar = true;
if (IsGameRunning) if (IsGameRunning)
{ {
@@ -1817,12 +1855,104 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result == UserResult.Yes) if (result == UserResult.Yes)
{ {
ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = string.Empty;
SaveConfig(); SaveConfig();
} }
} }
} }
public async void ProcessTrimResult(String filename, Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome operationOutcome)
{
string notifyUser = operationOutcome.ToLocalisedText();
if (notifyUser != null)
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileFailedPrimaryText],
notifyUser
);
}
else
{
switch (operationOutcome)
{
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.Successful:
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (desktop.MainWindow is MainWindow mainWindow)
mainWindow.LoadApplications();
}
break;
}
}
}
public async Task TrimXCIFile(string filename)
{
if (filename == null)
{
return;
}
var trimmer = new XCIFileTrimmer(filename, new Common.XCIFileTrimmerMainWindowLog(this));
if (trimmer.CanBeTrimmed)
{
var savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0;
var currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0;
var cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0;
string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings);
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText],
secondaryText,
LocaleManager.Instance[LocaleKeys.Continue],
LocaleManager.Instance[LocaleKeys.Cancel],
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogTitle]
);
if (result == UserResult.Yes)
{
Thread XCIFileTrimThread = new(() =>
{
Dispatcher.UIThread.Post(() =>
{
StatusBarProgressStatusText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, Path.GetFileName(filename));
StatusBarProgressStatusVisible = true;
StatusBarProgressMaximum = 1;
StatusBarProgressValue = 0;
StatusBarVisible = true;
});
try
{
XCIFileTrimmer.OperationOutcome operationOutcome = trimmer.Trim();
Dispatcher.UIThread.Post(() =>
{
ProcessTrimResult(filename, operationOutcome);
});
}
finally
{
Dispatcher.UIThread.Post(() =>
{
StatusBarProgressStatusVisible = false;
StatusBarProgressStatusText = string.Empty;
StatusBarVisible = false;
});
}
})
{
Name = "GUI.XCIFileTrimmerThread",
IsBackground = true,
};
XCIFileTrimThread.Start();
}
}
}
#endregion #endregion
} }
} }

View File

@@ -80,8 +80,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
Dispatcher.UIThread.InvokeAsync(() => Dispatcher.UIThread.InvokeAsync(() =>
ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage],
"", string.Empty,
"", string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk], LocaleManager.Instance[LocaleKeys.InputDialogOk],
LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle])
); );
@@ -338,7 +338,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
_gpuIds.Add(device.Id); _gpuIds.Add(device.Id);
AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : "")}" }); AvailableGpus.Add(new ComboBoxItem { Content = $"{device.Name} {(device.IsDiscrete ? "(dGPU)" : string.Empty)}" });
}); });
} }
} }

View File

@@ -227,10 +227,16 @@ namespace Ryujinx.Ava.UI.ViewModels
private Task ShowNewUpdatesAddedDialog(int numAdded) private Task ShowNewUpdatesAddedDialog(int numAdded)
{ {
var msg = string.Format(LocaleManager.Instance[LocaleKeys.UpdateWindowUpdateAddedMessage], numAdded); var msg = string.Format(LocaleManager.Instance[LocaleKeys.UpdateWindowUpdateAddedMessage], numAdded);
return Dispatcher.UIThread.InvokeAsync(async () => return Dispatcher.UIThread.InvokeAsync(async () =>
{ await ContentDialogHelper.ShowTextDialog(
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark); LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
}); msg,
string.Empty,
string.Empty,
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
(int)Symbol.Checkmark
));
} }
} }
} }

View File

@@ -0,0 +1,541 @@
using Avalonia.Collections;
using DynamicData;
using Gommon;
using Avalonia.Threading;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common.Utilities;
using Ryujinx.UI.App.Common;
using Ryujinx.UI.Common.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static Ryujinx.Common.Utilities.XCIFileTrimmer;
namespace Ryujinx.Ava.UI.ViewModels
{
public class XCITrimmerViewModel : BaseModel
{
private const long _bytesPerMB = 1024 * 1024;
private enum ProcessingMode
{
Trimming,
Untrimming
}
public enum SortField
{
Name,
Saved
}
private const string _FileExtXCI = "XCI";
private readonly Ryujinx.Common.Logging.XCIFileTrimmerLog _logger;
private readonly ApplicationLibrary _applicationLibrary;
private Optional<XCITrimmerFileModel> _processingApplication = null;
private AvaloniaList<XCITrimmerFileModel> _allXCIFiles = new();
private AvaloniaList<XCITrimmerFileModel> _selectedXCIFiles = new();
private AvaloniaList<XCITrimmerFileModel> _displayedXCIFiles = new();
private MainWindowViewModel _mainWindowViewModel;
private CancellationTokenSource _cancellationTokenSource;
private string _search;
private ProcessingMode _processingMode;
private SortField _sortField = SortField.Name;
private bool _sortAscending = true;
public XCITrimmerViewModel(MainWindowViewModel mainWindowViewModel)
{
_logger = new XCIFileTrimmerWindowLog(this);
_mainWindowViewModel = mainWindowViewModel;
_applicationLibrary = _mainWindowViewModel.ApplicationLibrary;
LoadXCIApplications();
}
private void LoadXCIApplications()
{
var apps = _applicationLibrary.Applications.Items
.Where(app => app.FileExtension == _FileExtXCI);
foreach (var xciApp in apps)
AddOrUpdateXCITrimmerFile(CreateXCITrimmerFile(xciApp.Path));
ApplicationsChanged();
}
private XCITrimmerFileModel CreateXCITrimmerFile(
string path,
OperationOutcome operationOutcome = OperationOutcome.Undetermined)
{
var xciApp = _applicationLibrary.Applications.Items.First(app => app.FileExtension == _FileExtXCI && app.Path == path);
return XCITrimmerFileModel.FromApplicationData(xciApp, _logger) with { ProcessingOutcome = operationOutcome };
}
private bool AddOrUpdateXCITrimmerFile(XCITrimmerFileModel xci, bool suppressChanged = true, bool autoSelect = true)
{
bool replaced = _allXCIFiles.ReplaceWith(xci);
_displayedXCIFiles.ReplaceWith(xci, Filter(xci));
_selectedXCIFiles.ReplaceWith(xci, xci.Trimmable && autoSelect);
if (!suppressChanged)
ApplicationsChanged();
return replaced;
}
private void FilteringChanged()
{
OnPropertyChanged(nameof(Search));
SortAndFilter();
}
private void SortingChanged()
{
OnPropertyChanged(nameof(IsSortedByName));
OnPropertyChanged(nameof(IsSortedBySaved));
OnPropertyChanged(nameof(SortingAscending));
OnPropertyChanged(nameof(SortingField));
OnPropertyChanged(nameof(SortingFieldName));
SortAndFilter();
}
private void DisplayedChanged()
{
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(DisplayedXCIFiles));
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
}
private void ApplicationsChanged()
{
OnPropertyChanged(nameof(AllXCIFiles));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(PotentialSavings));
OnPropertyChanged(nameof(ActualSavings));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
DisplayedChanged();
SortAndFilter();
}
private void SelectionChanged(bool displayedChanged = true)
{
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
OnPropertyChanged(nameof(SelectedXCIFiles));
if (displayedChanged)
OnPropertyChanged(nameof(SelectedDisplayedXCIFiles));
}
private void ProcessingChanged()
{
OnPropertyChanged(nameof(Processing));
OnPropertyChanged(nameof(Cancel));
OnPropertyChanged(nameof(Status));
OnPropertyChanged(nameof(CanTrim));
OnPropertyChanged(nameof(CanUntrim));
}
private IEnumerable<XCITrimmerFileModel> GetSelectedDisplayedXCIFiles()
{
return _displayedXCIFiles.Where(xci => _selectedXCIFiles.Contains(xci));
}
private void PerformOperation(ProcessingMode processingMode)
{
if (Processing)
{
return;
}
_processingMode = processingMode;
Processing = true;
var cancellationToken = _cancellationTokenSource.Token;
Thread XCIFileTrimThread = new(() =>
{
var toProcess = Sort(SelectedXCIFiles
.Where(xci =>
(processingMode == ProcessingMode.Untrimming && xci.Untrimmable) ||
(processingMode == ProcessingMode.Trimming && xci.Trimmable)
)).ToList();
var viewsSaved = DisplayedXCIFiles.ToList();
Dispatcher.UIThread.Post(() =>
{
_selectedXCIFiles.Clear();
_displayedXCIFiles.Clear();
_displayedXCIFiles.AddRange(toProcess);
});
try
{
foreach (var xciApp in toProcess)
{
if (cancellationToken.IsCancellationRequested)
break;
var trimmer = new XCIFileTrimmer(xciApp.Path, _logger);
Dispatcher.UIThread.Post(() =>
{
ProcessingApplication = xciApp;
});
var outcome = OperationOutcome.Undetermined;
try
{
if (cancellationToken.IsCancellationRequested)
break;
switch (processingMode)
{
case ProcessingMode.Trimming:
outcome = trimmer.Trim(cancellationToken);
break;
case ProcessingMode.Untrimming:
outcome = trimmer.Untrim(cancellationToken);
break;
}
if (outcome == OperationOutcome.Cancelled)
outcome = OperationOutcome.Undetermined;
}
finally
{
Dispatcher.UIThread.Post(() =>
{
ProcessingApplication = CreateXCITrimmerFile(xciApp.Path);
AddOrUpdateXCITrimmerFile(ProcessingApplication, false, false);
ProcessingApplication = null;
});
}
}
}
finally
{
Dispatcher.UIThread.Post(() =>
{
_displayedXCIFiles.AddOrReplaceMatching(_allXCIFiles, viewsSaved);
_selectedXCIFiles.AddOrReplaceMatching(_allXCIFiles, toProcess);
Processing = false;
ApplicationsChanged();
});
}
})
{
Name = "GUI.XCIFilesTrimmerThread",
IsBackground = true,
};
XCIFileTrimThread.Start();
}
private bool Filter<T>(T arg)
{
if (arg is XCITrimmerFileModel content)
{
return string.IsNullOrWhiteSpace(_search)
|| content.Name.ToLower().Contains(_search.ToLower())
|| content.Path.ToLower().Contains(_search.ToLower());
}
return false;
}
private class CompareXCITrimmerFiles : IComparer<XCITrimmerFileModel>
{
private XCITrimmerViewModel _viewModel;
public CompareXCITrimmerFiles(XCITrimmerViewModel ViewModel)
{
_viewModel = ViewModel;
}
public int Compare(XCITrimmerFileModel x, XCITrimmerFileModel y)
{
int result = 0;
switch (_viewModel.SortingField)
{
case SortField.Name:
result = x.Name.CompareTo(y.Name);
break;
case SortField.Saved:
result = x.PotentialSavingsB.CompareTo(y.PotentialSavingsB);
break;
}
if (!_viewModel.SortingAscending)
result = -result;
if (result == 0)
result = x.Path.CompareTo(y.Path);
return result;
}
}
private IOrderedEnumerable<XCITrimmerFileModel> Sort(IEnumerable<XCITrimmerFileModel> list)
{
return list
.OrderBy(xci => xci, new CompareXCITrimmerFiles(this))
.ThenBy(it => it.Path);
}
public void TrimSelected()
{
PerformOperation(ProcessingMode.Trimming);
}
public void UntrimSelected()
{
PerformOperation(ProcessingMode.Untrimming);
}
public void SetProgress(int current, int maximum)
{
if (_processingApplication != null)
{
int percentageProgress = 100 * current / maximum;
if (!ProcessingApplication.HasValue || (ProcessingApplication.Value.PercentageProgress != percentageProgress))
ProcessingApplication = ProcessingApplication.Value with { PercentageProgress = percentageProgress };
}
}
public void SelectDisplayed()
{
SelectedXCIFiles.AddRange(DisplayedXCIFiles);
SelectionChanged();
}
public void DeselectDisplayed()
{
SelectedXCIFiles.RemoveMany(DisplayedXCIFiles);
SelectionChanged();
}
public void Select(XCITrimmerFileModel model)
{
bool selectionChanged = !SelectedXCIFiles.Contains(model);
bool displayedSelectionChanged = !SelectedDisplayedXCIFiles.Contains(model);
SelectedXCIFiles.ReplaceOrAdd(model, model);
if (selectionChanged)
SelectionChanged(displayedSelectionChanged);
}
public void Deselect(XCITrimmerFileModel model)
{
bool displayedSelectionChanged = !SelectedDisplayedXCIFiles.Contains(model);
if (SelectedXCIFiles.Remove(model))
SelectionChanged(displayedSelectionChanged);
}
public void SortAndFilter()
{
if (Processing)
return;
Sort(AllXCIFiles)
.AsObservableChangeSet()
.Filter(Filter)
.Bind(out var view).AsObservableList();
_displayedXCIFiles.Clear();
_displayedXCIFiles.AddRange(view);
DisplayedChanged();
}
public Optional<XCITrimmerFileModel> ProcessingApplication
{
get => _processingApplication;
set
{
if (!value.HasValue && _processingApplication.HasValue)
value = _processingApplication.Value with { PercentageProgress = null };
if (value.HasValue)
_displayedXCIFiles.ReplaceWith(value.Value);
_processingApplication = value;
OnPropertyChanged();
}
}
public bool Processing
{
get => _cancellationTokenSource != null;
private set
{
if (value && !Processing)
{
_cancellationTokenSource = new CancellationTokenSource();
}
else if (!value && Processing)
{
_cancellationTokenSource.Dispose();
_cancellationTokenSource = null;
}
ProcessingChanged();
}
}
public bool Cancel
{
get => _cancellationTokenSource != null && _cancellationTokenSource.IsCancellationRequested;
set
{
if (value)
{
if (!Processing)
return;
_cancellationTokenSource.Cancel();
}
ProcessingChanged();
}
}
public string Status
{
get
{
if (Processing)
{
return _processingMode switch
{
ProcessingMode.Trimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusTrimming], DisplayedXCIFiles.Count),
ProcessingMode.Untrimming => string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusUntrimming], DisplayedXCIFiles.Count),
_ => string.Empty
};
}
else
{
return string.IsNullOrEmpty(Search) ?
string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCount], SelectedXCIFiles.Count, AllXCIFiles.Count) :
string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerTitleStatusCountWithFilter], SelectedXCIFiles.Count, AllXCIFiles.Count, DisplayedXCIFiles.Count);
}
}
}
public string Search
{
get => _search;
set
{
_search = value;
FilteringChanged();
}
}
public SortField SortingField
{
get => _sortField;
set
{
_sortField = value;
SortingChanged();
}
}
public string SortingFieldName
{
get
{
return SortingField switch
{
SortField.Name => LocaleManager.Instance[LocaleKeys.XCITrimmerSortName],
SortField.Saved => LocaleManager.Instance[LocaleKeys.XCITrimmerSortSaved],
_ => string.Empty,
};
}
}
public bool SortingAscending
{
get => _sortAscending;
set
{
_sortAscending = value;
SortingChanged();
}
}
public bool IsSortedByName
{
get => _sortField == SortField.Name;
}
public bool IsSortedBySaved
{
get => _sortField == SortField.Saved;
}
public AvaloniaList<XCITrimmerFileModel> SelectedXCIFiles
{
get => _selectedXCIFiles;
set
{
_selectedXCIFiles = value;
SelectionChanged();
}
}
public AvaloniaList<XCITrimmerFileModel> AllXCIFiles
{
get => _allXCIFiles;
}
public AvaloniaList<XCITrimmerFileModel> DisplayedXCIFiles
{
get => _displayedXCIFiles;
}
public string PotentialSavings
{
get
{
return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.PotentialSavingsB / _bytesPerMB));
}
}
public string ActualSavings
{
get
{
return string.Format(LocaleManager.Instance[LocaleKeys.XCITrimmerSavingsMb], AllXCIFiles.Sum(xci => xci.CurrentSavingsB / _bytesPerMB));
}
}
public IEnumerable<XCITrimmerFileModel> SelectedDisplayedXCIFiles
{
get
{
return GetSelectedDisplayedXCIFiles().ToList();
}
}
public bool CanTrim
{
get
{
return !Processing && _selectedXCIFiles.Any(xci => xci.Trimmable);
}
}
public bool CanUntrim
{
get
{
return !Processing && _selectedXCIFiles.Any(xci => xci.Untrimmable);
}
}
}
}

View File

@@ -1,7 +1,7 @@
<UserControl <UserControl
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
@@ -73,7 +73,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}" Text="{ext:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonZl"> <ToggleButton Name="ButtonZl">
<TextBlock <TextBlock
@@ -89,7 +89,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}" Text="{ext:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonL"> <ToggleButton Name="ButtonL">
<TextBlock <TextBlock
@@ -105,7 +105,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}" Text="{ext:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonMinus"> <ToggleButton Name="ButtonMinus">
<TextBlock <TextBlock
@@ -128,7 +128,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" /> Text="{ext:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Controller --> <!-- Left Joystick Controller -->
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Left Joystick Button --> <!-- Left Joystick Button -->
@@ -139,7 +139,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{ext:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickButton"> <ToggleButton Name="LeftStickButton">
<TextBlock <TextBlock
@@ -156,7 +156,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}" Text="{ext:Locale ControllerSettingsStickStick}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftJoystick" Tag="stick"> <ToggleButton Name="LeftJoystick" Tag="stick">
<TextBlock <TextBlock
@@ -168,13 +168,13 @@
Margin="0,8,0,8" Margin="0,8,0,8"
Height="1" /> Height="1" />
<CheckBox IsChecked="{Binding Config.LeftInvertStickX}"> <CheckBox IsChecked="{Binding Config.LeftInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> <TextBlock Text="{ext:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding Config.LeftInvertStickY}"> <CheckBox IsChecked="{Binding Config.LeftInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> <TextBlock Text="{ext:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding Config.LeftRotate90}"> <CheckBox IsChecked="{Binding Config.LeftRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> <TextBlock Text="{ext:Locale ControllerSettingsRotate90}" />
</CheckBox> </CheckBox>
<Separator <Separator
Margin="0,8,0,8" Margin="0,8,0,8"
@@ -182,7 +182,7 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDeadzone}" /> Text="{ext:Locale ControllerSettingsStickDeadzone}" />
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -202,7 +202,7 @@
</StackPanel> </StackPanel>
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRange}" /> Text="{ext:Locale ControllerSettingsStickRange}" />
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -238,7 +238,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPad}" /> Text="{ext:Locale ControllerSettingsDPad}" />
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Left DPad Up --> <!-- Left DPad Up -->
<StackPanel <StackPanel
@@ -249,7 +249,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}" Text="{ext:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadUp"> <ToggleButton Name="DpadUp">
<TextBlock <TextBlock
@@ -266,7 +266,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}" Text="{ext:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadDown"> <ToggleButton Name="DpadDown">
<TextBlock <TextBlock
@@ -283,7 +283,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}" Text="{ext:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadLeft"> <ToggleButton Name="DpadLeft">
<TextBlock <TextBlock
@@ -300,7 +300,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}" Text="{ext:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadRight"> <ToggleButton Name="DpadRight">
<TextBlock <TextBlock
@@ -334,7 +334,7 @@
Orientation="Vertical"> Orientation="Vertical">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerThreshold}" /> Text="{ext:Locale ControllerSettingsTriggerThreshold}" />
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
@@ -363,7 +363,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}" Text="{ext:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftButtonSr"> <ToggleButton Name="LeftButtonSr">
<TextBlock <TextBlock
@@ -381,7 +381,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}" Text="{ext:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftButtonSl"> <ToggleButton Name="LeftButtonSl">
<TextBlock <TextBlock
@@ -399,7 +399,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}" Text="{ext:Locale ControllerSettingsRightSR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightButtonSr"> <ToggleButton Name="RightButtonSr">
<TextBlock <TextBlock
@@ -417,7 +417,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}" Text="{ext:Locale ControllerSettingsRightSL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightButtonSl"> <ToggleButton Name="RightButtonSl">
<TextBlock <TextBlock
@@ -450,13 +450,13 @@
MinWidth="0" MinWidth="0"
Grid.Column="0" Grid.Column="0"
IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}"> IsChecked="{Binding Config.EnableMotion, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsMotion}" /> <TextBlock Text="{ext:Locale ControllerSettingsMotion}" />
</CheckBox> </CheckBox>
<Button <Button
Margin="10" Margin="10"
Grid.Column="1" Grid.Column="1"
Command="{Binding ShowMotionConfig}"> Command="{Binding ShowMotionConfig}">
<TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> <TextBlock Text="{ext:Locale ControllerSettingsConfigureGeneral}" />
</Button> </Button>
</Grid> </Grid>
</Border> </Border>
@@ -476,13 +476,13 @@
MinWidth="0" MinWidth="0"
Grid.Column="0" Grid.Column="0"
IsChecked="{Binding Config.EnableRumble, Mode=TwoWay}"> IsChecked="{Binding Config.EnableRumble, Mode=TwoWay}">
<TextBlock Text="{locale:Locale ControllerSettingsRumble}" /> <TextBlock Text="{ext:Locale ControllerSettingsRumble}" />
</CheckBox> </CheckBox>
<Button <Button
Margin="10" Margin="10"
Grid.Column="1" Grid.Column="1"
Command="{Binding ShowRumbleConfig}"> Command="{Binding ShowRumbleConfig}">
<TextBlock Text="{locale:Locale ControllerSettingsConfigureGeneral}" /> <TextBlock Text="{ext:Locale ControllerSettingsConfigureGeneral}" />
</Button> </Button>
</Grid> </Grid>
</Border> </Border>
@@ -519,7 +519,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}" Text="{ext:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonZr"> <ToggleButton Name="ButtonZr">
<TextBlock <TextBlock
@@ -537,7 +537,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}" Text="{ext:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonR"> <ToggleButton Name="ButtonR">
<TextBlock <TextBlock
@@ -555,7 +555,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}" Text="{ext:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonPlus"> <ToggleButton Name="ButtonPlus">
<TextBlock <TextBlock
@@ -578,7 +578,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtons}" /> Text="{ext:Locale ControllerSettingsButtons}" />
<StackPanel <StackPanel
Orientation="Vertical"> Orientation="Vertical">
<!-- Right Buttons A --> <!-- Right Buttons A -->
@@ -590,7 +590,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}" Text="{ext:Locale ControllerSettingsButtonA}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonA"> <ToggleButton Name="ButtonA">
<TextBlock <TextBlock
@@ -607,7 +607,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}" Text="{ext:Locale ControllerSettingsButtonB}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonB"> <ToggleButton Name="ButtonB">
<TextBlock <TextBlock
@@ -624,7 +624,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}" Text="{ext:Locale ControllerSettingsButtonX}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonX"> <ToggleButton Name="ButtonX">
<TextBlock <TextBlock
@@ -641,7 +641,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}" Text="{ext:Locale ControllerSettingsButtonY}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonY"> <ToggleButton Name="ButtonY">
<TextBlock <TextBlock
@@ -664,7 +664,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" /> Text="{ext:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Controller --> <!-- Right Joystick Controller -->
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Right Joystick Button --> <!-- Right Joystick Button -->
@@ -675,7 +675,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{ext:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickButton"> <ToggleButton Name="RightStickButton">
<TextBlock <TextBlock
@@ -693,7 +693,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickStick}" Text="{ext:Locale ControllerSettingsStickStick}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightJoystick" Tag="stick"> <ToggleButton Name="RightJoystick" Tag="stick">
<TextBlock <TextBlock
@@ -703,19 +703,19 @@
</StackPanel> </StackPanel>
<Separator Margin="0,8,0,8" Height="1" /> <Separator Margin="0,8,0,8" Height="1" />
<CheckBox IsChecked="{Binding Config.RightInvertStickX}"> <CheckBox IsChecked="{Binding Config.RightInvertStickX}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertXAxis}" /> <TextBlock Text="{ext:Locale ControllerSettingsStickInvertXAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding Config.RightInvertStickY}"> <CheckBox IsChecked="{Binding Config.RightInvertStickY}">
<TextBlock Text="{locale:Locale ControllerSettingsStickInvertYAxis}" /> <TextBlock Text="{ext:Locale ControllerSettingsStickInvertYAxis}" />
</CheckBox> </CheckBox>
<CheckBox IsChecked="{Binding Config.RightRotate90}"> <CheckBox IsChecked="{Binding Config.RightRotate90}">
<TextBlock Text="{locale:Locale ControllerSettingsRotate90}" /> <TextBlock Text="{ext:Locale ControllerSettingsRotate90}" />
</CheckBox> </CheckBox>
<Separator Margin="0,8,0,8" Height="1" /> <Separator Margin="0,8,0,8" Height="1" />
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDeadzone}" /> Text="{ext:Locale ControllerSettingsStickDeadzone}" />
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
@@ -737,7 +737,7 @@
</StackPanel> </StackPanel>
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRange}" /> Text="{ext:Locale ControllerSettingsStickRange}" />
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"

View File

@@ -2,7 +2,7 @@
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
@@ -56,7 +56,7 @@
Width="90" Width="90"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsPlayer}" /> Text="{ext:Locale ControllerSettingsPlayer}" />
<ComboBox <ComboBox
Grid.Column="1" Grid.Column="1"
Name="PlayerIndexBox" Name="PlayerIndexBox"
@@ -90,7 +90,7 @@
Width="90" Width="90"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsProfile}" /> Text="{ext:Locale ControllerSettingsProfile}" />
<ui:FAComboBox <ui:FAComboBox
Grid.Column="1" Grid.Column="1"
IsEditable="True" IsEditable="True"
@@ -105,7 +105,7 @@
MinWidth="0" MinWidth="0"
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsLoadProfileToolTip}" ToolTip.Tip="{ext:Locale ControllerSettingsLoadProfileToolTip}"
Command="{Binding LoadProfile}"> Command="{Binding LoadProfile}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="Upload" Symbol="Upload"
@@ -117,7 +117,7 @@
MinWidth="0" MinWidth="0"
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsSaveProfileToolTip}" ToolTip.Tip="{ext:Locale ControllerSettingsSaveProfileToolTip}"
Command="{Binding SaveProfile}"> Command="{Binding SaveProfile}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="Save" Symbol="Save"
@@ -129,7 +129,7 @@
MinWidth="0" MinWidth="0"
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
ToolTip.Tip="{locale:Locale ControllerSettingsRemoveProfileToolTip}" ToolTip.Tip="{ext:Locale ControllerSettingsRemoveProfileToolTip}"
Command="{Binding RemoveProfile}"> Command="{Binding RemoveProfile}">
<ui:SymbolIcon <ui:SymbolIcon
Symbol="Delete" Symbol="Delete"
@@ -161,7 +161,7 @@
Width="90" Width="90"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsInputDevice}" /> Text="{ext:Locale ControllerSettingsInputDevice}" />
<ComboBox <ComboBox
Grid.Column="1" Grid.Column="1"
Name="DeviceBox" Name="DeviceBox"
@@ -196,7 +196,7 @@
Width="90" Width="90"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsControllerType}" /> Text="{ext:Locale ControllerSettingsControllerType}" />
<ComboBox <ComboBox
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"

View File

@@ -1,7 +1,7 @@
<UserControl <UserControl
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
@@ -72,7 +72,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZL}" Text="{ext:Locale ControllerSettingsTriggerZL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonZl"> <ToggleButton Name="ButtonZl">
<TextBlock <TextBlock
@@ -88,7 +88,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerL}" Text="{ext:Locale ControllerSettingsTriggerL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonL"> <ToggleButton Name="ButtonL">
<TextBlock <TextBlock
@@ -104,7 +104,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonMinus}" Text="{ext:Locale ControllerSettingsButtonMinus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonMinus"> <ToggleButton Name="ButtonMinus">
<TextBlock <TextBlock
@@ -127,7 +127,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsLStick}" /> Text="{ext:Locale ControllerSettingsLStick}" />
<!-- Left Joystick Keyboard --> <!-- Left Joystick Keyboard -->
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Left Joystick Button --> <!-- Left Joystick Button -->
@@ -139,7 +139,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{ext:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickButton"> <ToggleButton Name="LeftStickButton">
<TextBlock <TextBlock
@@ -156,7 +156,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}" Text="{ext:Locale ControllerSettingsStickUp}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickUp"> <ToggleButton Name="LeftStickUp">
<TextBlock <TextBlock
@@ -173,7 +173,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}" Text="{ext:Locale ControllerSettingsStickDown}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickDown"> <ToggleButton Name="LeftStickDown">
<TextBlock <TextBlock
@@ -190,7 +190,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}" Text="{ext:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickLeft"> <ToggleButton Name="LeftStickLeft">
<TextBlock <TextBlock
@@ -207,7 +207,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}" Text="{ext:Locale ControllerSettingsStickRight}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftStickRight"> <ToggleButton Name="LeftStickRight">
<TextBlock <TextBlock
@@ -232,7 +232,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPad}" /> Text="{ext:Locale ControllerSettingsDPad}" />
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Left DPad Up --> <!-- Left DPad Up -->
<StackPanel <StackPanel
@@ -243,7 +243,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadUp}" Text="{ext:Locale ControllerSettingsDPadUp}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadUp"> <ToggleButton Name="DpadUp">
<TextBlock <TextBlock
@@ -260,7 +260,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadDown}" Text="{ext:Locale ControllerSettingsDPadDown}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadDown"> <ToggleButton Name="DpadDown">
<TextBlock <TextBlock
@@ -277,7 +277,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadLeft}" Text="{ext:Locale ControllerSettingsDPadLeft}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadLeft"> <ToggleButton Name="DpadLeft">
<TextBlock <TextBlock
@@ -294,7 +294,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsDPadRight}" Text="{ext:Locale ControllerSettingsDPadRight}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="DpadRight"> <ToggleButton Name="DpadRight">
<TextBlock <TextBlock
@@ -337,7 +337,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSR}" Text="{ext:Locale ControllerSettingsLeftSR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftButtonSr"> <ToggleButton Name="LeftButtonSr">
<TextBlock <TextBlock
@@ -355,7 +355,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsLeftSL}" Text="{ext:Locale ControllerSettingsLeftSL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="LeftButtonSl"> <ToggleButton Name="LeftButtonSl">
<TextBlock <TextBlock
@@ -373,7 +373,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSR}" Text="{ext:Locale ControllerSettingsRightSR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightButtonSr"> <ToggleButton Name="RightButtonSr">
<TextBlock <TextBlock
@@ -391,7 +391,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsRightSL}" Text="{ext:Locale ControllerSettingsRightSL}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightButtonSl"> <ToggleButton Name="RightButtonSl">
<TextBlock <TextBlock
@@ -433,7 +433,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerZR}" Text="{ext:Locale ControllerSettingsTriggerZR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonZr"> <ToggleButton Name="ButtonZr">
<TextBlock <TextBlock
@@ -451,7 +451,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsTriggerR}" Text="{ext:Locale ControllerSettingsTriggerR}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonR"> <ToggleButton Name="ButtonR">
<TextBlock <TextBlock
@@ -469,7 +469,7 @@
Width="20" Width="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonPlus}" Text="{ext:Locale ControllerSettingsButtonPlus}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonPlus"> <ToggleButton Name="ButtonPlus">
<TextBlock <TextBlock
@@ -492,7 +492,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtons}" /> Text="{ext:Locale ControllerSettingsButtons}" />
<StackPanel <StackPanel
Orientation="Vertical"> Orientation="Vertical">
<!-- Right Buttons A --> <!-- Right Buttons A -->
@@ -504,7 +504,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonA}" Text="{ext:Locale ControllerSettingsButtonA}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonA"> <ToggleButton Name="ButtonA">
<TextBlock <TextBlock
@@ -521,7 +521,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonB}" Text="{ext:Locale ControllerSettingsButtonB}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonB"> <ToggleButton Name="ButtonB">
<TextBlock <TextBlock
@@ -538,7 +538,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonX}" Text="{ext:Locale ControllerSettingsButtonX}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonX"> <ToggleButton Name="ButtonX">
<TextBlock <TextBlock
@@ -555,7 +555,7 @@
Margin="0,0,10,0" Margin="0,0,10,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsButtonY}" Text="{ext:Locale ControllerSettingsButtonY}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="ButtonY"> <ToggleButton Name="ButtonY">
<TextBlock <TextBlock
@@ -578,7 +578,7 @@
<TextBlock <TextBlock
Margin="0,0,0,10" Margin="0,0,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRStick}" /> Text="{ext:Locale ControllerSettingsRStick}" />
<!-- Right Joystick Keyboard --> <!-- Right Joystick Keyboard -->
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<!-- Right Joystick Button --> <!-- Right Joystick Button -->
@@ -590,7 +590,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickButton}" Text="{ext:Locale ControllerSettingsStickButton}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickButton"> <ToggleButton Name="RightStickButton">
<TextBlock <TextBlock
@@ -607,7 +607,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickUp}" Text="{ext:Locale ControllerSettingsStickUp}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickUp"> <ToggleButton Name="RightStickUp">
<TextBlock <TextBlock
@@ -624,7 +624,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickDown}" Text="{ext:Locale ControllerSettingsStickDown}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickDown"> <ToggleButton Name="RightStickDown">
<TextBlock <TextBlock
@@ -641,7 +641,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickLeft}" Text="{ext:Locale ControllerSettingsStickLeft}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickLeft"> <ToggleButton Name="RightStickLeft">
<TextBlock <TextBlock
@@ -658,7 +658,7 @@
Width="120" Width="120"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsStickRight}" Text="{ext:Locale ControllerSettingsStickRight}"
TextAlignment="Center" /> TextAlignment="Center" />
<ToggleButton Name="RightStickRight"> <ToggleButton Name="RightStickRight">
<TextBlock <TextBlock
@@ -672,4 +672,4 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls" xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView" x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
@@ -23,7 +23,7 @@
<TextBlock <TextBlock
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" /> Text="{ext:Locale ControllerSettingsMotionGyroSensitivity}" />
<controls:SliderScroll <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="150" Width="150"
@@ -45,7 +45,7 @@
<TextBlock <TextBlock
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" /> Text="{ext:Locale ControllerSettingsMotionGyroDeadzone}" />
<controls:SliderScroll <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="150" Width="150"
@@ -70,7 +70,7 @@
<TextBlock <TextBlock
Margin="0,3,0,0" Margin="0,3,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" /> Text="{ext:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
<Border <Border
@@ -98,7 +98,7 @@
Margin="5" Margin="5"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionServerHost}" /> Text="{ext:Locale ControllerSettingsMotionServerHost}" />
<TextBox <TextBox
Height="30" Height="30"
MinWidth="100" MinWidth="100"
@@ -130,7 +130,7 @@
<TextBlock <TextBlock
Margin="0,10,0,0" Margin="0,10,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionControllerSlot}" /> Text="{ext:Locale ControllerSettingsMotionControllerSlot}" />
<ui:NumberBox <ui:NumberBox
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
@@ -145,7 +145,7 @@
Grid.Row="1" Grid.Row="1"
Grid.Column="0" Grid.Column="0"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" /> Text="{ext:Locale ControllerSettingsMotionRightJoyConSlot}" />
<ui:NumberBox <ui:NumberBox
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
@@ -162,7 +162,7 @@
IsChecked="{Binding MirrorInput, Mode=TwoWay}"> IsChecked="{Binding MirrorInput, Mode=TwoWay}">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" /> Text="{ext:Locale ControllerSettingsMotionMirrorInput}" />
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
</Grid> </Grid>

View File

@@ -43,7 +43,7 @@ namespace Ryujinx.Ava.UI.Views.Input
{ {
Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle], Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle],
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave], PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
SecondaryButtonText = "", SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose], CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
Content = content, Content = content,
}; };

View File

@@ -4,7 +4,7 @@
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels.Input"
mc:Ignorable="d" mc:Ignorable="d"
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView" x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
@@ -21,7 +21,7 @@
Width="100" Width="100"
TextWrapping="WrapWithOverflow" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" /> Text="{ext:Locale ControllerSettingsRumbleStrongMultiplier}" />
<controls:SliderScroll <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="200" Width="200"
@@ -41,7 +41,7 @@
Width="100" Width="100"
TextWrapping="WrapWithOverflow" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" /> Text="{ext:Locale ControllerSettingsRumbleWeakMultiplier}" />
<controls:SliderScroll <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="200" Width="200"

Some files were not shown because too many files have changed in this diff Show More