Compare commits

...

24 Commits

Author SHA1 Message Date
reggie
fec4f4ada9
meta: Link Greem's project 🙂 2024-10-24 11:16:46 -05:00
Iván Mestre
49574a99f5
Added a command line option (-c, --config) to load a configuration file through the command line parameters (#59) 2024-10-22 18:48:59 -05:00
reggie
68092bf00b
infra: We don't need to create test-ava archives for macOS either 2024-10-22 10:37:14 -05:00
TheToid
6253fe143a
Add ability to trim XCI files from the application context menu (#33) 2024-10-22 10:25:40 -05:00
reggie
7e9a293dab
docs: "s_" on internal static fields should not be enforced 2024-10-22 10:25:20 -05:00
reggie
d3619bc6fb
Remove test-ava archives from release workflow 2024-10-22 10:08:41 -05:00
EmulationEnjoyer
e5fdbd0b83
Fix divide by zero when recovering from missed draw (Vulkan) (#52) 2024-10-22 10:00:34 -05:00
dependabot[bot]
c4ee9c7555
nuget: bump the avalonia group with 6 updates (#41)
Bumps the avalonia group with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [Avalonia](https://github.com/AvaloniaUI/Avalonia) | `11.1.3` | `11.1.4` |
| [Avalonia.Controls.DataGrid](https://github.com/AvaloniaUI/Avalonia) | `11.1.3` | `11.1.4` |
| [Avalonia.Desktop](https://github.com/AvaloniaUI/Avalonia) | `11.1.3` | `11.1.4` |
| [SkiaSharp.NativeAssets.Linux](https://github.com/mono/SkiaSharp) | `2.88.7` | `2.88.8` |
| [Avalonia.Diagnostics](https://github.com/AvaloniaUI/Avalonia) | `11.1.3` | `11.1.4` |
| [Avalonia.Markup.Xaml.Loader](https://github.com/AvaloniaUI/Avalonia) | `11.1.3` | `11.1.4` |


Updates `Avalonia` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia.Controls.DataGrid` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia.Desktop` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `SkiaSharp.NativeAssets.Linux` from 2.88.7 to 2.88.8
- [Release notes](https://github.com/mono/SkiaSharp/releases)
- [Commits](https://github.com/mono/SkiaSharp/compare/v2.88.7...v2.88.8)

Updates `Avalonia.Diagnostics` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia.Controls.DataGrid` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia.Markup.Xaml.Loader` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

Updates `Avalonia` from 11.1.3 to 11.1.4
- [Release notes](https://github.com/AvaloniaUI/Avalonia/releases)
- [Commits](https://github.com/AvaloniaUI/Avalonia/compare/11.1.3...11.1.4)

---
updated-dependencies:
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Controls.DataGrid
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Desktop
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: SkiaSharp.NativeAssets.Linux
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Diagnostics
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Controls.DataGrid
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia.Markup.Xaml.Loader
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
- dependency-name: Avalonia
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: avalonia
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 19:29:54 -05:00
dependabot[bot]
80fa93faef
nuget: bump Microsoft.IdentityModel.JsonWebTokens from 8.1.1 to 8.1.2 (#40)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 8.1.1 to 8.1.2.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.1.1...8.1.2)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-09 12:14:31 -05:00
TheToid
b4cac89c1f
Fix some input controller issues (mapping sticks and duplicate controller names) (#31)
Co-authored-by: reggie <reggie@latte.to>
2024-10-08 21:41:31 -05:00
dependabot[bot]
0e5ce0bd20
nuget: bump Microsoft.IdentityModel.JsonWebTokens from 8.0.1 to 8.1.1 (#37)
Bumps [Microsoft.IdentityModel.JsonWebTokens](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) from 8.0.1 to 8.1.1.
- [Release notes](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases)
- [Changelog](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/compare/8.0.1...8.1.1)

---
updated-dependencies:
- dependency-name: Microsoft.IdentityModel.JsonWebTokens
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-07 09:03:28 -05:00
reggie
dc545c33e4 infra: Fix AppImage build for arm64 2024-10-06 14:42:31 -05:00
Samuel
51b956ac7f
infra: don't repackage appimages and fix zsync files (#34)
Co-authored-by: reggie <reggie@latte.to>
2024-10-06 14:14:04 -05:00
reggie
2880892c2c meta: Update issue template's Discord link 2024-10-06 14:09:51 -05:00
reggie
9c5dda1848 infra: Add AppImage build to release workflow (Enjoy Steam Deck users 🙂) 2024-10-06 03:20:22 -05:00
reggie
8a63c0bc69
infra: Fix AppImage Actions build job (#30)
* Are symlinks the problem?

(P.S., yep, symlinks were most definitely the problem lol)
2024-10-06 03:01:24 -05:00
Samuel
aa34084ba1
infra: Add AppImage build workflow (#28)
Co-authored-by: reggie <reggie@latte.to>
2024-10-06 01:34:01 -05:00
reggie
f49bd44cc1 Guarantee that releases are only set as latest after [release, macos_release] finish 2024-10-06 00:39:34 -05:00
reggie
6971fdd686 Base version has been 1.1.0, no idea why this wasn't ever changed 2024-10-05 23:57:34 -05:00
reggie
448666fd06 infra: Prepend "r." for build ver, flatten "publish" dir w/ tar and zip archives
* Also updated this behavior in the updater module.. WHY wasn't this done before? No clue. This was setup very interestingly.
2024-10-05 23:45:15 -05:00
reggie
0b111b3bb7 Replace Latte with dedicated ryujinx-mirror community! 2024-10-04 21:31:28 -05:00
reggie
e6ac7f9475 Add meta tag to labeler config 2024-10-03 22:42:27 -05:00
reggie
fdf5ee79da infra: Re-add issue templates 2024-10-03 22:21:18 -05:00
reggie
63f33e068b Update information on the fork 2024-10-03 20:28:16 -05:00
40 changed files with 1394 additions and 94 deletions

86
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Bug Report
description: File a bug report
title: "[Bug]"
labels: bug
body:
- type: textarea
id: issue
attributes:
label: Description of the issue
description: What's the issue you encountered?
validations:
required: true
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: How can the issue be reproduced?
placeholder: Describe each step as precisely as possible
validations:
required: true
- type: textarea
id: log
attributes:
label: Log file
description: A log file will help us to better diagnose and fix the issue.
placeholder: Logs files can be found under "Logs" folder in the Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
validations:
required: true
- type: input
id: os
attributes:
label: OS
placeholder: "e.g. Windows 10"
validations:
required: true
- type: input
id: ryujinx-version
attributes:
label: Ryujinx version (revision hash)
placeholder: "e.g. r67111a5"
validations:
required: true
- type: input
id: game-version
attributes:
label: Game version
placeholder: "e.g. 1.1.1"
validations:
required: false
- type: input
id: cpu
attributes:
label: CPU
placeholder: "e.g. i7-6700"
validations:
required: false
- type: input
id: gpu
attributes:
label: GPU
placeholder: "e.g. NVIDIA RTX 2070"
validations:
required: false
- type: input
id: ram
attributes:
label: RAM
placeholder: "e.g. 16GB"
validations:
required: false
- type: textarea
id: mods
attributes:
label: List of applied mods
placeholder: You can list applied mods here.
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional context?
description: |
- Additional info about your environment:
- Any other information relevant to your issue.
validations:
required: false

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: ryujinx-mirror (Discord)
url: https://discord.gg/xmHPGDfVCa
about: This is the home of development for the ryujinx-mirror fork, feel free to make a post in `#ryujinx-help` for general support & technical issues

View File

@ -0,0 +1,26 @@
name: Missing CPU Instruction
description: CPU Instruction is missing in Ryujinx.
title: "[CPU]"
labels: [cpu, not-implemented]
body:
- type: textarea
id: instruction
attributes:
label: CPU instruction
description: What CPU instruction is missing?
validations:
required: true
- type: textarea
id: name
attributes:
label: Instruction name
description: Include the name from [armconverter.com](https://armconverter.com/?disasm) or [shell-storm.org](http://shell-storm.org/online/Online-Assembler-and-Disassembler/?arch=arm64&endianness=big&dis_with_raw=True&dis_with_ins=True) in the above code block
validations:
required: true
- type: textarea
id: required
attributes:
label: Required by
description: Add links to the [compatibility list page(s)](https://github.com/ryujinx-mirror/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
validations:
required: true

View File

@ -0,0 +1,25 @@
name: Missing Service Call
description: Service call is missing in Ryujinx.
labels: not-implemented
body:
- type: textarea
id: instruction
attributes:
label: Service call
description: What service call is missing?
validations:
required: true
- type: textarea
id: name
attributes:
label: Service description
description: Include the description/explanation from [Switchbrew](https://switchbrew.org/w/index.php?title=Services_API) and/or [SwIPC](https://reswitched.github.io/SwIPC/) in the above code block
validations:
required: true
- type: textarea
id: required
attributes:
label: Required by
description: Add links to the [compatibility list page(s)](https://github.com/ryujinx-mirror/Ryujinx-Games-List/issues) of the game(s) that require this service.
validations:
required: true

View File

@ -0,0 +1,19 @@
name: Missing Shader Instruction
description: Shader Instruction is missing in Ryujinx.
title: "[GPU]"
labels: [gpu, not-implemented]
body:
- type: textarea
id: instruction
attributes:
label: Shader instruction
description: What shader instruction is missing?
validations:
required: true
- type: textarea
id: required
attributes:
label: Required by
description: Add links to the [compatibility list page(s)](https://github.com/ryujinx-mirror/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
validations:
required: true

4
.github/labeler.yml vendored
View File

@ -33,3 +33,7 @@ kernel:
infra:
- changed-files:
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props']
meta:
- changed-files:
- any-glob-to-any-file: ['docs/**', 'README.md', 'CONTRIBUTING.md']

View File

@ -40,65 +40,102 @@ jobs:
shell: bash
- name: Change config filename
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Change config filename for macOS
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test
uses: TSRBerry/unstable-commands@v1
- name: Run tests
uses: ryujinx-mirror/unstable-commands@releases/v1.0.6
if: matrix.platform.name != 'linux-arm64'
with:
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
timeout-minutes: 10
retry-codes: 139
if: matrix.platform.name != 'linux-arm64'
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
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
- 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
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
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
- name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
- name: Set executable bit
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
- name: Build AppImage
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
run: |
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
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
ARCH_NAME=x64
export ARCH=x86_64
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
ARCH_NAME=arm64
export ARCH=aarch64
else
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
exit 1
fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
shell: bash
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx (AppImage) artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
with:
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
build_macos:
name: macOS Universal (${{ matrix.configuration }})
@ -137,9 +174,9 @@ jobs:
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Change config filename
if: github.event_name == 'pull_request'
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx
run: |
@ -151,14 +188,14 @@ jobs:
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request'
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"
if: github.event_name == 'pull_request'

View File

@ -32,7 +32,7 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "build_version=r`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
echo "build_version=r.`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
shell: bash
- name: Create tag
@ -53,7 +53,7 @@ jobs:
tag: ${{ steps.version_info.outputs.build_version }}
draft: "true"
omitBody: true
omitBodyDuringUpdate: true
#omitBodyDuringUpdate: true
#owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
#repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.GITHUB_TOKEN }}
@ -61,7 +61,7 @@ jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
timeout-minutes: 60
strategy:
matrix:
platform:
@ -81,7 +81,7 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "build_version=r`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
echo "build_version=r.`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
@ -100,36 +100,77 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p: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_ava -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
- name: Packing Windows builds
if: matrix.platform.os == 'windows-latest'
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
ZIP_OS_NAME="${{ matrix.platform.zip_os_name }}"
pushd publish_ava
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
cp Ryujinx.exe Ryujinx.Ava.exe
7z a ../release_output/ryujinx-$BUILD_VERSION-$ZIP_OS_NAME.zip *
popd
pushd publish_sdl2_headless
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/sdl2-ryujinx-headless-$BUILD_VERSION-$ZIP_OS_NAME.zip *
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
# 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: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest'
run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
ZIP_OS_NAME="${{ matrix.platform.zip_os_name }}"
pushd publish_ava
cp publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava 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/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
cp Ryujinx Ryujinx.Ava
chmod +x Ryujinx.sh Ryujinx Ryujinx.Ava
tar -czvf ../release_output/ryujinx-$BUILD_VERSION-$ZIP_OS_NAME.tar.gz *
popd
pushd publish_sdl2_headless
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
chmod +x Ryujinx.sh Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-$BUILD_VERSION-$ZIP_OS_NAME.tar.gz *
popd
shell: bash
@ -137,11 +178,11 @@ jobs:
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip"
tag: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip,release_output/*AppImage*"
draft: "true"
omitBody: true
omitBodyDuringUpdate: true
#omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
#owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
@ -151,7 +192,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@ -179,8 +220,9 @@ jobs:
- name: Get version info
id: version_info
run: |
echo "build_version=r`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
echo "build_version=r.`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
run: |
@ -204,14 +246,36 @@ jobs:
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
tag: ${{ steps.version_info.outputs.build_version }}
draft: "false"
makeLatest: "true"
artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz"
draft: "true"
omitBody: true
omitBodyDuringUpdate: true
#omitBodyDuringUpdate: true
allowUpdates: true
replacesArtifacts: true
#owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
#repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.GITHUB_TOKEN }}
set_latest_release:
name: Set as latest release
runs-on: ubuntu-latest
needs: [release, macos_release]
steps:
- name: Get version info
id: version_info
run: |
echo "build_version=r.`echo ${{ github.sha }} | cut -c1-7`" >> $GITHUB_OUTPUT
shell: bash
- name: Update release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
makeLatest: "true"
omitBody: true
allowUpdates: true
#owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
#repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.GITHUB_TOKEN }}

8
.gitignore vendored
View File

@ -10,13 +10,15 @@
# Build results
[Dd]ebug/
[Rr]elease/
[Dd]ebug*/
[Rr]elease*/
x64/
build/
[Bb]in/
[Oo]bj/
AppDir/
# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
!packages/*/build/
@ -95,7 +97,7 @@ DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
publish*/
# Publish Web Output
*.Publish.xml

View File

@ -3,11 +3,11 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.1.3" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.1.3" />
<PackageVersion Include="Avalonia.Desktop" Version="11.1.3" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.1.3" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.1.3" />
<PackageVersion Include="Avalonia" Version="11.1.4" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.1.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.1.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.1.4" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.1.4" />
<PackageVersion Include="Avalonia.Svg" Version="11.1.0.1" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.1.0.1" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
@ -20,7 +20,7 @@
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.1.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />

View File

@ -1,15 +1,25 @@
## Notice
[links/discord]: https://discord.gg/xmHPGDfVCa
[badges/discord]: https://img.shields.io/discord/1291765437100720243?label=ryujinx-mirror&logo=discord&logoColor=FFFFFF&color=5865F3
As of now, the [ryujinx-mirror/ryujinx](https://github.com/ryujinx-mirror/ryujinx) repository serves simply as a downstream fork of the original Ryujinx project, and won't be accepting any new changes until further information arises.
As of now, the [ryujinx-mirror/ryujinx](https://github.com/ryujinx-mirror/ryujinx) repository serves as a downstream hard fork of the original Ryujinx project. You can download nightly binaries for Windows, macOS, and Linux (including `AppImage`s) from the [latest release](https://github.com/ryujinx-mirror/ryujinx/releases/latest).
> [!NOTE]
> This mirror is not affiliated with the original Ryujinx project, or Nintendo whatsoever.
> This fork is not affiliated with the **original** Ryujinx project, or Nintendo whatsoever.
### Current Goals
* To be as safe as possible, remove all in-app and meta references to Patreon, Discord, `ryujinx.org` etc while keeping full attribution of original authors and contributors in-tact.
If you would like a version with more new features & improvements, feel free to check out [GreemDev's fork](https://github.com/GreemDev/Ryujinx). We aim to keep this repository more focused on small fixes and infrastructure reconstruction, staying more true to the original Ryujinx project.
* ☑️ Reconstruct basic build infrastructure & workflows for this repository, based on revision hashes as opposed to semver releases (for now)
* ☑️ To be as safe as possible, remove all previous in-app and meta references to Patreon, `ryujinx.org` etc while keeping full attribution of original authors and contributors in-tact.
* Keep 'branding' as pure and faithful to the original project as possible.
* Reconstruct basic build infrastructure & workflows for this repository, based on revision hashes as opposed to semver releases (for now)
### Join Discussion
Feel free to join the [ryujinx-mirror Discord community][links/discord] to join in on the development of this fork going forward.<br>
See `#ryujinx-info` for more information.
[![ryujinx-mirror Discord][badges/discord]][links/discord]
___

View File

@ -0,0 +1,3 @@
#!/bin/sh
CURRENTDIR="$(readlink -f "$(dirname "$0")")"
exec "$CURRENTDIR"/usr/bin/Ryujinx.sh "$@"

View File

@ -0,0 +1,30 @@
#!/bin/sh
set -eu
ROOTDIR="$(readlink -f "$(dirname "$0")")"/../../../
cd "$ROOTDIR"
BUILDDIR=${BUILDDIR:-publish}
OUTDIR=${OUTDIR:-publish_appimage}
UFLAG=${UFLAG:-"gh-releases-zsync|ryujinx-mirror|ryujinx|latest|*-x64.AppImage.zsync"}
rm -rf AppDir
mkdir -p AppDir/usr/bin
cp distribution/linux/Ryujinx.desktop AppDir/Ryujinx.desktop
cp distribution/linux/appimage/AppRun AppDir/AppRun
cp distribution/misc/Logo.svg AppDir/Ryujinx.svg
cp -r "$BUILDDIR"/* AppDir/usr/bin/
# Ensure necessary bins are set as executable
chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR"
appimagetool --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
-u "$UFLAG" \
AppDir "$OUTDIR"/Ryujinx.AppImage
# Move zsync file needed for delta updates
mv ./*.AppImage.zsync "$OUTDIR"

View File

@ -110,11 +110,11 @@ gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
#if [ "$VERSION" != "1.1.0" ];
#then
# cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
#fi
popd
echo "Done"
echo "Done"

View File

@ -5,7 +5,7 @@ Using an IDE that supports the `.editorconfig` standard will make this much simp
1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block.
2. We use four spaces of indentation (no tabs).
3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used.
3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used.
4. We avoid `this.` unless absolutely necessary.
5. We always specify the visibility, even if it's the default (e.g.
`private string _foo` not `string _foo`). Visibility should be the first modifier (e.g.

View File

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

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

@ -0,0 +1,507 @@
using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace Ryujinx.Common.Utilities
{
internal static class Performance
{
internal static TimeSpan Measure(Action action)
{
var sw = new Stopwatch();
sw.Start();
try
{
action();
}
finally
{
sw.Stop();
}
return sw.Elapsed;
}
}
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
{
InvalidXCIFile,
NoTrimNecessary,
NoUntrimPossible,
FreeSpaceCheckFailed,
FileIOWriteError,
ReadOnlyFileCannotFix,
FileSizeChanged,
Successful
}
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()
{
if (FreeSpaceChecked)
return;
try
{
if (CanBeTrimmed)
{
_freeSpaceValid = false;
OpenReaders();
try
{
Pos = TrimmedFileSizeB;
bool freeSpaceValid = true;
long readSizeB = FileSizeB - TrimmedFileSizeB;
TimeSpan time = Performance.Measure(() =>
{
freeSpaceValid = CheckPadding(readSizeB);
});
if (time.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Checked at {readSizeB / (double)XCIFileTrimmer.BytesInAMegabyte / time.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)
{
long maxReads = readSizeB / XCIFileTrimmer.BufferSize;
long read = 0;
var buffer = new byte[BufferSize];
while (true)
{
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()
{
if (!FileOK)
{
return OperationOutcome.InvalidXCIFile;
}
if (!CanBeTrimmed)
{
return OperationOutcome.NoTrimNecessary;
}
if (!FreeSpaceChecked)
{
CheckFreeSpace();
}
if (!FreeSpaceValid)
{
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
{
outfileStream.SetLength(TrimmedFileSizeB);
return OperationOutcome.Successful;
}
finally
{
outfileStream.Close();
Reset();
}
}
catch (Exception e)
{
Log?.Write(LogType.Error, e.ToString());
return OperationOutcome.FileIOWriteError;
}
}
public OperationOutcome Untrim()
{
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
{
TimeSpan time = Performance.Measure(() =>
{
WritePadding(outfileStream, bytesToWriteB);
});
if (time.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Wrote at {bytesToWriteB / (double)XCIFileTrimmer.BytesInAMegabyte / time.TotalSeconds:N} Mb/sec");
}
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)
{
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)
{
long bytesToWrite = Math.Min(XCIFileTrimmer.BufferSize, bytesLeftToWriteB);
outfileStream.Write(buffer, 0, (int)bytesToWrite);
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

@ -55,8 +55,10 @@ namespace Ryujinx.Graphics.Vulkan
if (_handle != BufferHandle.Null)
{
// May need to restride the vertex buffer.
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0)
//
// Fix divide by zero when recovering from missed draw (Oct. 16 2024)
// (fixes crash in 'Baldo: The Guardian Owls' opening cutscene)
if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && alignment != 0 && (_stride % alignment) != 0)
{
autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment);

View File

@ -165,6 +165,11 @@ namespace Ryujinx
? appDataConfigurationPath
: null;
if (!string.IsNullOrEmpty(CommandLineState.OverrideConfigFile) && File.Exists(CommandLineState.OverrideConfigFile))
{
ConfigurationPath = CommandLineState.OverrideConfigFile;
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk

View File

@ -134,6 +134,7 @@ namespace Ryujinx.UI
[GUI] ScrolledWindow _gameTableWindow;
[GUI] Label _gpuName;
[GUI] Label _progressLabel;
[GUI] Label _progressStatusLabel;
[GUI] Label _firmwareVersionLabel;
[GUI] Gtk.ProgressBar _progressBar;
[GUI] Box _viewBox;
@ -727,6 +728,34 @@ namespace Ryujinx.UI
});
}
public void StartProgress(string action)
{
Application.Invoke(delegate
{
_progressStatusLabel.Text = action;
_progressStatusLabel.Visible = true;
_progressBar.Fraction = 0;
});
}
public void UpdateProgress(double percentage)
{
Application.Invoke(delegate
{
_progressBar.Fraction = percentage;
});
}
public void EndProgress()
{
Application.Invoke(delegate
{
_progressStatusLabel.Text = String.Empty;
_progressStatusLabel.Visible = false;
_progressBar.Fraction = 1.0;
});
}
public void UpdateGameTable()
{
if (_updatingGameTable || _gameLoaded)

View File

@ -667,6 +667,22 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="_progressStatusLabel">
<property name="visible">False</property>
<property name="can-focus">False</property>
<property name="margin-left">10</property>
<property name="margin-right">5</property>
<property name="margin-top">2</property>
<property name="margin-bottom">2</property>
<property name="label" translatable="yes"></property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="_progressBar">
<property name="width-request">200</property>
@ -680,7 +696,7 @@
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>

View File

@ -25,6 +25,7 @@ namespace Ryujinx.UI.Widgets
private MenuItem _openPtcDirMenuItem;
private MenuItem _openShaderCacheDirMenuItem;
private MenuItem _createShortcutMenuItem;
private MenuItem _trimXCIMenuItem;
private void InitializeComponent()
{
@ -198,6 +199,15 @@ namespace Ryujinx.UI.Widgets
};
_createShortcutMenuItem.Activated += CreateShortcut_Clicked;
//
// _trimXCIMenuItem
//
_trimXCIMenuItem = new MenuItem("Check and Trim XCI File")
{
TooltipText = "Check and Trim XCI File to Save Disk Space."
};
_trimXCIMenuItem.Activated += TrimXCI_Clicked;
ShowComponent();
}
@ -224,6 +234,8 @@ namespace Ryujinx.UI.Widgets
Add(_openTitleModDirMenuItem);
Add(_openTitleSdModDirMenuItem);
Add(new SeparatorMenuItem());
Add(_trimXCIMenuItem);
Add(new SeparatorMenuItem());
Add(_manageCacheMenuItem);
Add(_extractMenuItem);

View File

@ -13,6 +13,7 @@ using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
@ -75,6 +76,7 @@ namespace Ryujinx.UI.Widgets
_extractLogoMenuItem.Sensitive = hasNca;
_createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild;
_trimXCIMenuItem.Sensitive = _applicationData != null && Ryujinx.Common.Utilities.XCIFileTrimmer.CanTrim(_applicationData.Path, new XCIFileTrimmerLog(_parent));
PopupAtPointer(null);
}
@ -630,5 +632,91 @@ namespace Ryujinx.UI.Widgets
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem, checkLevel).GetApplicationIcon(_applicationData.Path, ConfigurationState.Instance.System.Language, _applicationData.Id);
ShortcutHelper.CreateAppShortcut(_applicationData.Path, _applicationData.Name, _applicationData.IdString, appIcon);
}
private void ProcessTrimResult(String filename, Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome operationOutcome)
{
string notifyUser = null;
switch (operationOutcome)
{
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.NoTrimNecessary:
notifyUser = "XCI File does not need to be trimmed. Check logs for further details";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.ReadOnlyFileCannotFix:
notifyUser = "XCI File is Read Only and could not be made writable. Check logs for further details";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FreeSpaceCheckFailed:
notifyUser = "XCI File has data in the free space area, it is not safe to trim";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.InvalidXCIFile:
notifyUser = "XCI File contains invalid data. Check logs for further details";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileIOWriteError:
notifyUser = "XCI File could not be opened for writing. Check logs for further details";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileSizeChanged:
notifyUser = "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.";
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.Successful:
_parent.UpdateGameTable();
break;
}
if (notifyUser != null)
{
GtkDialog.CreateWarningDialog("Trimming of the XCI file failed", notifyUser);
}
}
private void TrimXCI_Clicked(object sender, EventArgs args)
{
if (_applicationData?.Path == null)
{
return;
}
var trimmer = new XCIFileTrimmer(_applicationData.Path, new XCIFileTrimmerLog(_parent));
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;
using MessageDialog confirmationDialog = GtkDialog.CreateConfirmationDialog(
$"This function will first check the empty space and then trim the XCI File to save disk space. Continue?",
$"Current File Size: {currentFileSize:n} MB\n" +
$"Game Data Size: {cartDataSize:n} MB\n" +
$"Disk Space Savings: {savings:n} MB\n"
);
if (confirmationDialog.Run() == (int)ResponseType.Yes)
{
Thread xciFileTrimmerThread = new(() =>
{
_parent.StartProgress($"Trimming file '{_applicationData.Path}");
try
{
XCIFileTrimmer.OperationOutcome operationOutcome = trimmer.Trim();
Gtk.Application.Invoke(delegate
{
ProcessTrimResult(_applicationData.Path, operationOutcome);
});
}
finally
{
_parent.EndProgress();
}
})
{
Name = "GUI.XCIFileTrimmerThread",
IsBackground = true,
};
xciFileTrimmerThread.Start();
}
}
}
}
}

View File

@ -41,7 +41,7 @@ namespace Ryujinx.UI.Windows
private void DiscordButton_Pressed(object sender, ButtonPressEventArgs args)
{
OpenHelper.OpenUrl("https://example.com/");
OpenHelper.OpenUrl("https://discord.gg/xmHPGDfVCa");
}
private void TwitterButton_Pressed(object sender, ButtonPressEventArgs args)

View File

@ -0,0 +1,27 @@
using Ryujinx.Common.Logging;
using System;
namespace Ryujinx.UI
{
internal class XCIFileTrimmerLog : Ryujinx.Common.Logging.XCIFileTrimmerLog
{
private readonly MainWindow _mainWindow;
public XCIFileTrimmerLog(MainWindow mainWindow)
{
_mainWindow = mainWindow;
}
public override void Progress(long current, long total, string text, bool complete)
{
if (!complete)
{
_mainWindow.UpdateProgress((double)current / (double)total);
}
else
{
_mainWindow.EndProgress();
}
}
}
}

View File

@ -13,6 +13,7 @@ namespace Ryujinx.HLE.Generators
var syntaxReceiver = (ServiceSyntaxReceiver)context.SyntaxReceiver;
CodeGenerator generator = new CodeGenerator();
generator.AppendLine("#nullable enable");
generator.AppendLine("using System;");
generator.EnterScope($"namespace Ryujinx.HLE.HOS.Services.Sm");
generator.EnterScope($"partial class IUserInterface");
@ -58,6 +59,7 @@ namespace Ryujinx.HLE.Generators
generator.LeaveScope();
generator.LeaveScope();
generator.AppendLine("#nullable disable");
context.AddSource($"IUserInterface.g.cs", generator.ToString());
}

View File

@ -313,6 +313,32 @@ namespace Ryujinx.Input.SDL2
return value * ConvertRate;
}
private JoyconConfigControllerStick<GamepadInputId, Common.Configuration.Hid.Controller.StickInputId> GetLogicalJoyStickConfig(StickInputId inputId)
{
switch (inputId)
{
case StickInputId.Left:
if (_configuration.RightJoyconStick.Joystick == Common.Configuration.Hid.Controller.StickInputId.Left)
{
return _configuration.RightJoyconStick;
}
else
{
return _configuration.LeftJoyconStick;
}
case StickInputId.Right:
if (_configuration.LeftJoyconStick.Joystick == Common.Configuration.Hid.Controller.StickInputId.Right)
{
return _configuration.LeftJoyconStick;
}
else
{
return _configuration.RightJoyconStick;
}
}
return null;
}
public (float, float) GetStick(StickInputId inputId)
{
if (inputId == StickInputId.Unbound)
@ -343,24 +369,26 @@ namespace Ryujinx.Input.SDL2
if (HasConfiguration)
{
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickX) ||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickX))
{
resultX = -resultX;
}
var joyconStickConfig = GetLogicalJoyStickConfig(inputId);
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.InvertStickY) ||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.InvertStickY))
if (joyconStickConfig != null)
{
resultY = -resultY;
}
if (joyconStickConfig.InvertStickX)
{
resultX = -resultX;
}
if ((inputId == StickInputId.Left && _configuration.LeftJoyconStick.Rotate90CW) ||
(inputId == StickInputId.Right && _configuration.RightJoyconStick.Rotate90CW))
{
float temp = resultX;
resultX = resultY;
resultY = -temp;
if (joyconStickConfig.InvertStickY)
{
resultY = -resultY;
}
if (joyconStickConfig.Rotate90CW)
{
float temp = resultX;
resultX = resultY;
resultY = -temp;
}
}
}

View File

@ -1,5 +1,6 @@
using Ryujinx.Common.Logging;
using System.Collections.Generic;
using System.IO;
namespace Ryujinx.UI.Common.Helper
{
@ -16,6 +17,7 @@ namespace Ryujinx.UI.Common.Helper
public static string LaunchPathArg { get; private set; }
public static string LaunchApplicationId { get; private set; }
public static bool StartFullscreenArg { get; private set; }
public static string OverrideConfigFile { get; private set; }
public static void ParseArguments(string[] args)
{
@ -96,6 +98,29 @@ namespace Ryujinx.UI.Common.Helper
case "--software-gui":
OverrideHardwareAcceleration = false;
break;
case "-c":
case "--config":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
string configFile = args[++i];
if (Path.GetExtension(configFile).ToLower() != ".json")
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideConfigFile = configFile;
arguments.Add(arg);
arguments.Add(args[i]);
break;
default:
LaunchPathArg = arg;
break;

View File

@ -82,8 +82,11 @@
"GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods",
"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.",
"GameListContextMenuTrimXCI": "Check and Trim XCI File",
"GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"StatusBarXCIFileTrimming": "Trimming XCI File '{0}'",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
"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.",
@ -704,6 +707,16 @@
"SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files",
"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",
"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",
"UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
@ -714,6 +727,7 @@
"DlcWindowHeading": "{0} Downloadable Content(s)",
"ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected",
"Continue": "Continue",
"Cancel": "Cancel",
"Save": "Save",
"Discard": "Discard",

View File

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

View File

@ -36,7 +36,6 @@ namespace Ryujinx.Modules
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4;
private static string _buildVer;
@ -632,7 +631,7 @@ namespace Ryujinx.Modules
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
});
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog);
MoveAllFilesOver(_updateDir, _homeDir, taskDialog);
Directory.Delete(_updateDir, true);
}
@ -709,7 +708,7 @@ namespace Ryujinx.Modules
{
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(_updatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var newFiles = Directory.EnumerateFiles(_updateDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(_homeDir, filename));
// Remove user files from the paths in files.

View File

@ -150,6 +150,11 @@ namespace Ryujinx.Ava
ConfigurationPath = appDataConfigurationPath;
}
if (!string.IsNullOrEmpty(CommandLineState.OverrideConfigFile) && File.Exists(CommandLineState.OverrideConfigFile))
{
ConfigurationPath = CommandLineState.OverrideConfigFile;
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk

View File

@ -4,7 +4,7 @@
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<Version>1.1.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>

View File

@ -59,6 +59,12 @@
Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem
Click="TrimXCI_Click"
Header="{locale:Locale GameListContextMenuTrimXCI}"
IsEnabled="{Binding TrimXCIEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuTrimXCIToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem

View File

@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
@ -15,6 +16,8 @@ using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.Controls
@ -355,5 +358,15 @@ namespace Ryujinx.Ava.UI.Controls
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

@ -45,7 +45,6 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
private PlayerIndex _playerId;
private int _controller;
private int _controllerNumber;
private string _controllerImage;
private int _device;
private object _configViewModel;
@ -439,6 +438,24 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
public void LoadDevices()
{
string GetGamepadName(IGamepad gamepad, int controllerNumber)
{
return $"{GetShortGamepadName(gamepad.Name)} ({controllerNumber})";
}
string GetUniqueGamepadName(IGamepad gamepad, ref int controllerNumber)
{
string name = GetGamepadName(gamepad, controllerNumber);
if (Devices.Any(controller => controller.Name == name))
{
controllerNumber++;
name = GetGamepadName(gamepad, controllerNumber);
}
return name;
}
lock (Devices)
{
Devices.Clear();
@ -455,23 +472,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
}
}
int controllerNumber = 0;
foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds)
{
using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id);
if (gamepad != null)
{
if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id)))
{
_controllerNumber++;
}
Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})"));
string name = GetUniqueGamepadName(gamepad, ref controllerNumber);
Devices.Add((DeviceType.Controller, id, name));
}
}
_controllerNumber = 0;
DeviceList.AddRange(Devices.Select(x => x.Name));
Device = Math.Min(Device, DeviceList.Count);
}
@ -685,7 +697,7 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
if (!File.Exists(path))
{
var index = ProfilesList.IndexOf(ProfileName);
int index = ProfilesList.IndexOf(ProfileName);
if (index != -1)
{
ProfilesList.RemoveAt(index);

View File

@ -20,6 +20,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
@ -36,6 +37,7 @@ using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Threading;
@ -78,6 +80,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isAppletMenuActive;
private int _statusBarProgressMaximum;
private int _statusBarProgressValue;
private string _statusBarProgressStatusText;
private bool _statusBarProgressStatusVisible;
private bool _isPaused;
private bool _showContent = true;
private bool _isLoadingIndeterminate = true;
@ -366,6 +370,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool TrimXCIEnabled => Ryujinx.Common.Utilities.XCIFileTrimmer.CanTrim(SelectedApplication.Path, new Common.XCIFileTrimmerLog(this));
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
@ -480,6 +486,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
{
get => _fifoStatusText;
@ -1747,6 +1775,114 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
public async void ProcessTrimResult(String filename, Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome operationOutcome)
{
string notifyUser = null;
switch (operationOutcome)
{
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.NoTrimNecessary:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.ReadOnlyFileCannotFix:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileReadOnlyFileCannotFix];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FreeSpaceCheckFailed:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFreeSpaceCheckFailed];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.InvalidXCIFile:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileIOWriteError:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileSizeChanged:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.Successful:
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (desktop.MainWindow is MainWindow mainWindow)
mainWindow.LoadApplications();
}
break;
}
if (notifyUser != null)
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileFailedPrimaryText],
notifyUser
);
}
}
public async Task TrimXCIFile(string filename)
{
if (filename == null)
{
return;
}
var trimmer = new XCIFileTrimmer(filename, new Common.XCIFileTrimmerLog(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.XCFileTrimmerThread",
IsBackground = true,
};
XCIFileTrimThread.Start();
}
}
}
#endregion
}
}

View File

@ -36,6 +36,7 @@
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
@ -60,9 +61,16 @@
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<TextBlock
Name="StatusBarProgressStatus"
Grid.Column="2"
Margin="10,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding StatusBarProgressStatusVisible}"
Text="{Binding StatusBarProgressStatusText}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Grid.Column="3"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource SystemAccentColorLight2}"

View File

@ -151,7 +151,6 @@
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
<Image Source="{Binding GithubLogo}" />
</Button>
<!--
<Button
MinWidth="30"
MinHeight="30"
@ -161,10 +160,11 @@
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://discordapp.com/invite/N2FmfVc"
Tag="https://discord.gg/xmHPGDfVCa"
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" />
</Button>
<!--
<Button
MinWidth="30"
MinHeight="30"