Compare commits

..

3 Commits

251 changed files with 3114 additions and 9085 deletions

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Ryubing Issue Tracker
url: https://github.com/Ryubing/Issues/issues/
about: "Please use this GitHub repository instead of creating issues on this Forgejo repository. Blank issues should only be used by maintainers and authorized bots. Issues made on this repository can and will be deleted."

View File

@@ -1,35 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"renovate/config"
],
"enabledManagers": ["nuget", "github-actions"],
"packageRules": [
{
// require approval for *all* NuGet package updates, not just major versions.
"matchDepTypes": "nuget",
"dependencyDashboardApproval": true
},
{
// Ignore Gommon for automatic updates. I make breaking changes on minor updates not infrequently.
"matchDepNames": "Gommon",
"matchDepTypes": "nuget",
"enabled": false
},
{
"description": "group Silk.NET packages",
"extends": ["renovate/config//groups/silkdotnet.json"],
"groupName": "Silk.NET"
},
{
"description": "group OpenTK packages",
"extends": ["renovate/config//groups/opentk.json"],
"groupName": "OpenTK"
},
{
"description": "group Svg.Controls packages",
"extends": ["renovate/config//groups/svgcontrols.json"],
"groupName": "Svg.Controls"
}
]
}

View File

@@ -1,204 +0,0 @@
name: Build PR
on:
pull_request:
branches: [ master ]
paths:
- '**'
- '!.forgejo/**'
- '!*.yml'
- '!*.config'
- '!*.md'
- '.forgejo/workflows/*.yml'
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RELEASE: 0
jobs:
build:
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
timeout-minutes: 45
strategy:
matrix:
configuration: [Release]
platform:
- { name: win-x64, zip_os_name: win_x64 }
- { name: win-arm64, zip_os_name: win_arm64 }
- { name: linux-x64, zip_os_name: linux_x64 }
- { name: linux-arm64, zip_os_name: linux_arm64 }
#- { name: osx-x64, zip_os_name: osx_x64 }
fail-fast: false
steps:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install 7zip
run: |
sudo apt update && sudo apt install -y 7zip
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.forgejo/csc.json"
- name: Get version info
id: version_info
run: |
echo "result=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
shell: bash
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: forgejo.event_name == 'pull_request'
- name: 'Cache: ~/.nuget/packages'
uses: actions/cache@v5
with:
path: |
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ steps.version_info.outputs.result }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER
- name: Test
uses: actions/unstable-commands@v1
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="${{ steps.version_info.outputs.result }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained
if: forgejo.event_name == 'pull_request'
- name: Packing Windows builds
if: contains(matrix.platform.name, 'win')
run: |
7z a artifact/ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}.7z publish
shell: bash
- name: Upload Ryujinx Windows artifact
uses: actions/upload-artifact@v5
with:
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}
path: artifact
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'win')
- name: Build AppImage
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
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
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
shell: bash
- name: Upload Ryujinx AppImage artifact
uses: actions/upload-artifact@v5
if: forgejo.event_name == 'pull_request' && contains(matrix.platform.name, 'linux')
with:
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
matrix:
configuration: [ Release ]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install rcodesign
run: |
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
tar -xzvf apple-codesign.tar.gz --wildcards '*/rcodesign' --strip-components=1
rm apple-codesign.tar.gz
sudo mv rcodesign /usr/bin/rcodesign
- name: Get version info
id: version_info
run: |
echo "result=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
shell: bash
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: forgejo.event_name == 'pull_request'
- name: 'Cache: ~/.nuget/packages'
uses: actions/cache@v5
with:
path: |
~/.nuget/packages
key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }}
- name: Publish macOS Ryujinx
run: |
bash distribution/macos/create_macos_pr_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.result }}" "${{ steps.version_info.outputs.git_short_hash }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
shell: bash
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v5
with:
name: ryujinx-${{ matrix.configuration }}-${{ steps.version_info.outputs.result }}+${{ steps.version_info.outputs.git_short_hash }}-macos_universal
path: "publish/*.tar.gz"
if: forgejo.event_name == 'pull_request'

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 our developers to better diagnose and fix the issue. UPLOAD THE FILE. DO NOT COPY AND PASTE THE FILE'S CONTENT."
placeholder: Logs files can be found under "Logs" folder in 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
placeholder: "e.g. 1.0.470"
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: false
contact_links:
- name: Ryujinx Discord
url: https://discord.gg/N2FmfVc
about: This is for development related issues. For support and technical issues, please come to our Discord server.

View File

@@ -0,0 +1,31 @@
name: Feature Request
description: Suggest a new feature for Ryujinx.
title: "[Feature Request]"
labels: enhancement
body:
- type: textarea
id: overview
attributes:
label: Overview
description: Include the basic, high-level concepts for this feature here.
validations:
required: true
- type: textarea
id: details
attributes:
label: Smaller details
description: These may include specific methods of implementation etc.
validations:
required: true
- type: textarea
id: request
attributes:
label: Nature of request
validations:
required: true
- type: textarea
id: feature
attributes:
label: Why would this feature be useful?
validations:
required: true

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/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/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/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
validations:
required: true

View File

@@ -10,10 +10,6 @@ gpu:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.*/**', 'src/Spv.Generator/**', 'src/Ryujinx.ShaderTools/**']
input:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Input*/**', 'src/Ryujinx/UI/Views/Input/**']
'graphics-backend:opengl':
- changed-files:
- any-glob-to-any-file: 'src/Ryujinx.Graphics.OpenGL/**'
@@ -22,17 +18,17 @@ input:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Vulkan/**', 'src/Spv.Generator/**']
'graphics-backend:metal':
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.Graphics.Metal/**', 'src/Ryujinx.Graphics.Metal.SharpMetalExtensions/**']
gui:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.LocaleGenerator/**']
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**']
'horizon/hle':
horizon:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.HLE.Generators/**', 'src/Ryujinx.Horizon/**']
i18n:
- changed-files:
- any-glob-to-any-file: ['assets/**/*.json', 'src/Ryujinx.UI.LocaleGenerator/**']
- any-glob-to-any-file: ['src/Ryujinx.HLE/**', 'src/Ryujinx.Horizon/**']
kernel:
- changed-files:
@@ -40,7 +36,7 @@ kernel:
infra:
- changed-files:
- any-glob-to-any-file: ['.forgejo/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
- any-glob-to-any-file: ['.github/**', 'distribution/**', 'Directory.Packages.props', 'src/Ryujinx.BuildValidationTasks/**']
documentation:
- changed-files:
@@ -48,4 +44,4 @@ documentation:
ldn:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx.HLE/HOS/Services/Ldn/**', 'src/Ryujinx/UI/Windows/LdnGamesListWindow.*', 'src/Ryujinx/UI/ViewModels/LdnGamesListViewModel.cs']
- any-glob-to-any-file: 'src/Ryujinx.HLE/HOS/Services/Ldn/**'

168
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,168 @@
name: Build job
on:
workflow_call:
env:
POWERSHELL_TELEMETRY_OPTOUT: 1
DOTNET_CLI_TELEMETRY_OPTOUT: 1
RYUJINX_BASE_VERSION: "1.2.0"
RELEASE: 0
jobs:
build:
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
runs-on: ${{ matrix.platform.os }}
timeout-minutes: 45
strategy:
matrix:
configuration: [Debug, Release]
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
fail-fast: false
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 git short hash
id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Change config filename for macOS
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-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
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
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- 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
chmod +x distribution/linux/appimage/build-appimage.sh
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
if [ "$PLATFORM_NAME" = "linux-x64" ]; then
ARCH_NAME=x64
export ARCH=x86_64
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
ARCH_NAME=arm64
export ARCH=aarch64
else
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
exit 1
fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
shell: bash
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
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
build_macos:
name: macOS Universal (${{ matrix.configuration }})
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
matrix:
configuration: [ Debug, Release ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Setup LLVM 17
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install 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 git short hash
id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
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'

View File

@@ -6,7 +6,7 @@ on:
push:
branches: [ master ]
paths-ignore:
- '.forgejo/**'
- '.github/**'
- 'docs/**'
- 'assets/**'
- '*.yml'
@@ -25,41 +25,45 @@ env:
jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: docker
container:
image: ${{ matrix.platform.os }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.forgejo/csc.json"
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
run: echo "::add-matcher::.github/csc.json"
- name: Install 7zip
run: |
sudo apt update && sudo apt install -y 7zip
sudo apt install -y 7zip
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
@@ -84,9 +88,12 @@ jobs:
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
popd
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Packing Linux builds
if: contains(matrix.platform.name, 'linux')
@@ -95,8 +102,9 @@ jobs:
rm libarmeilleure-jitsupport.dylib
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
tar -cJvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
popd
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
shell: bash
- name: Build AppImage (Linux)
@@ -105,7 +113,7 @@ jobs:
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt update && sudo apt install -y zsync desktop-file-utils appstream libfuse2t64
sudo apt install -y zsync desktop-file-utils appstream
mkdir -p tools
export PATH="$PATH:$(readlink -f tools)"
@@ -132,28 +140,17 @@ jobs:
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
popd
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash
- name: Create release
uses: actions/create-release@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
repository: "Ryubing/Canary"
token: ${{ secrets.RELEASER_TOKEN }}
tag_name: ${{ steps.version_info.outputs.build_version }}
files: |-
release_output/**
macos_release:
name: Release MacOS universal
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -163,24 +160,33 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rcodesign
run: |
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
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 /usr/bin/rcodesign
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=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
@@ -195,54 +201,48 @@ jobs:
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -p publish/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_arm64.app.tar.gz
- name: Create release
uses: actions/create-release@v1
with:
name: "Canary ${{ steps.version_info.outputs.build_version }}"
body: "**Full Changelog:** [`${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}`](https://git.ryujinx.app/projects/Ryubing/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
repository: "Ryubing/Canary"
token: ${{ secrets.RELEASER_TOKEN }}
tag_name: ${{ steps.version_info.outputs.build_version }}
files: |-
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
post_ci:
name: Post CI Steps
runs-on: docker
container:
image: ghcr.io/catthehacker/ubuntu:act-latest
create_gitlab_release:
name: Create GitLab Release
runs-on: ubuntu-24.04
needs:
- macos_release
- release
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
echo "build_version=$(gli get-next-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Canary -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Create tag
run: |
gli create-tag -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }}
- name: Link to actual source archives for Canary
run: |
gli canary-release -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -r ${{ steps.version_info.outputs.build_version }}
gli create-tag -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Canary-${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -c "${{ steps.version_info.outputs.commit_message }}"
- name: Create release
run: |
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -n Ryubing-Canary -v ${{ steps.version_info.outputs.build_version }} -r main -t "Canary ${{ steps.version_info.outputs.build_version }}" -b "**Full Changelog:** [${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})"
- name: Send notification webhook
run: |
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P Ryubing/Canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/canary -t ${{ steps.version_info.outputs.build_version }} -c FF4500 -w ${{ secrets.CANARY_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
- name: Notify update server of new builds
run: |

25
.github/workflows/checks.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Build PR
on:
pull_request:
branches: [ master ]
paths:
- '**'
- '!.github/**'
- '!*.yml'
- '!*.config'
- '!*.md'
- '.github/workflows/*.yml'
permissions:
pull-requests: write
checks: write
concurrency:
group: pr-checks-${{ github.event.number }}
cancel-in-progress: true
jobs:
pr_build:
uses: ./.github/workflows/build.yml
secrets: inherit

View File

@@ -0,0 +1,61 @@
name: Comment PR artifacts links
on:
workflow_run:
workflows: ['Build PR']
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
with:
script: |
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
const issue_number = await (async () => {
const pulls = await github.rest.pulls.list({owner, repo});
for await (const {data} of github.paginate.iterator(pulls)) {
for (const pull of data) {
if (pull.head.sha === pull_head_sha) {
return pull.number;
}
}
}
})();
if (issue_number) {
core.info(`Using pull request ${issue_number}`);
} else {
return core.error(`No matching pull request found`);
}
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
const url = `https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip`;
if (art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](${url})`;
} else {
body += `\n* [${art.name}](${url})`;
}
}
hidden_debug_artifacts += `\n</details>`;
body += hidden_debug_artifacts;
const {data: comments} = await github.rest.issues.listComments({repo, owner, issue_number});
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
if (existing_comment) {
core.info(`Updating comment ${existing_comment.id}`);
await github.rest.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
} else {
core.info(`Creating a comment`);
await github.rest.issues.createComment({repo, owner, issue_number, body});
}

View File

@@ -5,22 +5,24 @@ on:
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
# Grab sources to get latest labeler.yml
- name: Fetch sources
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: projects/Ryubing
repository: GreemDev/Ryujinx
ref: master
- name: Update labels based on changes
uses: actions/labeler@v6
uses: actions/labeler@v5
with:
repo-token: ${{ secrets.LABELER_TOKEN }}
configuration-path: .forgejo/labeler.yml
sync-labels: true
dot: true

View File

@@ -19,20 +19,18 @@ env:
jobs:
release:
name: Release for ${{ matrix.platform.name }}
runs-on: docker
container:
image: ${{ matrix.platform.os }}
runs-on: ${{ matrix.platform.os }}
strategy:
matrix:
platform:
- { name: win-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_x64 }
- { name: win-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ghcr.io/catthehacker/ubuntu:act-latest, zip_os_name: linux_arm64 }
- { name: win-x64, os: ubuntu-latest, zip_os_name: win_x64 }
#- { name: win-arm64, os: ubuntu-latest, zip_os_name: win_arm64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -43,21 +41,26 @@ jobs:
run: |
sudo apt install -y 7zip
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
@@ -81,9 +84,12 @@ jobs:
pushd publish
rm libarmeilleure-jitsupport.dylib
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.7z ../publish
popd
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Packing Linux builds
if: contains(matrix.platform.name, 'linux')
@@ -92,9 +98,12 @@ jobs:
rm libarmeilleure-jitsupport.dylib
chmod +x Ryujinx.sh Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish
tar -cJvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.xz ../publish
popd
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build AppImage (Linux)
if: contains(matrix.platform.name, 'linux')
@@ -129,25 +138,17 @@ jobs:
pushd publish_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
popd
shell: bash
- name: Create release
uses: actions/create-release@v1
with:
name: "${{ steps.version_info.outputs.build_version }}"
repository: "projects/Ryubing"
token: ${{ secrets.RELEASER_TOKEN }}
tag_name: ${{ steps.version_info.outputs.build_version }}
files: |-
release_output/**
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
shell: bash
macos_release:
name: Release MacOS universal
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v5
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
@@ -157,28 +158,37 @@ jobs:
chmod +x llvm.sh
sudo ./llvm.sh 17
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install rcodesign
run: |
gli ghr -R indygreg/apple-platform-rs -p apple-codesign-*-x86_64-unknown-linux-musl.tar.gz -O apple-codesign.tar.gz
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 /usr/bin/rcodesign
mv rcodesign $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Configure for release
@@ -191,47 +201,49 @@ jobs:
- name: Publish macOS Ryujinx
run: |
bash distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
- name: Create release
uses: actions/create-release@v1
with:
name: "${{ steps.version_info.outputs.build_version }}"
repository: "projects/Ryubing"
token: ${{ secrets.RELEASER_TOKEN }}
tag_name: ${{ steps.version_info.outputs.build_version }}
files: |-
publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz
post_ci:
name: Post-CI Steps
runs-on: ubuntu-latest
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0
gli upload-generic-package -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -p publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_arm64.app.tar.gz
create_gitlab_release:
name: Create GitLab Release
runs-on: ubuntu-24.04
needs:
- macos_release
- release
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install GLI
uses: actions/setup-gli@v1
with:
token: ${{ secrets.SETUP_GLI_TOKEN }}
- name: Install gli
run: |
mkdir -p $HOME/.bin
gh release download -R GreemDev/GLI -O gli -p 'gli-linux-x64'
chmod +x gli
mv gli $HOME/.bin/
echo "$HOME/.bin" >> $GITHUB_PATH
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Get version info
id: version_info
run: |
if [ '${{ inputs.is_bugfix_release }}' == 'false' ]; then
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -m -c Stable -R)" >> $GITHUB_OUTPUT
else
echo "build_version=$(gli get-next-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "build_version=$(gli get-next-version -c Stable -R)" >> $GITHUB_OUTPUT
fi
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $FORGEJO_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ forgejo.sha }}")" >> $FORGEJO_OUTPUT
echo "prev_build_version=$(gli get-current-version -c Stable -R)" >> $GITHUB_OUTPUT
echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
shell: bash
- name: Create release
run: |
gli create-release-from-generic-package-files -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -n Ryubing -v ${{ steps.version_info.outputs.build_version }} -r ${{ steps.version_info.outputs.git_short_hash }} -t "${{ steps.version_info.outputs.build_version }}" -b "msd:${{ steps.version_info.outputs.build_version }}"
- name: Send notification webhook
run: |
gli send-update-message -T ${{ secrets.RELEASER_TOKEN }} -P projects/Ryubing -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
gli send-update-message -T ${{ secrets.GITLAB_TOKEN }} -P ryubing/ryujinx -t ${{ steps.version_info.outputs.build_version }} -c 32cd32 -w ${{ secrets.STABLE_DISCORD_WEBHOOK }} -i https://avatars.githubusercontent.com/u/192939710?s=200&v=4
- name: Notify update server of new builds
run: |

View File

@@ -3,66 +3,60 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.3.15" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.15" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.15" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.15" />
<PackageVersion Include="SharpCompress" Version="0.48.1" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.9.5" />
<PackageVersion Include="Avalonia" Version="11.3.6" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.3.6" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.6" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.6" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.3.6" />
<PackageVersion Include="Svg.Controls.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Svg.Controls.Skia.Avalonia" Version="11.3.6.2" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.11.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.50" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Projektanker.Icons.Avalonia" Version="9.6.2" />
<PackageVersion Include="Projektanker.Icons.Avalonia.FontAwesome" Version="9.6.2" />
<PackageVersion Include="Projektanker.Icons.Avalonia.MaterialDesign" Version="9.6.2" />
<PackageVersion Include="Ryujinx.SDL3-CS" Version="2026.501.0" />
<PackageVersion Include="ppy.SDL3-CS" Version="2025.920.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageVersion Include="Concentus" Version="2.2.2" />
<PackageVersion Include="DiscordRichPresence" Version="1.6.1.70" />
<PackageVersion Include="DynamicData" Version="9.4.31" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.5.1" />
<PackageVersion Include="DynamicData" Version="9.4.1" />
<PackageVersion Include="FluentAvaloniaUI.NoAnim" Version="2.4.0-build3" />
<PackageVersion Include="Humanizer" Version="2.14.1" />
<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.18.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" />
<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" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.14.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageVersion Include="OpenTK.Core" Version="4.9.4" />
<PackageVersion Include="OpenTK.Graphics" Version="4.9.4" />
<!-- OpenTk.Audio.OpenAL has moved to OpenTk.Audio -->
<!--<PackageVersion Include="OpenTK.Audio" Version="5.0.0-pre.15" />-->
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.9.4" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.9.4" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
<PackageVersion Include="Open.NAT.Core" Version="2.1.0.5" />
<!-- Ryujinx.Audio.OpenAL.Dependencies is from the original project, last updated 12/30/20 -->
<!--<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />-->
<PackageVersion Include="Ryujinx.Audio.OpenAL" Version="1.25.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.4-build6" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.MoltenVK" Version="1.4.2-ryujinx.3" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.133" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="2.0.6" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="2.0.6" />
<PackageVersion Include="Gommon" Version="2.8.1.2" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies.AllArch" Version="6.1.2-build3" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.LibHac" Version="0.21.0-alpha.126" />
<PackageVersion Include="Ryujinx.UpdateClient" Version="1.0.44" />
<PackageVersion Include="Ryujinx.Systems.Update.Common" Version="1.0.44" />
<PackageVersion Include="Gommon" Version="2.8.0.1" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="Sep" Version="0.14.1" />
<PackageVersion Include="Sep" Version="0.11.1" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.23.0" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Win32" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.macOS" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.15" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.1.3" />
<PackageVersion Include="System.IO.Hashing" Version="9.0.2" />
<PackageVersion Include="System.Management" Version="9.0.2" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@@ -5,10 +5,10 @@
</td>
<td align="center" width="75%">
<h1 class="ryu-gradient-text">Ryujinx</h1>
# Ryujinx
[![Latest release](https://git.ryujinx.app/projects/Ryubing/badges/release.svg?label=stable&color=32cd32)](https://update.ryujinx.app/latest/stable)
[![Latest canary release](https://git.ryujinx.app/Ryubing/Canary/badges/release.svg?label=canary&color=FF4500)](https://update.ryujinx.app/latest/canary)
[![Latest release](https://img.shields.io/gitlab/v/release/ryubing%2Fryujinx?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=stable&color=32cd32)](https://update.ryujinx.app/latest/stable)
[![Latest canary release](https://img.shields.io/gitlab/v/release/ryubing%2Fcanary?gitlab_url=https%3A%2F%2Fgit.ryujinx.app&label=canary&color=FF4500)](https://update.ryujinx.app/latest/canary)
<br>
<a href="https://discord.gg/PEuzjrFXUA">
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
@@ -21,7 +21,7 @@
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
It was written from scratch and development on the project began in September 2017.
Ryujinx is available on a self-managed <a class="forgejo-gradient-text" href="https://github.com/Ryubing/forgejo" target="_blank">modified Forgejo</a> instance under the <a href="https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt" target="_blank">MIT license</a>.
Ryujinx is available on a self-managed GitLab instance under the <a href="https://git.ryujinx.app/ryubing/ryujinx/-/blob/master/LICENSE.txt?ref_type=heads" target="_blank">MIT license</a>.
<br />
</p>
<p align="center">
@@ -31,11 +31,11 @@
<br>
This is not a Ryujinx revival project. This is not a Phoenix project.
<br>
Guides and documentation can be found on the <a href="https://git.ryujinx.app/projects/Ryubing/wiki/Home">Wiki tab</a>.
Guides and documentation can be found on the <a href="https://git.ryujinx.app/groups/ryubing/-/wikis/home">Wiki tab</a>.
</p>
<p align="center">
<img src="https://git.ryujinx.app/projects/Ryubing/raw/branch/master/docs/shell.png" alt="Ryujinx example">
<img src="https://git.ryujinx.app/ryubing/ryujinx/-/raw/master/docs/shell.png?ref_type=heads&inline=false" alt="Ryujinx example">
</p>
## Usage
@@ -49,17 +49,17 @@ Stable builds are made every so often, based on the `master` branch, that then g
These stable builds exist so that the end user can get a more **enjoyable and stable experience**.
They are released every month or so, to ensure consistent updates, while not being an annoying amount of individual updates to download over the course of that month.
You can find the stable releases [here](https://git.ryujinx.app/projects/Ryubing/releases).
You can find the stable releases [here](https://git.ryujinx.app/ryubing/ryujinx/-/releases).
Canary builds are compiled automatically for each commit on the `master` branch.
While we strive to ensure optimal stability and performance prior to pushing an update, these builds **may be unstable or completely broken**.
These canary builds are only recommended for experienced users.
You can find the canary releases [here](https://git.ryujinx.app/Ryubing/Canary/releases).
You can find the canary releases [here](https://git.ryujinx.app/ryubing/canary/-/releases).
## Documentation
If you are planning to contribute or just want to learn more about this project please read through our [documentation](https://git.ryujinx.app/projects/Ryubing/src/branch/master/docs/README.md).
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
## Features
@@ -105,13 +105,13 @@ If you are planning to contribute or just want to learn more about this project
## License
This software is licensed under the terms of the [MIT license](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt).
This software is licensed under the terms of the [MIT license](LICENSE.txt).
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](https://git.ryujinx.app/projects/Ryubing/src/branch/master/LICENSE.txt) and [THIRDPARTY.md](https://git.ryujinx.app/projects/Ryubing/src/branch/master/distribution/legal/THIRDPARTY.md) for more details.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits
- [LibHac](https://git.ryujinx.app/projects/LibHac) is used for our file-system.
- [LibHac](https://git.ryujinx.app/ryubing/libhac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View File

@@ -21,8 +21,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.GAL", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.OpenGL", "src\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj", "{9558FB96-075D-4219-8FFF-401979DC0B69}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.RenderDoc", "src\Ryujinx.Graphics.RenderDocApi\Ryujinx.Graphics.RenderDocApi.csproj", "{D58FA894-27D5-4EAA-9042-AD422AD82931}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Texture", "src\Ryujinx.Graphics.Texture\Ryujinx.Graphics.Texture.csproj", "{E1B1AD28-289D-47B7-A106-326972240207}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "src\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}"
@@ -47,8 +45,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vic", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "src\Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Apple", "src\Ryujinx.Audio.Backends.Apple\Ryujinx.Audio.Backends.Apple.csproj", "{AC26EFF0-8593-4184-9A09-98E37EFFB32E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SDL3", "src\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj", "{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "src\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}"
@@ -86,11 +82,11 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.forgejo\workflows\build.yml = .forgejo\workflows\build.yml
.forgejo\workflows\canary.yml = .forgejo\workflows\canary.yml
.github\workflows\build.yml = .github\workflows\build.yml
.github\workflows\canary.yml = .github\workflows\canary.yml
Directory.Packages.props = Directory.Packages.props
Directory.Build.props = Directory.Build.props
.forgejo\workflows\release.yml = .forgejo\workflows\release.yml
.github\workflows\release.yml = .github\workflows\release.yml
nuget.config = nuget.config
EndProjectSection
EndProject
@@ -559,30 +555,6 @@ Global
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x64.Build.0 = Release|Any CPU
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.ActiveCfg = Release|Any CPU
{988E6191-82E1-4E13-9DDB-CB9FA2FDAF29}.Release|x86.Build.0 = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.ActiveCfg = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x64.Build.0 = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.ActiveCfg = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Debug|x86.Build.0 = Debug|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|Any CPU.Build.0 = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.ActiveCfg = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x64.Build.0 = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.ActiveCfg = Release|Any CPU
{D58FA894-27D5-4EAA-9042-AD422AD82931}.Release|x86.Build.0 = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.ActiveCfg = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x64.Build.0 = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.ActiveCfg = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Debug|x86.Build.0 = Debug|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|Any CPU.Build.0 = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.ActiveCfg = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x64.Build.0 = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.ActiveCfg = Release|Any CPU
{AC26EFF0-8593-4184-9A09-98E37EFFB32E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,24 +0,0 @@
{
"Languages": {
"ar_SA": "اَلْعَرَبِيَّةُ",
"de_DE": "Deutsch",
"el_GR": "Ελληνικά",
"en_US": "English (US)",
"es_ES": "Español (ES)",
"fr_FR": "Français (FR)",
"he_IL": "עִברִית",
"it_IT": "Italiano",
"ja_JP": "日本語",
"ko_KR": "한국어",
"no_NO": "Norsk",
"pl_PL": "Polski",
"pt_BR": "Português (BR)",
"ru_RU": "Русский",
"sv_SE": "Svenska",
"th_TH": "ภาษาไทย",
"tr_TR": "Türkçe",
"uk_UA": "Українська",
"zh_CN": "简体中文",
"zh_TW": "繁體中文 (台灣)"
}
}

View File

@@ -1,60 +0,0 @@
# Ryubing Locales
Ryubing Locales uses a custom format, which uses a file for defining the supported languages and a folder of json files for the locales themselves.
Each json file holds the locales for a specific part of the emulator, e.g. the Setup Wizard locales are in `SetupWizard.json`, and each locale entry in the file includes all the supported languages in the same place.
## Languages
In the `/assets/` folder you will find the `Languages.json` file, which defines all the languages supported by the emulator.
The file includes a table of the language codes and their language names.
#Example of the format for Languages.json
{
"Languages": {
"ar_SA": "اَلْعَرَبِيَّةُ",
"en_US": "English (US)",
...
"zh_TW": "繁體中文 (台灣)"
}
}
## Locales
In the `/assets/Locales/` folder you will find the json files, which define all the locales supported by the emulator.
Each json file holds locales for a specific part of the emulator in a large array of locale objects.
Each locale is made up an ID used for lookup and a list of the languages and their matching translations.
Any empty string or null value will automatically use the English translation instead in the emulator.
### Format
When adding a new locale, you just need to add the ID and the en_US language translation, then the validation system will add default values for the rest of languages automatically, when rebuilding the project.
If you want to signal that a translation is supposed to match the English translation, you just have to replace the empty string with `null`.
When you want to check what translations are missing for a language just search for `"<lang_code>": ""`, e.g: `"en_US": ""` (but with any other language, as English will never be missing translations).
### Legacy file (Root.json)
Currently all older locales are stored in `Root.json`, but they are slowly being moved into newer, more descriptive json files, to make the locale system more accessible.
Do **not** add new locales to `Root.json`.
If no json file exists for the specific part of the emulator you're working on, you should instead add a new json file for that part.
#Example of the format for Root.json
{
"Locales": [
{
"ID": "MenuBarActionsOpenMiiEditor",
"Translations": {
"ar_SA": "",
"en_US": "Mii Editor",
...
"zh_TW": "Mii 編輯器"
}
},
{
"ID": "KeyNumber9",
"Translations": {
"ar_SA": "٩",
"en_US": "9",
...
"zh_TW": null
}
}
]
}

View File

@@ -1,329 +0,0 @@
{
"Locales": [
{
"ID": "InstallFromFileFilePickerTitle",
"Translations": {
"ar_SA": "حدد ملف XCI أو أرشيف ZIP لتثبيت البرنامج الثابت منه",
"de_DE": "Wählen Sie eine XCI-Datei oder ein ZIP-Archiv zur Firmware-Installation aus",
"el_GR": "Επιλέξτε ένα αρχείο XCI ή ένα αρχείο ZIP για εγκατάσταση υλικολογισμικού",
"en_US": "Select an XCI file or a ZIP archive to install firmware from",
"es_ES": "Selecciona un archivo XCI o un archivo ZIP para instalar el firmware",
"fr_FR": "Sélectionnez un fichier XCI ou une archive ZIP pour installer le firmware",
"he_IL": "בחר קובץ XCI או ארכיון ZIP להתקנת קושחה",
"it_IT": "Seleziona un file XCI o un archivio ZIP da cui installare il firmware",
"ja_JP": "ファームウェアをインストールする XCI ファイルまたは ZIP アーカイブを選択してください",
"ko_KR": "펌웨어를 설치할 XCI 파일 또는 ZIP 아카이브를 선택하세요",
"no_NO": "Velg en XCI-fil eller et ZIP-arkiv for å installere fastvare fra",
"pl_PL": "Wybierz plik XCI lub archiwum ZIP, z którego chcesz zainstalować firmware",
"pt_BR": "Selecione um arquivo XCI ou um arquivo ZIP para instalar o firmware",
"ru_RU": "Выберите XCI файл или ZIP-архив для установки прошивки",
"sv_SE": "Välj en XCI-fil eller ett ZIP-arkiv för att installera firmware från",
"th_TH": "เลือกไฟล์ XCI หรือไฟล์ ZIP เพื่อติดตั้งเฟิร์มแวร์",
"tr_TR": "Üretici yazılımını yüklemek için bir XCI dosyası veya ZIP arşivi seçin",
"uk_UA": "Виберіть XCI файл або ZIP-архів для встановлення прошивки",
"zh_CN": "选择一个用于安装固件的 XCI 文件或 ZIP 压缩包",
"zh_TW": "選擇一個用於安裝韌體的 XCI 檔案或 ZIP 壓縮檔"
}
},
{
"ID": "InstallFromFolderFilePickerTitle",
"Translations": {
"ar_SA": "حدد مجلدًا لتثبيت البرنامج الثابت منه",
"de_DE": "Wählen Sie einen Ordner aus, um die Firmware zu installieren",
"el_GR": "Επιλέξτε έναν φάκελο για να εγκαταστήσετε το firmware από αυτόν",
"en_US": "Select a folder to install firmware from",
"es_ES": "Selecciona una carpeta para instalar el firmware desde ella",
"fr_FR": "Sélectionnez un dossier pour installer le firmware à partir de celui-ci",
"he_IL": "בחר תיקיה שממנה תותקן הקושחה",
"it_IT": "Seleziona una cartella da cui installare il firmware",
"ja_JP": "ファームウェアをインストールするフォルダを選択してください",
"ko_KR": "펌웨어를 설치할 폴더를 선택하세요",
"no_NO": "Velg en mappe å installere fastvaren fra",
"pl_PL": "Wybierz folder, z którego chcesz zainstalować oprogramowanie układowe",
"pt_BR": "Selecione uma pasta para instalar o firmware a partir dela",
"ru_RU": "Выберите папку, из которой будет установлена прошивка",
"sv_SE": "Välj en mapp att installera firmware från",
"th_TH": "เลือกโฟลเดอร์เพื่อติดตั้งเฟิร์มแวร์จากโฟลเดอร์นั้น",
"tr_TR": "Firmware yüklemek için bir klasör seçin",
"uk_UA": "Виберіть папку, з якої буде встановлено прошивку",
"zh_CN": "选择一个文件夹以从中安装固件",
"zh_TW": "選擇一個資料夾以從中安裝韌體"
}
},
{
"ID": "InstallerTitle",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت {0}",
"de_DE": "Installiere Firmware {0}",
"el_GR": "Εγκατάσταση Firmware {0}",
"en_US": "Install Firmware {0}",
"es_ES": "Instalar Firmware {0}",
"fr_FR": "Installer le Firmware {0}",
"he_IL": "התקן קושחה {0}",
"it_IT": "Installa firmware {0}",
"ja_JP": "ファームウェア {0} をインストール",
"ko_KR": "펌웨어 {0} 설치",
"no_NO": "Installer fastvare {0}",
"pl_PL": "Zainstaluj Firmware {0}",
"pt_BR": "Instalar Firmware {0}",
"ru_RU": "Установить прошивку {0}",
"sv_SE": "Installera firmware {0}",
"th_TH": "ติดตั้งเฟิร์มแวร์ {0}",
"tr_TR": "Firmware {0} Yükle",
"uk_UA": "Встановити прошивку {0}",
"zh_CN": "安装系统固件 {0}",
"zh_TW": "安裝韌體 {0}"
}
},
{
"ID": "InstallerMainMessage",
"Translations": {
"ar_SA": "سيتم تثبيت إصدار النظام {0}.",
"de_DE": "Systemversion {0} wird jetzt installiert.",
"el_GR": "Θα εγκατασταθεί η έκδοση συστήματος {0}.",
"en_US": "System version {0} will be installed.",
"es_ES": "Se instalará la versión de sistema {0}.",
"fr_FR": "La version {0} du système sera installée.",
"he_IL": "גירסת המערכת {0} תותקן.",
"it_IT": "La versione del sistema {0} sarà installata.",
"ja_JP": "システムバージョン {0} がインストールされます。",
"ko_KR": "시스템 버전 {0}이(가) 설치됩니다.",
"no_NO": "Systemversjon {0} vil bli installert.",
"pl_PL": "Wersja systemu {0} zostanie zainstalowana.",
"pt_BR": "A versão do sistema {0} será instalada.",
"ru_RU": "Будет установлена версия прошивки {0}.",
"sv_SE": "Systemversion {0} kommer att installeras.",
"th_TH": "ระบบเวอร์ชั่น {0} ได้รับการติดตั้งเร็วๆ นี้",
"tr_TR": "Sistem sürümü {0} yüklenecek.",
"uk_UA": "Буде встановлено версію системи {0}.",
"zh_CN": "即将安装系统固件版本 {0} 。",
"zh_TW": "即將安裝系統韌體版本 {0}。"
}
},
{
"ID": "InstallerSubMessage",
"Translations": {
"ar_SA": "\n\nهذا سيحل محل إصدار النظام الحالي {0}.",
"de_DE": "\n\nDies wird die aktuelle Systemversion {0} ersetzen.",
"el_GR": "\n\nΑυτό θα αντικαταστήσει την τρέχουσα έκδοση συστήματος {0}.",
"en_US": "\n\nThis will replace the current system version {0}.",
"es_ES": "\n\nEsto reemplazará la versión de sistema actual, {0}.",
"fr_FR": "\n\nCela remplacera la version actuelle du système {0}.",
"he_IL": "\n\nזה יחליף את גרסת המערכת הנוכחית {0}.",
"it_IT": "\n\nQuesta sostituirà l'attuale versione del sistema ({0}).",
"ja_JP": "\n\n現在のシステムバージョン {0} を置き換えます。",
"ko_KR": "\n\n현재 시스템 버전 {0}을(를) 대체합니다.",
"no_NO": "\n\nDette erstatter den gjeldende systemversjonen {0}.",
"pl_PL": "\n\nZastąpi to obecną wersję systemu {0}.",
"pt_BR": "\n\nIsso substituirá a versão do sistema atual {0}.",
"ru_RU": "\n\nЭто заменит текущую версию прошивки {0}.",
"sv_SE": "\n\nDetta kommer att ersätta aktuella systemversionen {0}.",
"th_TH": "\n\nสิ่งนี้จะแทนที่เวอร์ชั่นของระบบเวอร์ชั่นปัจจุบัน {0}.",
"tr_TR": "\n\nBu şimdiki sistem sürümünün yerini alacak {0}.",
"uk_UA": "\n\nЦе замінить поточну версію системи {0}.",
"zh_CN": "\n\n替换当前系统固件版本 {0} 。",
"zh_TW": "\n\n這將取代目前的系統韌體版本 {0}。"
}
},
{
"ID": "InstallerConfirmMessage",
"Translations": {
"ar_SA": "\nهل تريد المتابعة؟",
"de_DE": "\n\nMöchtest du fortfahren?",
"el_GR": "\n\nΘέλετε να συνεχίσετε;",
"en_US": "\n\nDo you want to continue?",
"es_ES": "\n\n¿Continuar?",
"fr_FR": "\n\nVoulez-vous continuer ?",
"he_IL": "\n\nהאם ברצונך להמשיך?",
"it_IT": "\n\nVuoi continuare?",
"ja_JP": "\n\n続けてよろしいですか?",
"ko_KR": "\n\n계속하시겠습니까?",
"no_NO": "\n\nVil du fortsette?",
"pl_PL": "\n\nCzy chcesz kontynuować?",
"pt_BR": "\n\nDeseja continuar?",
"ru_RU": "\n\nПродолжить?",
"sv_SE": "\n\nVill du fortsätta?",
"th_TH": "\n\nคุณต้องการดำเนินการต่อหรือไม่?",
"tr_TR": "\n\nDevam etmek istiyor musunuz?",
"uk_UA": "\n\nВи хочете продовжити?",
"zh_CN": "\n\n是否继续",
"zh_TW": "\n\n您確定要繼續嗎?"
}
},
{
"ID": "InstallerWaitMessage",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت...",
"de_DE": "Firmware wird installiert...",
"el_GR": "Εγκατάσταση Firmware...",
"en_US": "Installing Firmware...",
"es_ES": "Instalando Firmware...",
"fr_FR": "Installation du Firmware...",
"he_IL": "מתקין קושחה...",
"it_IT": "Installazione del firmware...",
"ja_JP": "ファームウェアをインストール中...",
"ko_KR": "펌웨어 설치 중...",
"no_NO": "Installerer fastvare...",
"pl_PL": "Instalowanie firmware'u...",
"pt_BR": "Instalando firmware...",
"ru_RU": "Установка прошивки...",
"sv_SE": "Installerar firmware...",
"th_TH": "กำลังติดตั้งเฟิร์มแวร์...",
"tr_TR": "Firmware yükleniyor...",
"uk_UA": "Встановлення прошивки...",
"zh_CN": "安装系统固件中...",
"zh_TW": "正在安裝韌體..."
}
},
{
"ID": "InstallerSuccessMessage",
"Translations": {
"ar_SA": "تم تثبيت إصدار النظام {0} بنجاح.",
"de_DE": "Systemversion {0} wurde erfolgreich installiert.",
"el_GR": "Η έκδοση συστήματος {0} εγκαταστάθηκε με επιτυχία.",
"en_US": "System version {0} successfully installed.",
"es_ES": "Versión de sistema {0} instalada con éxito.",
"fr_FR": "Version du système {0} installée avec succès.",
"he_IL": "גרסת המערכת {0} הותקנה בהצלחה.",
"it_IT": "La versione del sistema {0} è stata installata.",
"ja_JP": "システムバージョン {0} が正常にインストールされました。",
"ko_KR": "시스템 버전 {0}이(가) 설치되었습니다.",
"no_NO": "Systemversjon {0} ble installert.",
"pl_PL": "Wersja systemu {0} została pomyślnie zainstalowana.",
"pt_BR": "Versão do sistema {0} instalada com sucesso.",
"ru_RU": "Прошивка версии {0} успешно установлена.",
"sv_SE": "Systemversion {0} har installerats.",
"th_TH": "ระบบเวอร์ชั่น {0} ติดตั้งเรียบร้อยแล้ว",
"tr_TR": "Sistem sürümü {0} başarıyla yüklendi.",
"uk_UA": "Версію системи {0} успішно встановлено.",
"zh_CN": "成功安装系统固件版本 {0}。",
"zh_TW": "成功安裝系統韌體版本 {0}。"
}
},
{
"ID": "InstallerInstalledMessage",
"Translations": {
"ar_SA": "تم تثبيت البرنامج الثابت {0}",
"de_DE": "Firmware {0} wurde installiert",
"el_GR": "Το Firmware {0} εγκαταστάθηκε",
"en_US": "Firmware {0} was installed",
"es_ES": "Se Instaló el Firmware {0}",
"fr_FR": "Le firmware {0} a été installé",
"he_IL": "הקושחה {0} הותקנה",
"it_IT": "Il firmware {0} è stato installato",
"ja_JP": "ファームウェア {0} がインストールされました",
"ko_KR": "펌웨어 {0}이(가) 설치됨",
"no_NO": "fastvare {0} ble installert",
"pl_PL": "Firmware {0} został zainstalowany",
"pt_BR": "Firmware {0} foi instalado",
"ru_RU": "Прошивка {0} была установлена",
"sv_SE": "Firmware {0} installerades",
"th_TH": "เฟิร์มแวร์ {0} ติดตั้งแล้ว",
"tr_TR": "Yazılım {0} yüklendi",
"uk_UA": "Встановлено прошивку {0}",
"zh_CN": "已安装系统固件 {0}",
"zh_TW": "已安裝韌體{0}"
}
},
{
"ID": "InstallerNotInstalledMessage",
"Translations": {
"ar_SA": "لا يوجد برنامج ثابت مثبت",
"de_DE": "Keine Firmware installiert.",
"el_GR": "Δεν έχει εγκατασταθεί Firmware.",
"en_US": "No Firmware Installed.",
"es_ES": "No hay Firmware Instalado.",
"fr_FR": "Aucun Firmware Installé.",
"he_IL": "לא מותקנת קושחה.",
"it_IT": "Nessun firmware installato.",
"ja_JP": "ファームウェアがインストールされていません。",
"ko_KR": "펌웨어가 설치되어 있지 .않음",
"no_NO": "Ingen fastvare installert.",
"pl_PL": "Brak Zainstalowanego Firmware'u.",
"pt_BR": "Nenhum Firmware Instalado.",
"ru_RU": "Прошивка не установлена.",
"sv_SE": "Inget firmware installerat.",
"th_TH": "ไม่มีการติดตั้งเฟิร์มแวร์",
"tr_TR": "Yazılım Yüklü Değil.",
"uk_UA": "Прошивка не встановлена.",
"zh_CN": "未安装系统固件。",
"zh_TW": "未安裝韌體。"
}
},
{
"ID": "InstallerFirmwareNotFound",
"Translations": {
"ar_SA": "لم يتم العثور على برنامج ثابت للنظام صالح في {0}.",
"de_DE": "Es wurde keine gültige System-Firmware gefunden in {0}.",
"el_GR": "Δεν βρέθηκε έγκυρο Firmware συστήματος στο {0}.",
"en_US": "A valid system firmware was not found in {0}.",
"es_ES": "No se pudo encontrar un firmware válido en {0}.",
"fr_FR": "Un firmware valide n'a pas été trouvé dans {0}.",
"he_IL": "לא נמצאה קושחת מערכת תקפה ב-{0}.",
"it_IT": "Un firmware del sistema valido non è stato trovato in {0}.",
"ja_JP": "{0} には有効なシステムファームウェアがありません。",
"ko_KR": "{0}에서 유효한 시스템 펌웨어를 찾을 수 없습니다.",
"no_NO": "En gyldig systemfastvare ble ikke funnet i {0}.",
"pl_PL": "Nie znaleziono prawidłowego firmware'u systemowego w {0}.",
"pt_BR": "Um firmware de sistema válido não foi encontrado em {0}.",
"ru_RU": "Не удалось найти действительную системную прошивку в {0}.",
"sv_SE": "Ett giltigt systemfirmware hittades inte i {0}.",
"th_TH": "ไม่พบเฟิร์มแวร์ของระบบที่ถูกต้อง {0}.",
"tr_TR": "{0} da geçerli bir sistem firmware'i bulunamadı.",
"uk_UA": "Дійсна прошивка системи не знайдена в {0}.",
"zh_CN": "在路径 {0} 中找不到有效的 Switch 系统固件。",
"zh_TW": "在 {0} 中未發現有效的系統韌體。"
}
},
{
"ID": "InstallerEmbeddedMessage",
"Translations": {
"ar_SA": "هل ترغب في تثبيت البرنامج الثابت المدمج في هذه اللعبة؟ (البرنامج الثابت {0})",
"de_DE": "Die in diesem Spiel enthaltene Firmware installieren? (Firmware {0})",
"el_GR": "Θα θέλατε να εγκαταστήσετε το Firmware που είναι ενσωματωμένο σε αυτό το παιχνίδι; (Firmware {0})",
"en_US": "Would you like to install the firmware embedded in this game? (Firmware {0})",
"es_ES": "¿Quieres instalar el firmware incluido en este juego? (Firmware versión {0})",
"fr_FR": "Voulez-vous installer le firmware intégré dans ce jeu ? (Firmware {0})",
"he_IL": "האם תרצו להתקין את הקושחה המוטמעת במשחק הזה? (קושחה {0})",
"it_IT": "Vuoi installare il firmware incluso in questo gioco? (Firmware {0})",
"ja_JP": "このゲームに含まれるファームウェアをインストールしてよろしいですか? (ファームウェア {0})",
"ko_KR": "이 게임에 포함된 펌웨어를 설치하시겠습니까?(Firmware {0})",
"no_NO": "Ønsker du å installere fastvaren innebygd i dette spillet? (Firmware {0})",
"pl_PL": "Czy chcesz zainstalować firmware wbudowany w tę grę? (Firmware {0})",
"pt_BR": "Gostaria de instalar o firmware incluso neste jogo? (Firmware {0})",
"ru_RU": "Хотите установить прошивку, встроенную в эту игру? (Прошивка {0})",
"sv_SE": "Vill du installera det firmware som är inbäddat i detta spel? (Firmware {0})",
"th_TH": "คุณต้องการติดตั้งเฟิร์มแวร์ที่ฝังอยู่ในเกมนี้หรือไม่? (เฟิร์มแวร์ {0})",
"tr_TR": "Bu oyunun içine gömülü olan yazılımı yüklemek ister misiniz? (Firmware {0})",
"uk_UA": "Бажаєте встановити прошивку, вбудовану в цю гру? (Прошивка {0})",
"zh_CN": "要安装游戏文件中内嵌的系统固件吗?(固件版本 {0})",
"zh_TW": "您想安裝遊戲內建的韌體嗎? (韌體 {0})"
}
},
{
"ID": "InstallerEmbeddedSuccessMessage",
"Translations": {
"ar_SA": "لم يتم العثور على أي برنامج ثابت مثبت ولكن ريوجينكس كان قادرا على تثبيت البرنامج الثابت {0} من اللعبة المقدمة.\nسيبدأ المحاكي الآن.",
"de_DE": "Es wurde keine installierte Firmware gefunden, aber Ryujinx konnte die Firmware {0} aus dem bereitgestellten Spiel installieren.\nRyujinx wird nun gestartet.",
"el_GR": "Δεν βρέθηκε εγκατεστημένο υλικολογισμικό, αλλά το Ryujinx κατάφερε να εγκαταστήσει το υλικολογισμικό {0} από το παρεχόμενο παιχνίδι.\nΟ προσομοιωτής θα ξεκινήσει τώρα.",
"en_US": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\nThe emulator will now start.",
"es_ES": "No se encontró ningún firmware instalado, pero Ryujinx pudo instalar el firmware {0} del juego proporcionado.\nEl emulador iniciará.",
"fr_FR": "Aucun firmware installé n'a été trouvé mais Ryujinx a pu installer le firmware {0} à partir du jeu fourni.\nL'émulateur va maintenant démarrer.",
"he_IL": "לא נמצאה קושחה מותקנת אבל ריוג'ינקס הצליח להתקין קושחה {0} מהמשחק שסופק. \nהאמולטור יופעל כעת.",
"it_IT": "Non è stato trovato alcun firmware installato, ma Ryujinx è riuscito ad installare il firmware {0} dal gioco fornito.\nL'emulatore si avvierà adesso.",
"ja_JP": "ファームウェアがインストールされていませんが, ゲームに含まれるファームウェア {0} をインストールできます.\nエミュレータが開始します。",
"ko_KR": "설치된 펌웨어를 찾을 수 없지만 Ryujinx는 제공된 게임에서 펌웨어 {0}을(를) 설치할 수 있습니다.\n이제 에뮬레이터가 시작됩니다.",
"no_NO": "Det ble ikke funnet noen installert fastvare, men Ryujinx kunne installere fastvare {0} fra det oppgitte spillet.\nemulatoren vil nå starte.",
"pl_PL": "Nie znaleziono zainstalowanego oprogramowania, ale Ryujinx był w stanie zainstalować oprogramowanie {0} z dostarczonej gry.\n\nEmulator uruchomi się teraz.",
"pt_BR": "Nenhum firmware instalado foi encontrado, mas o Ryujinx conseguiu instalar o firmware {0} a partir do jogo fornecido.\nO emulador será iniciado agora.",
"ru_RU": "Установленной прошивки не было найдено, но Ryujinx удалось установить прошивку {0} из предоставленной игры.\nТеперь запустится эмулятор.",
"sv_SE": "Inget installerat firmware hittades men Ryujinx kunde installera firmware {0} från angiven spel.\nEmulatorn kommer nu att startas.",
"th_TH": "ไม่พบเฟิร์มแวร์ที่ติดตั้งไว้ แต่ Ryujinx จะติดตั้งเฟิร์มแวร์ได้ {0} จากเกมที่ให้มา\nขณะนี้โปรแกรมจำลองจะเริ่มทำงาน",
"tr_TR": "Yüklü bir firmware bulunamadı, ancak Ryujinx sağlanan oyundan firmware {0} yüklemeyi başardı.\nEmülatör şimdi başlatılacak.",
"uk_UA": "Встановлену прошивку не знайдено, але Ryujinx вдалося встановити прошивку {0} з наданої гри.\nТепер запуститься емулятор.",
"zh_CN": "Ryujinx 模拟器已经从当前游戏文件中安装了系统固件 {0} 。\n模拟器现在可以正常运行了。",
"zh_TW": "未找到已安裝的韌體,但 Ryujinx 可以從現有的遊戲安裝韌體{0}。\n模擬器現在可以執行。"
}
}
]
}

View File

@@ -1,204 +0,0 @@
{
"Locales": [
{
"ID": "InstallFromFileFilePickerTitle",
"Translations": {
"ar_SA": "حدد ملف KEYS لتثبيت المفاتيح منه",
"de_DE": "Wählen Sie eine KEYS-Datei zur Schlüsselinstallation aus",
"el_GR": "Επιλέξτε ένα αρχείο KEYS για εγκατάσταση κλειδιών",
"en_US": "Select a KEYS file to install keys from",
"es_ES": "Selecciona un archivo KEYS para instalar las claves",
"fr_FR": "Sélectionnez un fichier KEYS pour installer les clés",
"he_IL": "בחר קובץ KEYS להתקנת מפתחות",
"it_IT": "Seleziona un file KEYS da cui installare le chiavi",
"ja_JP": "キーをインストールする KEYS ファイルを選択してください",
"ko_KR": "키를 설치할 KEYS 파일을 선택하세요",
"no_NO": "Velg en KEYS-fil for å installere nøkler fra",
"pl_PL": "Wybierz plik KEYS, z którego chcesz zainstalować klucze",
"pt_BR": "Selecione um arquivo KEYS para instalar as chaves",
"ru_RU": "Выберите KEYS файл для установки ключей",
"sv_SE": "Välj en KEYS-fil för att installera nycklar från",
"th_TH": "เลือกไฟล์ KEYS เพื่อติดตั้งคีย์",
"tr_TR": "Anahtarları yüklemek için bir KEYS dosyası seçin",
"uk_UA": "Виберіть KEYS файл для встановлення ключів",
"zh_CN": "选择一个用于安装密钥的 KEYS 文件",
"zh_TW": "選擇一個用於安裝金鑰的 KEYS 檔案"
}
},
{
"ID": "InstallFromFolderFilePickerTitle",
"Translations": {
"ar_SA": "حدد مجلدًا لتثبيت المفاتيح منه",
"de_DE": "Wählen Sie einen Ordner aus, um die Schlüssel zu installieren",
"el_GR": "Επιλέξτε έναν φάκελο για να εγκαταστήσετε τα κλειδιά",
"en_US": "Select a folder to install keys from",
"es_ES": "Selecciona una carpeta para instalar las claves",
"fr_FR": "Sélectionnez un dossier pour installer les clés",
"he_IL": "בחר תיקיה להתקין ממנה את המפתחות",
"it_IT": "Seleziona una cartella da cui installare le chiavi",
"ja_JP": "キーをインストールするフォルダを選択してください",
"ko_KR": "키를 설치할 폴더를 선택하세요",
"no_NO": "Velg en mappe å installere nøklene fra",
"pl_PL": "Wybierz folder, z którego zainstalować klucze",
"pt_BR": "Selecione uma pasta para instalar as chaves",
"ru_RU": "Выберите папку, из которой установить ключи",
"sv_SE": "Välj en mapp att installera nycklar från",
"th_TH": "เลือกโฟลเดอร์เพื่อติดตั้งคีย์",
"tr_TR": "Anahtarları yüklemek için bir klasör seçin",
"uk_UA": "Виберіть папку, з якої встановити ключі",
"zh_CN": "选择一个文件夹以安装密钥",
"zh_TW": "選擇一個資料夾以安裝密鑰"
}
},
{
"ID": "InstallerMainMessage",
"Translations": {
"ar_SA": "سيتم تثبيت ملف مفاتيح جديد.",
"de_DE": "Eine neue Schlüsseldatei wird installiert.",
"el_GR": "Ένα νέο αρχείο Κλειδιών θα εγκατασταθεί.",
"en_US": "New Keys file will be installed.",
"es_ES": "Un nuevo archivo de Claves será instalado.",
"fr_FR": "Nouveau fichier de Clés sera installé.",
"he_IL": "קובץ מפתחות חדש יותקן.",
"it_IT": "Un nuovo file di chiavi sarà installato.",
"ja_JP": "新しいキー ファイルがインストールされます。",
"ko_KR": "새로운 키 파일이 설치됩니다.",
"no_NO": "Ny Keys-fil vil bli installert.",
"pl_PL": "Nowy plik kluczy zostanie zainstalowany.",
"pt_BR": "O novo arquivo Chaves será instalado.",
"ru_RU": "Будут установлены новые ключи.",
"sv_SE": "Ny nyckelfil kommer att installeras.",
"th_TH": "กำลังติดตั้งไฟล์ Keys ใหม่",
"tr_TR": "Yeni anahtar dosyası yüklenecek.",
"uk_UA": "Новий файл Ключів буде встановлено.",
"zh_CN": "将会安装新密匙文件。",
"zh_TW": "將會安裝新增的金鑰檔案。"
}
},
{
"ID": "InstallerSubMessage",
"Translations": {
"ar_SA": "\n\nقد يحل هذا محل بعض المفاتيح المثبتة حاليًا.",
"de_DE": "\n\nDies könnte einige der derzeit installierten Schlüssel ersetzen.",
"el_GR": "\n\nΑυτό μπορεί να αντικαταστήσει μερικά από τα τρέχοντα εγκατεστημένα κλειδιά.",
"en_US": "\n\nThis may replace some of the current installed Keys.",
"es_ES": "\n\nEsto puede reemplazar algunas de las Keys actualmente instaladas.",
"fr_FR": "\n\nCela peut remplacer certaines des Clés actuellement installées.",
"he_IL": "\n\nזה עשוי להחליף חלק מהמפתחות המותקנים הנוכחיים.",
"it_IT": "\n\nAlcune delle chiavi già installate potrebbero essere sovrascritte.",
"ja_JP": "\n\nこれにより、現在インストールされているキーの一部が置き換えられる場合があります。",
"ko_KR": "\n\n이로 인해 현재 설치된 키 중 일부가 대체될 수 있습니다.",
"no_NO": "\n\nDette kan erstatte noen av de nåværende installerte nøklene.",
"pl_PL": "\n\nTo może zastąpić niektóre z aktualnie zainstalowanych kluczy.",
"pt_BR": "\n\nIsso pode substituir algumas das chaves instaladas atualmente.",
"ru_RU": "\n\nЭто может заменить некоторые из текущих установленных ключей.",
"sv_SE": "\n\nDetta kan ersätta några av de redan installerade nycklarna.",
"th_TH": "\n\nสิ่งนี้อาจทำให้ไฟล์ Keys บางส่วนที่ติดตั้งอยู่ถูกแทนที่",
"tr_TR": "\n\nBu, şu anda kurulu olan anahtarların bazılarının yerine geçebilir.",
"uk_UA": "\n\nЦе замінить собою поточні файли Ключів.",
"zh_CN": "\n\n这也许会替换掉一些当前已安装的密匙。",
"zh_TW": "\n\n這將取代部分已安裝的金鑰。"
}
},
{
"ID": "InstallerConfirmInstall",
"Translations": {
"ar_SA": "\nهل تريد المتابعة؟",
"de_DE": "\n\nMöchtest du fortfahren?",
"el_GR": "\n\nΘέλετε να συνεχίσετε;",
"en_US": "\n\nDo you want to continue?",
"es_ES": "\n\n¿Continuar?",
"fr_FR": "\n\nVoulez-vous continuer ?",
"he_IL": "\n\nהאם ברצונך להמשיך?",
"it_IT": "\n\nVuoi continuare?",
"ja_JP": "\n\n続けてよろしいですか?",
"ko_KR": "\n\n계속하시겠습니까?",
"no_NO": "\n\nVil du fortsette?",
"pl_PL": "\n\nCzy chcesz kontynuować?",
"pt_BR": "\n\nDeseja continuar?",
"ru_RU": "\n\nПродолжить?",
"sv_SE": "\n\nVill du fortsätta?",
"th_TH": "\n\nคุณต้องการดำเนินการต่อหรือไม่?",
"tr_TR": "\n\nDevam etmek istiyor musunuz?",
"uk_UA": "\n\nВи хочете продовжити?",
"zh_CN": "\n\n是否继续",
"zh_TW": "\n\n您確定要繼續嗎?"
}
},
{
"ID": "InstallerWaitMessage",
"Translations": {
"ar_SA": "جارٍ تثبيت المفاتيح...",
"de_DE": "Schlüssel werden installiert...",
"el_GR": "Εγκατάσταση κλειδιών...",
"en_US": "Installing Keys...",
"es_ES": "Instalando Claves...",
"fr_FR": "Installation des Clés...",
"he_IL": "מתקין מפתחות...",
"it_IT": "Installazione delle chiavi...",
"ja_JP": "キーをインストールしています...",
"ko_KR": "키 설치 중...",
"no_NO": "Installere nøkler...",
"pl_PL": "Instalowanie kluczy...",
"pt_BR": "Instalando Chaves...",
"ru_RU": "Установка ключей...",
"sv_SE": "Installerar nycklar...",
"th_TH": "กำลังดำเนินการติดตั้ง Keys...",
"tr_TR": "Anahtarlar yükleniyor...",
"uk_UA": "Встановлення Ключів...",
"zh_CN": "安装密匙中。。。",
"zh_TW": "正在安裝金鑰。。。"
}
},
{
"ID": "InstallerSuccessMessage",
"Translations": {
"ar_SA": "تم تثبيت ملف المفاتيح الجديد بنجاح.",
"de_DE": "Neue Schlüsseldatei erfolgreich installiert.",
"el_GR": "Το νέο αρχείο Κλειδιών εγκαταστάθηκε με επιτυχία.",
"en_US": "New Keys file successfully installed.",
"es_ES": "Nuevo archivo Keys instalado con éxito.",
"fr_FR": "Nouveau fichier de Clés a été installé.",
"he_IL": "הקובץ החדש של המפתחות הותקן בהצלחה.",
"it_IT": "Nuovo file di chiavi installato con successo.",
"ja_JP": "新しいキー ファイルが正常にインストールされました。",
"ko_KR": "새로운 키 파일이 성공적으로 설치되었습니다.",
"no_NO": "Ny Keys -fil installert.",
"pl_PL": "Nowy plik kluczy został pomyślnie zainstalowany.",
"pt_BR": "Novo arquivo de chaves instalado com sucesso.",
"ru_RU": "Новые ключи успешно установлены.",
"sv_SE": "Ny nyckelfil installerades.",
"th_TH": "การติดตั้งไฟล์ Keys ใหม่เสร็จสมบูรณ์แล้ว",
"tr_TR": "Yeni anahtar dosyası başarıyla yüklendi.",
"uk_UA": "Нові ключі встановлено.",
"zh_CN": "已成功安装新密匙文件。",
"zh_TW": "成功安裝新增的金鑰檔案。"
}
},
{
"ID": "InstallerInvalidKeysFoundMessage",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "An invalid Keys file was found in {0}.",
"es_ES": "Se halló un archivo Keys inválido en {0}.",
"fr_FR": "Un fichier de Clés invalide a été trouvé dans {0}.",
"he_IL": "",
"it_IT": "È stato trovato un file di chiavi non valido in {0}.",
"ja_JP": "",
"ko_KR": "{0}에서 잘못된 키 파일이 발견.",
"no_NO": "En ugyldig Keys-fil ble funnet i {0}.",
"pl_PL": "",
"pt_BR": "Um arquivo Chaves inválido foi encontrado em {0}.",
"ru_RU": "В {0} найден некорректный файл ключей.",
"sv_SE": "En ogiltig nyckelfil hittades i {0}.",
"th_TH": "พบไฟล์ Keys ที่ไม่ถูกต้องใน {0}.",
"tr_TR": "",
"uk_UA": "Виявлено неправильний файл ключів у теці {0}.",
"zh_CN": "在 {0} 发现了一个无效的密匙文件。",
"zh_TW": "找到無效的金鑰檔案 {0}。"
}
}
]
}

View File

@@ -1,154 +0,0 @@
{
"Locales": [
{
"ID": "NoKeysFound",
"Translations": {
"ar_SA": "المفاتيح غير موجودة.",
"de_DE": "Keys nicht gefunden.",
"el_GR": "Τα κλειδιά δεν βρέθηκαν.",
"en_US": "Keys not found.",
"es_ES": "No se encontraron claves.",
"fr_FR": "Clés non trouvées.",
"he_IL": "המפתחות לא נמצאו.",
"it_IT": "Chiavi non trovate.",
"ja_JP": "Keys がありません。",
"ko_KR": "키를 찾을 수 없음.",
"no_NO": "Finner ikke nøkler.",
"pl_PL": "Nie znaleziono kluczy.",
"pt_BR": "Chaves não encontradas.",
"ru_RU": "Ключи не найдены.",
"sv_SE": "Nycklarna hittades inte.",
"th_TH": "ไม่พบ คีย์",
"tr_TR": "Keys bulunamadı.",
"uk_UA": "Ключі не знайдено.",
"zh_CN": "找不到密钥。",
"zh_TW": "找不到金鑰。"
}
},
{
"ID": "NoKeysFoundDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من العثور على ملف \"prod.keys\" الخاص بك.",
"de_DE": "Ryujinx konnte deine \"prod.keys\" Datei nicht finden.",
"el_GR": "Το Ryujinx δεν κατάφερε να εντοπίσει το αρχείο \"prod.keys\".",
"en_US": "Ryujinx was unable to find your \"prod.keys\" file.",
"es_ES": "Ryujinx no pudo encontrar tus \"prod.keys\".",
"fr_FR": "Ryujinx n'a pas pu trouver votre fichier \"prod.keys\".",
"he_IL": "ריוג'ינקס לא הצליח למצוא את קובץ ה-\"prod.keys\" שלך.",
"it_IT": "Ryujinx non è riuscito a trovare il file \"prod.keys\".",
"ja_JP": "\"prod.keys\" が見つかりませんでした。",
"ko_KR": "Ryujinx가 '\"prod.keys\" 파일을 찾지 못함.",
"no_NO": "Ryujinx kunne ikke finne \"prod.keys\" filen din.",
"pl_PL": "Ryujinx nie mógł znaleźć twojego pliku \"prod.keys\".",
"pt_BR": "Ryujinx não conseguiu encontrar o seu arquivo '\"prod.keys\".",
"ru_RU": "Ryujinx не удалось найти ваш \"prod.keys\" файл.",
"sv_SE": "Ryujinx kunde inte hitta din \"prod.keys\"-fil.",
"th_TH": "Ryujinx ไม่พบไฟล์ '\"prod.keys\" ในเครื่องของคุณ",
"tr_TR": "Ryujinx \"prod.keys\" dosyasını bulamadı.",
"uk_UA": "Ryujinx не вдалося знайти ваш файл \"prod.keys\".",
"zh_CN": "Ryujinx 模拟器找不到“prod.keys”密钥文件。",
"zh_TW": "Ryujinx 無法找到您的「prod.keys」檔案。"
}
},
{
"ID": "NoFirmwareFound",
"Translations": {
"ar_SA": "لم يتم العثور على البرنامج الثابت",
"de_DE": "Firmware nicht gefunden",
"el_GR": "Το firmware δε βρέθηκε",
"en_US": "Firmware not found",
"es_ES": "No se encontró Firmware",
"fr_FR": "Firmware introuvable",
"he_IL": "קושחה לא נמצאה",
"it_IT": "Firmware non trovato",
"ja_JP": "ファームウェアがありません",
"ko_KR": "펌웨어를 찾을 수 없음",
"no_NO": "Fastvare ikke funnet",
"pl_PL": "Nie znaleziono firmware'u",
"pt_BR": "Firmware não encontrado",
"ru_RU": "Прошивка не найдена",
"sv_SE": "Firmware hittades inte",
"th_TH": "ไม่พบ เฟิร์มแวร์",
"tr_TR": "Firmware bulunamadı",
"uk_UA": "Прошивка не знайдена",
"zh_CN": "未安装系统固件",
"zh_TW": "找不到韌體"
}
},
{
"ID": "NoFirmwareFoundDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من العثور على أية برامج ثابتة مثبتة.",
"de_DE": "Ryujinx konnte keine installierte Firmware finden!",
"el_GR": "Το Ryujinx δεν κατάφερε να εντοπίσει κανένα εγκατεστημένο firmware.",
"en_US": "Ryujinx was unable to find any firmwares installed.",
"es_ES": "Ryujinx no pudo encontrar un firmware instalado.",
"fr_FR": "Ryujinx n'a pas trouvé de firmware installé.",
"he_IL": "ריוג'ינקס לא הצליחה למצוא קושחה מותקנת.",
"it_IT": "Ryujinx non è riuscito a trovare alcun firmware installato.",
"ja_JP": "インストールされたファームウェアが見つかりませんでした。",
"ko_KR": "Ryujinx가 설치된 펌웨어를 찾을 수 없음.",
"no_NO": "Ryujinx kunne ikke finne noen fastvare installert.",
"pl_PL": "Ryujinx nie mógł znaleźć żadnego zainstalowanego firmware'u.",
"pt_BR": "Ryujinx não conseguiu encontrar nenhum Firmware instalado.",
"ru_RU": "Ryujinx не удалось найти ни одной установленной прошивки.",
"sv_SE": "Ryujinx kunde inte hitta några installerade firmwares.",
"th_TH": "Ryujinx ไม่พบ เฟิร์มแวร์ที่ติดตั้งไว้ในเครื่องของคุณ",
"tr_TR": "Ryujinx yüklü herhangi firmware bulamadı.",
"uk_UA": "Ryujinx не вдалося знайти жодної встановленої прошивки.",
"zh_CN": "Ryujinx 模拟器未安装 Switch 系统固件。",
"zh_TW": "Ryujinx 無法找到已安裝的任何韌體。"
}
},
{
"ID": "FirmwareParsingFailed",
"Translations": {
"ar_SA": "خطأ في تحليل البرنامج الثابت",
"de_DE": "Firmware-Analysierung-Fehler",
"el_GR": "Σφάλμα ανάλυσης firmware",
"en_US": "Firmware parsing error",
"es_ES": "Error al analizar el Firmware",
"fr_FR": "Erreur d'analyse du firmware",
"he_IL": "שגיאת ניתוח קושחה",
"it_IT": "Errore di analisi del firmware",
"ja_JP": "ファームウェアのパーズエラー",
"ko_KR": "펌웨어 구문 분석 오류",
"no_NO": "Fastvare analysefeil",
"pl_PL": "Błąd parsowania firmware'u",
"pt_BR": "Erro de análise de firmware",
"ru_RU": "Ошибка извлечения прошивки",
"sv_SE": "Tolkningsfel i firmware",
"th_TH": "เกิดข้อผิดพลาดในการวิเคราะห์เฟิร์มแวร์",
"tr_TR": "Firmware çözümleme hatası",
"uk_UA": "Помилка аналізу прошивки",
"zh_CN": "固件文件解析出错",
"zh_TW": "韌體解析錯誤"
}
},
{
"ID": "FirmwareParsingFailedDescription",
"Translations": {
"ar_SA": "لم يتمكن ريوجينكس من تحليل البرامج الثابتة المتوفرة. يحدث هذا عادة بسبب المفاتيح القديمة.",
"de_DE": "Ryujinx konnte die zu verfügung gestellte Firmware nicht analysieren. Ein möglicher Grund dafür sind veraltete keys.",
"el_GR": "Το Ryujinx δεν κατάφερε να αναλύσει το συγκεκριμένο firmware. Αυτό συνήθως οφείλετε σε ξεπερασμένα/παλιά κλειδιά.",
"en_US": "Ryujinx was unable to parse the provided firmware. This is usually caused by outdated keys.",
"es_ES": "Ryujinx no pudo analizar el firmware. Normalmente esto ocurre debido a keys desfasadas.",
"fr_FR": "Ryujinx n'a pas pu analyser le firmware fourni. Cela est généralement dû à des clés obsolètes.",
"he_IL": "ריוג'ינקס לא הצליחה לנתח את הקושחה שסופקה. זה נגרם בדרך כלל על ידי מפתחות לא עדכניים.",
"it_IT": "Ryujinx non è riuscito ad analizzare il firmware. Questo di solito è causato da chiavi non aggiornate.",
"ja_JP": "ファームウェアをパーズできませんでした.通常,古いキーが原因です.",
"ko_KR": "Ryujinx가 제공된 펌웨어를 구문 분석하지 못했습니다. 일반적으로 오래된 키로 인해 발생합니다.",
"no_NO": "Ryujinx klarte ikke å analysere levert fastvare. Dette er vanligvis forårsaket av utdaterte nøkler.",
"pl_PL": "Ryujinx nie był w stanie zparsować dostarczonego firmware'u. Jest to zwykle spowodowane nieaktualnymi kluczami.",
"pt_BR": "Ryujinx não conseguiu ler o Firmware fornecido. Geralmente isso é causado por chaves desatualizadas.",
"ru_RU": "Ryujinx не удалось распаковать выбранную прошивку. Обычно это вызвано устаревшими ключами.",
"sv_SE": "Ryujinx kunde inte tolka angiven firmware. Detta sker oftast med utdaterade nycklar.",
"th_TH": "Ryujinx ไม่สามารถวิเคราะห์เฟิร์มแวร์ที่ให้มาได้ ซึ่งมักมีสาเหตุมาจากคีย์ที่เก่าจนเกินไป",
"tr_TR": "Ryujinx temin edilen firmware'i çözümleyemedi. Bu durum genellikle güncel olmayan keys'den kaynaklanır.",
"uk_UA": "Ryujinx не вдалося проаналізувати прошивку. Зазвичай це спричинено застарілими ключами.",
"zh_CN": "Ryujinx 模拟器无法解密当前固件,一般是由于使用了旧版的密钥导致的。",
"zh_TW": "Ryujinx 無法解析所提供的韌體。這通常是由於金鑰過時造成的。"
}
}
]
}

View File

@@ -1,604 +0,0 @@
{
"Locales": [
{
"ID": "ActionsLabel",
"Translations": {
"ar_SA": "_الإجراءات",
"de_DE": "_Aktionen",
"el_GR": "_Δράσεις",
"en_US": "_Actions",
"es_ES": "_Acciones",
"fr_FR": null,
"he_IL": "_פעולות",
"it_IT": "_Azioni",
"ja_JP": "アクション(_A)",
"ko_KR": "동작(_A)",
"no_NO": "_Handlinger",
"pl_PL": "_Akcje",
"pt_BR": "_Ações",
"ru_RU": "_Действия",
"sv_SE": "_Åtgärder",
"th_TH": "_การดำเนินการ",
"tr_TR": "_Eylemler",
"uk_UA": "_Дії",
"zh_CN": "操作(_A)",
"zh_TW": "動作(_A)"
}
},
{
"ID": "InstallKeysLabel",
"Translations": {
"ar_SA": "تثبيت المفاتيح",
"de_DE": "Schlüssel installieren",
"el_GR": "Εγκατάσταση Κλειδιών",
"en_US": "Install Keys",
"es_ES": "Instalar Claves",
"fr_FR": "Installer des Clés",
"he_IL": "התקנת מפתחות",
"it_IT": "Installa chiavi",
"ja_JP": "キーをインストール",
"ko_KR": "설치 키",
"no_NO": "Installere nøkler",
"pl_PL": "Zainstaluj klucze",
"pt_BR": "Instalar Chaves",
"ru_RU": "Установить ключи",
"sv_SE": "Installera nycklar",
"th_TH": "ติดตั้ง Keys",
"tr_TR": "Anahtarları Yükle",
"uk_UA": "Встановити Ключі",
"zh_CN": "安装密匙",
"zh_TW": "安裝金鑰"
}
},
{
"ID": "InstallKeysFromFileButton",
"Translations": {
"ar_SA": "KEYS...",
"de_DE": null,
"el_GR": null,
"en_US": "KEYS...",
"es_ES": null,
"fr_FR": null,
"he_IL": "KEYS...",
"it_IT": null,
"ja_JP": null,
"ko_KR": null,
"no_NO": null,
"pl_PL": null,
"pt_BR": null,
"ru_RU": null,
"sv_SE": null,
"th_TH": null,
"tr_TR": null,
"uk_UA": null,
"zh_CN": null,
"zh_TW": null
}
},
{
"ID": "InstallKeysFromFolderButton",
"Translations": {
"ar_SA": "مجلد...",
"de_DE": "Verzeichnis...",
"el_GR": "Φάκελος...",
"en_US": "Folder...",
"es_ES": "Carpeta...",
"fr_FR": "Dossier...",
"he_IL": "תיקייה...",
"it_IT": "Cartella...",
"ja_JP": "フォルダー...",
"ko_KR": "폴더...",
"no_NO": "Mappe...",
"pl_PL": "Katalog...",
"pt_BR": "Diretório...",
"ru_RU": "Папка...",
"sv_SE": "Katalog...",
"th_TH": "โฟลเดอร์...",
"tr_TR": "Klasör...",
"uk_UA": "Тека...",
"zh_CN": "文件夹...",
"zh_TW": "資料夾..."
}
},
{
"ID": "InstallFirmwareLabel",
"Translations": {
"ar_SA": "تثبيت البرنامج الثابت",
"de_DE": "Firmware installieren",
"el_GR": "Εγκατάσταση Firmware",
"en_US": "Install Firmware",
"es_ES": "Instalar Firmware",
"fr_FR": "Installer le Firmware",
"he_IL": "התקן קושחה",
"it_IT": "Installa firmware",
"ja_JP": "ファームウェアをインストール",
"ko_KR": "펌웨어 설치",
"no_NO": "Installer fastvare",
"pl_PL": "Zainstaluj oprogramowanie",
"pt_BR": "Instalar Firmware",
"ru_RU": "Установить прошивку",
"sv_SE": "Installera firmware",
"th_TH": "ติดตั้งเฟิร์มแวร์",
"tr_TR": "Yazılım Yükle",
"uk_UA": "Встановити прошивку",
"zh_CN": "安装系统固件",
"zh_TW": "安裝韌體"
}
},
{
"ID": "InstallFirmwareFromFileButton",
"Translations": {
"ar_SA": "XCI أو ZIP...",
"de_DE": "XCI oder ZIP...",
"el_GR": "XCI ή ZIP...",
"en_US": "XCI or ZIP...",
"es_ES": "XCI o ZIP...",
"fr_FR": "XCI ou ZIP...",
"he_IL": "XCI או ZIP...",
"it_IT": "XCI o ZIP...",
"ja_JP": "XCI または ZIP...",
"ko_KR": "XCI 또는 ZIP...",
"no_NO": "XCI eller ZIP...",
"pl_PL": "XCI lub ZIP...",
"pt_BR": "XCI ou ZIP...",
"ru_RU": "XCI или ZIP...",
"sv_SE": "XCI eller ZIP...",
"th_TH": "XCI หรือ ZIP...",
"tr_TR": "XCI veya ZIP...",
"uk_UA": "XCI або ZIP...",
"zh_CN": "XCI 或 ZIP...",
"zh_TW": "XCI 或 ZIP..."
}
},
{
"ID": "InstallFirmwareFromFolderButton",
"Translations": {
"ar_SA": "مجلد...",
"de_DE": "Verzeichnis...",
"el_GR": "Φάκελος...",
"en_US": "Folder...",
"es_ES": "Carpeta...",
"fr_FR": "Dossier...",
"he_IL": "תיקייה...",
"it_IT": "Cartella...",
"ja_JP": "フォルダー...",
"ko_KR": "폴더...",
"no_NO": "Mappe...",
"pl_PL": "Katalog...",
"pt_BR": "Diretório...",
"ru_RU": "Папка...",
"sv_SE": "Katalog...",
"th_TH": "โฟลเดอร์...",
"tr_TR": "Klasör...",
"uk_UA": "Тека...",
"zh_CN": "文件夹...",
"zh_TW": "資料夾..."
}
},
{
"ID": "ToolsLabel",
"Translations": {
"ar_SA": "",
"de_DE": "Werkzeuge",
"el_GR": "",
"en_US": "Tools",
"es_ES": "Herramientas",
"fr_FR": "Outils",
"he_IL": "",
"it_IT": "Strumenti",
"ja_JP": "",
"ko_KR": "도구",
"no_NO": "",
"pl_PL": "",
"pt_BR": "Ferramentas",
"ru_RU": "Инструменты",
"sv_SE": "Verktyg",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "工具",
"zh_TW": "工具"
}
},
{
"ID": "MiiEditorButton",
"Translations": {
"ar_SA": "",
"de_DE": "Mii-Editor",
"el_GR": "",
"en_US": "Mii Editor",
"es_ES": "Editor de Mii",
"fr_FR": "Éditeur de Mii",
"he_IL": "",
"it_IT": "Editor di Mii",
"ja_JP": "",
"ko_KR": "Mii 편집기",
"no_NO": "Mii-redigerer",
"pl_PL": "Edytor Mii",
"pt_BR": "Editor de Mii",
"ru_RU": "Редактор Mii",
"sv_SE": "Mii-redigerare",
"th_TH": "",
"tr_TR": "",
"uk_UA": "Редактор Mii",
"zh_CN": "Mii 编辑器",
"zh_TW": "Mii 編輯器"
}
},
{
"ID": "XCITrimmerButton",
"Translations": {
"ar_SA": "",
"de_DE": "XCI-Dateien trimmen",
"el_GR": "",
"en_US": "Trim XCI Files",
"es_ES": "Recortar Archivos XCI",
"fr_FR": "Réduire les Fichiers XCI",
"he_IL": "",
"it_IT": "Riduci dimensioni dei file XCI",
"ja_JP": "",
"ko_KR": "XCI 파일 트리머",
"no_NO": "Trim XCI-filer",
"pl_PL": "",
"pt_BR": "Reduzir Arquivos XCI",
"ru_RU": "Обрезать XCI файлы",
"sv_SE": "Optimera XCI-filer",
"th_TH": "ตัดแต่งไฟล์ XCI",
"tr_TR": "",
"uk_UA": "Обрізати XCI файли",
"zh_CN": "瘦身 XCI 文件",
"zh_TW": "修剪 XCI 檔案"
}
},
{
"ID": "PauseEmulationButton",
"Translations": {
"ar_SA": "إيقاف التشغيل مؤقتًا",
"de_DE": "Emulation pausieren",
"el_GR": "Παύση προσομοίωσης",
"en_US": "Pause Emulation",
"es_ES": "Pausar Emulación",
"fr_FR": "Pauser l'Émulation",
"he_IL": "השהיית האמולציה",
"it_IT": "Pausa emulazione",
"ja_JP": "エミュレーション一時停止",
"ko_KR": "에뮬레이션 일시중지",
"no_NO": "Pause Emulatoren",
"pl_PL": "Wstrzymaj emulację",
"pt_BR": "Pausar emulação",
"ru_RU": "Пауза эмуляции",
"sv_SE": "Pausa emuleringen",
"th_TH": "พักการจำลอง",
"tr_TR": "Emülasyonu Duraklat",
"uk_UA": "Пауза емуляції",
"zh_CN": "暂停模拟",
"zh_TW": "暫停模擬"
}
},
{
"ID": "ResumeEmulationButton",
"Translations": {
"ar_SA": "استئناف المحاكاة",
"de_DE": "Emulation fortsetzen",
"el_GR": "Συνέχιση προσομοίωσης",
"en_US": "Resume Emulation",
"es_ES": "Reanudar Emulación",
"fr_FR": "Reprendre l'Émulation",
"he_IL": "המשך האמולציה",
"it_IT": "Riprendi l'emulazione",
"ja_JP": "エミュレーション再開",
"ko_KR": "에뮬레이션 다시 시작",
"no_NO": "Gjenoppta emuleringen",
"pl_PL": "Wznów emulację",
"pt_BR": "Retomar emulação",
"ru_RU": "Продолжить эмуляцию",
"sv_SE": "Återuppta emuleringen",
"th_TH": "ดำเนินการจำลองต่อ",
"tr_TR": "Emülasyonu Sürdür",
"uk_UA": "Продовжити емуляцію",
"zh_CN": "继续模拟",
"zh_TW": "繼續模擬"
}
},
{
"ID": "StopEmulationButton",
"Translations": {
"ar_SA": "إيقاف المحاكاة",
"de_DE": "Emulation beenden",
"el_GR": "Διακοπή Εξομοίωσης",
"en_US": "Stop Emulation",
"es_ES": "Detener Emulación",
"fr_FR": "Arrêter l'Émulation",
"he_IL": "עצור אמולציה",
"it_IT": "Arresta l'emulazione",
"ja_JP": "エミュレーションを中止",
"ko_KR": "에뮬레이션 중지",
"no_NO": "Stopp Emulering",
"pl_PL": "Zatrzymaj emulację",
"pt_BR": "Parar a Emulação",
"ru_RU": "Остановить эмуляцию",
"sv_SE": "Stoppa emulering",
"th_TH": "หยุดการจำลอง",
"tr_TR": "Emülasyonu Durdur",
"uk_UA": "Зупинити емуляцію",
"zh_CN": "停止模拟",
"zh_TW": "停止模擬"
}
},
{
"ID": "RestartEmulationButton",
"Translations": {
"ar_SA": "",
"de_DE": "Emulation neustarten",
"el_GR": "",
"en_US": "Restart Emulation",
"es_ES": "Reiniciar Emulación",
"fr_FR": "Redémarrer l'Émulation",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Перезапустить эмуляцию",
"sv_SE": "Starta om emulering",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "重启模拟",
"zh_TW": "重新啟動模擬"
}
},
{
"ID": "ScanAmiiboButton",
"Translations": {
"ar_SA": "مسح Amiibo",
"de_DE": "Amiibo scannen",
"el_GR": "Σάρωση Amiibo",
"en_US": "Scan Amiibo",
"es_ES": "Escanear Amiibo",
"fr_FR": "Scanner un Amiibo",
"he_IL": "סרוק אמיבו",
"it_IT": "Scansiona un Amiibo",
"ja_JP": "Amiibo をスキャン",
"ko_KR": "Amiibo 스캔",
"no_NO": "Skann en Amiibo",
"pl_PL": "Skanuj Amiibo",
"pt_BR": "Escanear um Amiibo",
"ru_RU": "Сканировать Amiibo",
"sv_SE": "Skanna en Amiibo",
"th_TH": "สแกนหา Amiibo",
"tr_TR": "Bir Amiibo Tara",
"uk_UA": "Сканувати Amiibo",
"zh_CN": "扫描 Amiibo",
"zh_TW": "掃描 Amiibo"
}
},
{
"ID": "ScanAmiiboFromBinButton",
"Translations": {
"ar_SA": "مسح Amiibo (.BIN)",
"de_DE": "Amiibo scannen (.BIN)",
"el_GR": "Σάρωση Amiibo (.BIN)",
"en_US": "Scan Amiibo (.BIN)",
"es_ES": "Escanear un Amiibo (.BIN)",
"fr_FR": "Scanner un Amiibo (.BIN)",
"he_IL": "סרוק Amiibo (.BIN)",
"it_IT": "Scansiona un Amiibo (.BIN)",
"ja_JP": "Amiibo をスキャン (.BIN)",
"ko_KR": "Amiibo 스캔 (.BIN)",
"no_NO": "Skann en Amiibo (.BIN)",
"pl_PL": "Skanuj Amiibo (.BIN)",
"pt_BR": "Escaneie um Amiibo (.BIN)",
"ru_RU": "Сканировать Amiibo (.BIN)",
"sv_SE": "Skanna en Amiibo (.BIN)",
"th_TH": "สแกนอามีโบ (.BIN)",
"tr_TR": "Amiibo Tara (.BIN)",
"uk_UA": "Сканувати Amiibo (.BIN)",
"zh_CN": "扫描 Amiibo (.BIN)",
"zh_TW": "掃瞄 Amiibo (.BIN)"
}
},
{
"ID": "ScanSkylanderButton",
"Translations": {
"ar_SA": "فحص Skylander",
"de_DE": "Skylander scannen",
"el_GR": "Σάρωση Skylander",
"en_US": "Scan Skylander",
"es_ES": "Escanear Skylander",
"fr_FR": "Scanner un Skylander",
"he_IL": "סרוק אמיבו",
"it_IT": "Scansiona un Skylander",
"ja_JP": "Skylander をスキャン",
"ko_KR": "Skylander 스캔",
"no_NO": "Skann en Skylander",
"pl_PL": "Skanuj Skylander",
"pt_BR": "Escanear um Skylander",
"ru_RU": "Сканировать Skylander",
"sv_SE": "Skanna en Skylander",
"th_TH": "สแกนหา Skylander",
"tr_TR": "Bir Skylander Tara",
"uk_UA": "Сканувати Skylander",
"zh_CN": "扫描 Skylander",
"zh_TW": "掃描 Skylander"
}
},
{
"ID": "RemoveSkylanderButton",
"Translations": {
"ar_SA": "إزالة Skylander",
"de_DE": "Skylander entfernen",
"el_GR": "Αφαίρεση Skylander",
"en_US": "Remove Skylander",
"es_ES": "Eliminar Skylander",
"fr_FR": "Supprimer un Skylander",
"he_IL": "הסר Skylander",
"it_IT": "Rimuovi Skylander",
"ja_JP": "Skylander を削除",
"ko_KR": "Skylander 제거",
"no_NO": "Fjern Skylander",
"pl_PL": "Usuń Skylander",
"pt_BR": "Remover um Skylander",
"ru_RU": "Удалить Skylander",
"sv_SE": "Ta bort Skylander",
"th_TH": "ลบ Skylander",
"tr_TR": "Skylander'ı Kaldır",
"uk_UA": "Видалити Skylander",
"zh_CN": "移除 Skylander",
"zh_TW": "移除 Skylander"
}
},
{
"ID": "TakeScreenshotButton",
"Translations": {
"ar_SA": "أخذ لقطة للشاشة",
"de_DE": "Screenshot aufnehmen",
"el_GR": "Λήψη Στιγμιότυπου",
"en_US": "Take Screenshot",
"es_ES": "Captura de Pantalla",
"fr_FR": "Prendre une Capture d'Écran",
"he_IL": "צלם מסך",
"it_IT": "Cattura uno screenshot",
"ja_JP": "スクリーンショットを撮影",
"ko_KR": "스크린샷 찍기",
"no_NO": "Ta skjermbilde",
"pl_PL": "Zrób zrzut ekranu",
"pt_BR": "Tirar Captura de tela",
"ru_RU": "Сделать снимок экрана",
"sv_SE": "Ta skärmbild",
"th_TH": "ถ่ายภาพหน้าจอ",
"tr_TR": "Ekran Görüntüsü Al",
"uk_UA": "Зробити знімок екрана",
"zh_CN": "保存截屏",
"zh_TW": "儲存擷取畫面"
}
},
{
"ID": "HideUiButton",
"Translations": {
"ar_SA": "إخفاء واجهة المستخدم",
"de_DE": "Oberfläche ausblenden",
"el_GR": "Απόκρυψη UI",
"en_US": "Hide UI",
"es_ES": "Ocultar Interfaz",
"fr_FR": "Masquer l'Interface",
"he_IL": "הסתר ממשק משתמש ",
"it_IT": "Nascondi l'interfaccia",
"ja_JP": "UIを隠す",
"ko_KR": "UI 숨기기",
"no_NO": "Skjul brukergrensesnitt",
"pl_PL": "Ukryj interfejs użytkownika",
"pt_BR": "Esconder Interface",
"ru_RU": "Скрыть интерфейс",
"sv_SE": "Dölj gränssnittet",
"th_TH": "ซ่อน UI",
"tr_TR": "Arayüzü Gizle",
"uk_UA": "Сховати інтерфейс",
"zh_CN": "隐藏菜单栏和状态栏",
"zh_TW": "隱藏 UI"
}
},
{
"ID": "StartRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme starten",
"el_GR": "",
"en_US": "Start RenderDoc Frame Capture",
"es_ES": "Iniciar una captura de fotograma de RenderDoc",
"fr_FR": "Démarrer une capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 시작",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Запустить захват кадра RenderDoc",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "启动 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "EndRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme beenden",
"el_GR": "",
"en_US": "End RenderDoc Frame Capture",
"es_ES": "Detener la captura de fotograma de RenderDoc",
"fr_FR": "Arrêter la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 종료",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Завершить захват кадра RenderDoc",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "DiscardRenderDocCaptureButton",
"Translations": {
"ar_SA": "",
"de_DE": "RenderDoc Frame-Aufnahme verwerfen",
"el_GR": "",
"en_US": "Discard RenderDoc Frame Capture",
"es_ES": "Descartar la captura de fotograma de RenderDoc",
"fr_FR": "Supprimer la capture de trame RenderDoc",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "RenderDoc 프레임 캡처 폐기",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Отменить захват кадра RenderDoc",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "丢弃 RenderDoc 帧捕获",
"zh_TW": ""
}
},
{
"ID": "DiscardRenderDocCaptureToolTip",
"Translations": {
"ar_SA": "",
"de_DE": "Beendet die jetzige RenderDoc Frame-Aufnahme, verwirft sofort das Ergebnis.",
"el_GR": "",
"en_US": "Ends the currently active RenderDoc Frame Capture, immediately discarding its result.",
"es_ES": "Finaliza la captura de fotograma de RenderDoc actualmente activa y descarta inmediatamente su resultado.",
"fr_FR": "Met fin à la capture de trame RenderDoc en cours, en supprimant immédiatement son résultat.",
"he_IL": "",
"it_IT": "",
"ja_JP": "",
"ko_KR": "현재 활성화된 RenderDoc 프레임 캡처를 종료하고 결과를 즉시 폐기합니다.",
"no_NO": "",
"pl_PL": "",
"pt_BR": "",
"ru_RU": "Завершает текущий активный захват кадра RenderDoc и немедленно удаляет его результат.",
"sv_SE": "",
"th_TH": "",
"tr_TR": "",
"uk_UA": "",
"zh_CN": "结束当前正在进行的 RenderDoc 帧捕获,并立即丢弃其结果。",
"zh_TW": ""
}
}
]
}

View File

@@ -1,79 +0,0 @@
{
"Locales": [
{
"ID": "ManageFileTypes",
"Translations": {
"ar_SA": "إدارة أنواع الملفات",
"de_DE": "Dateitypen verwalten",
"el_GR": "Διαχείριση τύπων αρχείων",
"en_US": "Manage File Types",
"es_ES": "Administrar Tipos de Archivo",
"fr_FR": "Gérer les Types de Fichiers",
"he_IL": "ניהול סוגי קבצים",
"it_IT": "Gestisci i tipi di file",
"ja_JP": "ファイル形式を管理",
"ko_KR": "파일 형식 관리",
"no_NO": "Behandle filtyper",
"pl_PL": "Zarządzaj rodzajami plików",
"pt_BR": "Gerenciar Tipos de Arquivos",
"ru_RU": "Управление типами файлов",
"sv_SE": "Hantera filtyper",
"th_TH": "จัดการประเภทไฟล์",
"tr_TR": "Dosya uzantılarını yönet",
"uk_UA": "Керувати типами файлів",
"zh_CN": "管理文件扩展名",
"zh_TW": "管理檔案類型"
}
},
{
"ID": "InstallFileTypes",
"Translations": {
"ar_SA": "تثبيت أنواع الملفات",
"de_DE": "Dateitypen installieren",
"el_GR": "Εγκαταστήσετε τύπους αρχείων.",
"en_US": "Install File Types",
"es_ES": "Instalar Tipos de Archivo",
"fr_FR": "Installer des Types de Fichiers",
"he_IL": "סוגי קבצי התקנה",
"it_IT": "Installa i tipi di file",
"ja_JP": "ファイル形式をインストール",
"ko_KR": "파일 형식 설치",
"no_NO": "Installer filtyper",
"pl_PL": "Typy plików instalacyjnych",
"pt_BR": "Instalar tipos de arquivos",
"ru_RU": "Установить типы файлов",
"sv_SE": "Installera filtyper",
"th_TH": "ติดตั้งประเภทไฟล์",
"tr_TR": "Dosya uzantılarını yükle",
"uk_UA": "Встановити типи файлів",
"zh_CN": "关联文件扩展名",
"zh_TW": "安裝檔案類型"
}
},
{
"ID": "UninstallFileTypes",
"Translations": {
"ar_SA": "إزالة أنواع الملفات",
"de_DE": "Dateitypen deinstallieren",
"el_GR": "Απεγκαταστήσετε τύπους αρχείων",
"en_US": "Uninstall File Types",
"es_ES": "Desinstalar Tipos de Archivo",
"fr_FR": "Désinstaller des Types de Fichiers",
"he_IL": "סוגי קבצי הסרה",
"it_IT": "Disinstalla i tipi di file",
"ja_JP": "ファイル形式をアンインストール",
"ko_KR": "파일 형식 제거",
"no_NO": "Avinstaller filtyper",
"pl_PL": "Typy plików dezinstalacyjnych",
"pt_BR": "Desinstalar tipos de arquivos",
"ru_RU": "Удалить типы файлов",
"sv_SE": "Avinstallera filtyper",
"th_TH": "ถอนการติดตั้งประเภทไฟล์",
"tr_TR": "Dosya uzantılarını kaldır",
"uk_UA": "Видалити типи файлів",
"zh_CN": "取消关联扩展名",
"zh_TW": "移除檔案類型"
}
}
]
}

View File

@@ -1,29 +0,0 @@
{
"Locales": [
{
"ID": "FirmwareVersion",
"Translations": {
"ar_SA": "",
"de_DE": "",
"el_GR": "",
"en_US": "Firmware Version: {0}",
"es_ES": "Versión del Firmware: {0}",
"fr_FR": "Version du Firmware : {0}",
"he_IL": "",
"it_IT": "Versione firmware: {0}",
"ja_JP": "",
"ko_KR": "펌웨어 버전 : {0}",
"no_NO": "Fastvareversjon: {0}",
"pl_PL": "",
"pt_BR": "Versão do Firmware: {0}",
"ru_RU": "Версия прошивки: {0}",
"sv_SE": "Firmware-version: {0}",
"th_TH": "เวอร์ชันเฟิร์มแวร์: {0}",
"tr_TR": "",
"uk_UA": "Версія прошивки: {0}",
"zh_CN": "系统固件版本:{0}",
"zh_TW": "系統韌體版本: {0}"
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -22,5 +22,5 @@ chmod +x AppDir/AppRun AppDir/usr/bin/Ryujinx*
mkdir -p "$OUTDIR"
appimagetool --appimage-extract-and-run -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage
appimagetool -n --comp zstd --mksquashfs-opt -Xcompression-level --mksquashfs-opt 21 \
AppDir "$OUTDIR"/Ryujinx.AppImage

View File

@@ -1,94 +0,0 @@
import argparse
import os
from pathlib import Path
import platform
import shutil
import subprocess
parser = argparse.ArgumentParser(
description="Construct Universal dylibs for nuget package"
)
parser.add_argument(
"arm64_input_directory", help="ARM64 Input directory containing dylibs"
)
parser.add_argument(
"x86_64_input_directory", help="x86_64 Input directory containing dylibs"
)
parser.add_argument("output_directory", help="Output directory")
parser.add_argument("rglob", help="rglob")
args = parser.parse_args()
# Use Apple LLVM on Darwin, otherwise standard LLVM.
if platform.system() == "Darwin":
LIPO = "lipo"
else:
LIPO = shutil.which("llvm-lipo")
if LIPO is None:
for llvm_ver in [17, 16, 15, 14, 13]:
lipo_path = shutil.which(f"llvm-lipo-{llvm_ver}")
if lipo_path is not None:
LIPO = lipo_path
break
if LIPO is None:
raise Exception("Cannot find a valid location for LLVM lipo!")
arm64_input_directory: Path = Path(args.arm64_input_directory)
x86_64_input_directory: Path = Path(args.x86_64_input_directory)
output_directory: Path = Path(args.output_directory)
rglob = args.rglob
def get_new_name(
input_directory: Path, output_directory: str, input_dylib_path: Path
) -> Path:
input_component = str(input_dylib_path).replace(str(input_directory), "")[1:]
return Path(os.path.join(output_directory, input_component))
def get_archs(dylib_path: Path) -> list[str]:
res = subprocess.check_output([LIPO, "-info", str(dylib_path)]).decode("utf-8")
if res.startswith("Non-fat file"):
return [res.split(":")[-1].strip()]
else:
return res.split("are:")[-1].strip().split()
def construct_universal_dylib(
arm64_input_dylib_path: Path, x86_64_input_dylib_path: Path, output_dylib_path: Path
):
if output_dylib_path.exists() or output_dylib_path.is_symlink():
os.remove(output_dylib_path)
os.makedirs(output_dylib_path.parent, exist_ok=True)
if arm64_input_dylib_path.is_symlink():
os.symlink(
os.path.basename(arm64_input_dylib_path.resolve()), output_dylib_path
)
else:
arm64_archs = get_archs(arm64_input_dylib_path)
x86_64_archs = get_archs(x86_64_input_dylib_path) if x86_64_input_dylib_path.exists() else []
if "arm64" in arm64_archs and "x86_64" in arm64_archs:
shutil.copy2(arm64_input_dylib_path, output_dylib_path)
elif x86_64_archs:
subprocess.check_call(
[
LIPO,
str(arm64_input_dylib_path.absolute()),
str(x86_64_input_dylib_path.absolute()),
"-output",
str(output_dylib_path.absolute()),
"-create",
]
)
print(rglob)
for path in arm64_input_directory.rglob("**/*.dylib"):
construct_universal_dylib(
path,
get_new_name(arm64_input_directory, x86_64_input_directory, path),
get_new_name(arm64_input_directory, output_directory, path),
)

View File

@@ -14,7 +14,7 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executable and nsure executable can be executed
# Copy executable and ensure executable can be executed
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"

View File

@@ -33,69 +33,44 @@ if [[ "$(uname)" == "Darwin" ]]; then
fi
if [ "$CANARY" == "1" ]; then
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-canary-$VERSION-macos_arm64.app.tar
elif [ "$VERSION" == "1.1.0" ]; then
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_arm64.app.tar
else
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_arm64.app.tar
fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
OUTPUT_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
rm -rf "$TEMP_DIRECTORY"
mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
pushd "$BASE_DIR/distribution/macos"
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
popd
rm -rf "$UNIVERSAL_APP_BUNDLE"
rm -rf "$OUTPUT_APP_BUNDLE"
mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different app bundle and remove the executable
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
# Make its libraries universal
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-17)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-17
fi
else
LIPO=lipo
fi
# Make the executable universal
$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
# Let's copy the app bundle to the output folder so we can package it freely
cp -R "$ARM64_APP_BUNDLE" "$OUTPUT_APP_BUNDLE"
# Patch up the Info.plist to have appropriate version
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$OUTPUT_APP_BUNDLE/Contents/Info.plist"
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$OUTPUT_APP_BUNDLE/Contents/Info.plist"
rm "$OUTPUT_APP_BUNDLE/Contents/Info.plist.bck"
# Now sign it
if ! [ -x "$(command -v codesign)" ];
@@ -109,10 +84,10 @@ then
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Using rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$OUTPUT_APP_BUNDLE"
else
echo "Using codesign for ad-hoc signing"
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$UNIVERSAL_APP_BUNDLE"
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$OUTPUT_APP_BUNDLE"
fi
echo "Creating archive"

View File

@@ -1,120 +0,0 @@
#!/bin/bash
set -e
if [ "$#" -lt 8 ]; then
echo "usage <BASE_DIR> <TEMP_DIRECTORY> <OUTPUT_DIRECTORY> <ENTITLEMENTS_FILE_PATH> <VERSION> <SOURCE_REVISION_ID> <CONFIGURATION>"
exit 1
fi
mkdir -p "$1"
mkdir -p "$2"
mkdir -p "$3"
BASE_DIR=$(readlink -f "$1")
TEMP_DIRECTORY=$(readlink -f "$2")
OUTPUT_DIRECTORY=$(readlink -f "$3")
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
VERSION=$5
SOURCE_REVISION_ID=$6
CONFIGURATION=$7
if [[ "$(uname)" == "Darwin" ]]; then
echo "Clearing xattr on all dot undercsore files"
find "$BASE_DIR" -type f -name "._*" -exec sh -c '
for f; do
dir=$(dirname "$f")
base=$(basename "$f")
orig="$dir/${base#._}"
[ -f "$orig" ] && xattr -c "$orig" || true
done
' sh {} +
fi
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
rm -rf "$TEMP_DIRECTORY"
mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant
# TODO: remove this once done
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
pushd "$BASE_DIR/distribution/macos"
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
popd
rm -rf "$UNIVERSAL_APP_BUNDLE"
mkdir -p "$OUTPUT_DIRECTORY"
# Let's copy one of the two different app bundle and remove the executable
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
# Make its libraries universal
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
if ! [ -x "$(command -v lipo)" ];
then
if ! [ -x "$(command -v llvm-lipo-17)" ];
then
LIPO=llvm-lipo
else
LIPO=llvm-lipo-17
fi
else
LIPO=lipo
fi
# Make the executable universal
$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
# Patch up the Info.plist to have appropriate version
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
# Now sign it
if ! [ -x "$(command -v codesign)" ];
then
if ! [ -x "$(command -v rcodesign)" ];
then
echo "Cannot find rcodesign on your system, please install rcodesign."
exit 1
fi
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
echo "Using rcodesign for ad-hoc signing"
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
else
echo "Using codesign for ad-hoc signing"
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f -s - "$UNIVERSAL_APP_BUNDLE"
fi
echo "Creating archive"
pushd "$OUTPUT_DIRECTORY"
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME" Ryujinx.app 1> /dev/null
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
popd
echo "Done"

View File

@@ -1061,7 +1061,6 @@
0100BCA016636000,"eBaseball Powerful Pro Yakyuu 2022",gpu;services-horizon;crash,nothing,2024-05-26 23:07:19
01001F20100B8000,"Eclipse: Edge of Light",,playable,2020-08-11 23:06:29
0100E0A0110F4000,"eCrossminton",,playable,2020-07-11 18:24:27
010054601D54C000,"Emio The Smiling Man: Famicom Detective Club (DEMO)",demo,playable,2026-05-13 18:32:12
0100ABE00DB4E000,"Edna & Harvey: Harvey's New Eyes",nvdec,playable,2021-01-26 14:36:08
01004F000B716000,"Edna & Harvey: The Breakout Anniversary Edition",crash;nvdec,ingame,2022-08-01 16:59:56
01002550129F0000,"Effie",,playable,2022-10-27 14:36:39
@@ -1205,7 +1204,7 @@
01003B200E440000,"Five Nights at Freddy's: Sister Location",,playable,2023-10-06 09:00:58
010038200E088000,"Flan",crash;regression,ingame,2021-11-17 07:39:28
01000A0004C50000,"FLASHBACK™",nvdec,playable,2020-05-14 13:57:29
0100C53004C52000,"Flat Heroes",,playable,2026-02-27 17:00:00
0100C53004C52000,"Flat Heroes",gpu,ingame,2022-07-26 19:37:37
0100B54012798000,"Flatland: Prologue",,playable,2020-12-11 20:41:12
0100307004B4C000,"Flinthook",online,playable,2021-03-25 20:42:29
010095A004040000,"Flip Wars",services;ldn-untested,ingame,2022-05-02 15:39:18
@@ -1395,7 +1394,6 @@
0100c3c012718000,"Grand Theft Auto: III The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100182014022000,"Grand Theft Auto: Vice City The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
010065a014024000,"Grand Theft Auto: San Andreas The Definitive Edition",gpu;UE4,ingame,2022-10-31 20:13:52
0100EB500D92E000,"GROOVE COASTER WAI WAI PARTY!!!!",gpu,ingame,2026-05-13 18:32:12
0100822012D76000,"HAAK",gpu,ingame,2023-02-19 14:31:05
01007E100EFA8000,"Habroxia",,playable,2020-06-16 23:04:42
0100535012974000,"Hades",vulkan,playable,2022-10-05 10:45:21
@@ -1834,7 +1832,6 @@
010055200E87E000,"Metamorphosis",UE4;audout;gpu;nvdec,ingame,2021-06-16 16:18:11
0100D4900E82C000,"Metro 2033 Redux",gpu,ingame,2022-11-09 10:53:13
0100F0400E850000,"Metro: Last Light Redux",slow;nvdec;vulkan-backend-bug,ingame,2023-11-01 11:53:52
010019A01E2F2000,"Metroid Prime 4: Beyond",,ingame,2026-05-13 18:32:12
010012101468C000,"Metroid Prime™ Remastered",gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug,ingame,2024-05-07 22:48:15
010093801237C000,"Metroid™ Dread",,playable,2023-11-13 04:02:36
0100A1200F20C000,"Midnight Evil",,playable,2022-10-18 22:55:19
@@ -1948,7 +1945,6 @@
0100C3E00ACAA000,"Mutant Football League: Dynasty Edition",online-broken,playable,2022-08-05 17:01:51
01004BE004A86000,"Mutant Mudds Collection",,playable,2022-08-05 17:11:38
0100E6B00DEA4000,"Mutant Year Zero: Road to Eden - Deluxe Edition",nvdec;UE4,playable,2022-09-10 13:31:10
010037501F864000,"Mute Crimson DX",,ingame,2026-05-13 18:32:12
0100161009E5C000,"MX Nitro: Unleashed",,playable,2022-09-27 22:34:33
0100218011E7E000,"MX vs ATV All Out",nvdec;UE4;vulkan-backend-bug,playable,2022-10-25 19:51:46
0100D940063A0000,"MXGP3 - The Official Motocross Videogame",UE4;gpu;nvdec,ingame,2020-12-16 14:00:20
@@ -2054,9 +2050,7 @@
010003C00B868000,"Ninjin: Clash of Carrots",online-broken,playable,2024-07-10 05:12:26
0100746010E4C000,"NinNinDays",,playable,2022-11-20 15:17:29
0100C9A00ECE6000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
010057D00ECE4000,"Nintendo 64™ Nintendo Switch Online",gpu;vulkan,ingame,2024-04-23 20:21:07
0100e0601c632000,"Nintendo 64™ Nintendo Switch Online: MATURE 17+",,ingame,2025-02-03 22:27:00
010037A0170D2000,"NINTENDO 64™ Nintendo Switch Online 18+",,ingame,2025-02-03 22:27:00
0100D870045B6000,"Nintendo Entertainment System™ - Nintendo Switch Online",online,playable,2022-07-01 15:45:06
0100C4B0034B2000,"Nintendo Labo Toy-Con 01 Variety Kit",gpu,ingame,2022-08-07 12:56:07
01001E9003502000,"Nintendo Labo Toy-Con 03 Vehicle Kit",services;crash,menus,2022-08-03 17:20:11
@@ -2272,7 +2266,6 @@
010086F0064CE000,"Poi: Explorer Edition",nvdec,playable,2021-01-21 19:32:00
0100EB6012FD2000,"Poison Control",,playable,2021-05-16 14:01:54
010072400E04A000,"Pokémon Café ReMix",,playable,2021-08-17 20:00:04
01005B7008C52800,"Pokémon Champions",Needs Update;services;online-broke,menus,2026-05-13 18:32:12
010008c01e742000,"Pokémon Friends",crash;services,menus,2025-07-24 13:32:00
01003D200BAA2000,"Pokémon Mystery Dungeon™: Rescue Team DX",mac-bug,playable,2024-01-21 00:16:32
01008DB008C2C000,"Pokémon Shield + Pokémon Shield Expansion Pass",deadlock;crash;online-broken;ldn-works;LAN,ingame,2024-08-12 07:20:22
@@ -2280,8 +2273,6 @@
01009AD008C4C000,"Pokémon: Let's Go, Pikachu! demo",slow;demo,playable,2023-11-26 11:23:20
0100000011D90000,"Pokémon™ Brilliant Diamond",gpu;ldn-works,ingame,2024-08-28 13:26:35
010018E011D92000,"Pokémon™ Shining Pearl",gpu;ldn-works,ingame,2024-08-28 13:26:35
100554023408000,"Pokémon FireRed Version",crashes,nothing,2026-05-13 18:32:12
010034D02340E000,"Pokémon LeafGreen Version",crashes,nothing,2026-05-13 18:32:12
010015F008C54000,"Pokémon™ HOME",Needs Update;crash;services,menus,2020-12-06 06:01:51
01001F5010DFA000,"Pokémon™ Legends: Arceus",gpu;Needs Update;ldn-works,ingame,2024-09-19 10:02:02
0100F43008C44000,"Pokémon™ Legends: Z-A",gpu;crash;ldn-works,ingame,2025-11-16 00:30:00
@@ -2647,7 +2638,6 @@
0100B16009C10000,"SINNER: Sacrifice for Redemption",nvdec;UE4;vulkan-backend-bug,playable,2022-08-12 20:37:33
0100E9201410E000,"Sir Lovelot",,playable,2021-04-05 16:21:46
0100134011E32000,"Skate City",,playable,2022-11-04 11:37:39
0100a8501b66e000,"Skateboard Drifting with Maxwell Cat: The Game Simulator",,playable,2026-02-17 19:05:00
0100B2F008BD8000,"Skee-Ball",,playable,2020-11-16 04:44:07
01001A900F862000,"Skelattack",,playable,2021-06-09 15:26:26
01008E700F952000,"Skelittle: A Giant Party!",,playable,2021-06-09 19:08:34
@@ -2873,7 +2863,7 @@
0100277011F1A000,"Super Mario Bros.™ 35",online-broken,menus,2022-08-07 16:27:25
010015100B514000,"Super Mario Bros.™ Wonder",amd-vendor-bug,playable,2024-09-06 13:21:21
01009B90006DC000,"Super Mario Maker™ 2",online-broken;ldn-broken,playable,2024-08-25 11:05:19
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug,playable,2026-05-13 18:32:12
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
0100965017338000,"Super Mario Party Jamboree",mac-bug;gpu,ingame,2025-02-17 02:09:20
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
@@ -3170,8 +3160,6 @@
0100E2E00CB14000,"Tokyo School Life",,playable,2022-09-16 20:25:54
010024601BB16000,"Tomb Raider I-III Remastered Starring Lara Croft",gpu;opengl,ingame,2024-09-27 12:32:04
0100D7F01E49C000,"Tomba! Special Edition",services-horizon,nothing,2024-09-15 21:59:54
010051F0207B2000,"Tomodachi Life: Living the Dream",amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken,ingame,2026-05-13 18:32:12
0100CA502552A000,"Tomodachi Life: Living the Dream Welcome Edtion",amd-vendor-bug;demo,playable,2026-05-13 18:32:12
0100D400100F8000,"Tonight We Riot",,playable,2021-02-26 15:55:09
0100CC00102B4000,"Tony Hawk's™ Pro Skater™ 1 + 2",gpu;Needs Update,ingame,2024-09-24 08:18:14
010093F00E818000,"Tools Up!",crash,ingame,2020-07-21 12:58:17
@@ -3319,7 +3307,6 @@
0100AFA011068000,"Voxel Pirates",,playable,2022-09-28 22:55:02
0100BFB00D1F4000,"Voxel Sword",,playable,2022-08-30 14:57:27
01004E90028A2000,"Vroom in the night sky",Needs Update;vulkan-backend-bug,playable,2023-02-20 02:32:29
0100BFC01D976000,"Virtual Boy Nintendo Classics",services,nothing,2026-02-17 11:26:59
0100C7C00AE6C000,"VSR: Void Space Racing",,playable,2021-01-27 14:08:59
0100B130119D0000,"Waifu Uncovered",crash,ingame,2023-02-27 01:17:46
0100E29010A4A000,"Wanba Warriors",,playable,2020-10-04 17:56:22
1 title_id game_name labels status last_updated
1061 0100BCA016636000 eBaseball Powerful Pro Yakyuu 2022 gpu;services-horizon;crash nothing 2024-05-26 23:07:19
1062 01001F20100B8000 Eclipse: Edge of Light playable 2020-08-11 23:06:29
1063 0100E0A0110F4000 eCrossminton playable 2020-07-11 18:24:27
010054601D54C000 Emio – The Smiling Man: Famicom Detective Club (DEMO) demo playable 2026-05-13 18:32:12
1064 0100ABE00DB4E000 Edna & Harvey: Harvey's New Eyes nvdec playable 2021-01-26 14:36:08
1065 01004F000B716000 Edna & Harvey: The Breakout – Anniversary Edition crash;nvdec ingame 2022-08-01 16:59:56
1066 01002550129F0000 Effie playable 2022-10-27 14:36:39
1204 01003B200E440000 Five Nights at Freddy's: Sister Location playable 2023-10-06 09:00:58
1205 010038200E088000 Flan crash;regression ingame 2021-11-17 07:39:28
1206 01000A0004C50000 FLASHBACK™ nvdec playable 2020-05-14 13:57:29
1207 0100C53004C52000 Flat Heroes gpu playable ingame 2026-02-27 17:00:00 2022-07-26 19:37:37
1208 0100B54012798000 Flatland: Prologue playable 2020-12-11 20:41:12
1209 0100307004B4C000 Flinthook online playable 2021-03-25 20:42:29
1210 010095A004040000 Flip Wars services;ldn-untested ingame 2022-05-02 15:39:18
1394 0100c3c012718000 Grand Theft Auto: III – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1395 0100182014022000 Grand Theft Auto: Vice City – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
1396 010065a014024000 Grand Theft Auto: San Andreas – The Definitive Edition gpu;UE4 ingame 2022-10-31 20:13:52
0100EB500D92E000 GROOVE COASTER WAI WAI PARTY!!!! gpu ingame 2026-05-13 18:32:12
1397 0100822012D76000 HAAK gpu ingame 2023-02-19 14:31:05
1398 01007E100EFA8000 Habroxia playable 2020-06-16 23:04:42
1399 0100535012974000 Hades vulkan playable 2022-10-05 10:45:21
1832 010055200E87E000 Metamorphosis UE4;audout;gpu;nvdec ingame 2021-06-16 16:18:11
1833 0100D4900E82C000 Metro 2033 Redux gpu ingame 2022-11-09 10:53:13
1834 0100F0400E850000 Metro: Last Light Redux slow;nvdec;vulkan-backend-bug ingame 2023-11-01 11:53:52
010019A01E2F2000 Metroid Prime 4: Beyond ingame 2026-05-13 18:32:12
1835 010012101468C000 Metroid Prime™ Remastered gpu;Needs Update;vulkan-backend-bug;opengl-backend-bug ingame 2024-05-07 22:48:15
1836 010093801237C000 Metroid™ Dread playable 2023-11-13 04:02:36
1837 0100A1200F20C000 Midnight Evil playable 2022-10-18 22:55:19
1945 0100C3E00ACAA000 Mutant Football League: Dynasty Edition online-broken playable 2022-08-05 17:01:51
1946 01004BE004A86000 Mutant Mudds Collection playable 2022-08-05 17:11:38
1947 0100E6B00DEA4000 Mutant Year Zero: Road to Eden - Deluxe Edition nvdec;UE4 playable 2022-09-10 13:31:10
010037501F864000 Mute Crimson DX ingame 2026-05-13 18:32:12
1948 0100161009E5C000 MX Nitro: Unleashed playable 2022-09-27 22:34:33
1949 0100218011E7E000 MX vs ATV All Out nvdec;UE4;vulkan-backend-bug playable 2022-10-25 19:51:46
1950 0100D940063A0000 MXGP3 - The Official Motocross Videogame UE4;gpu;nvdec ingame 2020-12-16 14:00:20
2050 010003C00B868000 Ninjin: Clash of Carrots online-broken playable 2024-07-10 05:12:26
2051 0100746010E4C000 NinNinDays playable 2022-11-20 15:17:29
2052 0100C9A00ECE6000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
010057D00ECE4000 Nintendo 64™ – Nintendo Switch Online gpu;vulkan ingame 2024-04-23 20:21:07
2053 0100e0601c632000 Nintendo 64™ – Nintendo Switch Online: MATURE 17+ ingame 2025-02-03 22:27:00
010037A0170D2000 NINTENDO 64™ – Nintendo Switch Online 18+ ingame 2025-02-03 22:27:00
2054 0100D870045B6000 Nintendo Entertainment System™ - Nintendo Switch Online online playable 2022-07-01 15:45:06
2055 0100C4B0034B2000 Nintendo Labo Toy-Con 01 Variety Kit gpu ingame 2022-08-07 12:56:07
2056 01001E9003502000 Nintendo Labo Toy-Con 03 Vehicle Kit services;crash menus 2022-08-03 17:20:11
2266 010086F0064CE000 Poi: Explorer Edition nvdec playable 2021-01-21 19:32:00
2267 0100EB6012FD2000 Poison Control playable 2021-05-16 14:01:54
2268 010072400E04A000 Pokémon Café ReMix playable 2021-08-17 20:00:04
01005B7008C52800 Pokémon Champions Needs Update;services;online-broke menus 2026-05-13 18:32:12
2269 010008c01e742000 Pokémon Friends crash;services menus 2025-07-24 13:32:00
2270 01003D200BAA2000 Pokémon Mystery Dungeon™: Rescue Team DX mac-bug playable 2024-01-21 00:16:32
2271 01008DB008C2C000 Pokémon Shield + Pokémon Shield Expansion Pass deadlock;crash;online-broken;ldn-works;LAN ingame 2024-08-12 07:20:22
2273 01009AD008C4C000 Pokémon: Let's Go, Pikachu! demo slow;demo playable 2023-11-26 11:23:20
2274 0100000011D90000 Pokémon™ Brilliant Diamond gpu;ldn-works ingame 2024-08-28 13:26:35
2275 010018E011D92000 Pokémon™ Shining Pearl gpu;ldn-works ingame 2024-08-28 13:26:35
100554023408000 Pokémon FireRed Version crashes nothing 2026-05-13 18:32:12
010034D02340E000 Pokémon LeafGreen Version crashes nothing 2026-05-13 18:32:12
2276 010015F008C54000 Pokémon™ HOME Needs Update;crash;services menus 2020-12-06 06:01:51
2277 01001F5010DFA000 Pokémon™ Legends: Arceus gpu;Needs Update;ldn-works ingame 2024-09-19 10:02:02
2278 0100F43008C44000 Pokémon™ Legends: Z-A gpu;crash;ldn-works ingame 2025-11-16 00:30:00
2638 0100B16009C10000 SINNER: Sacrifice for Redemption nvdec;UE4;vulkan-backend-bug playable 2022-08-12 20:37:33
2639 0100E9201410E000 Sir Lovelot playable 2021-04-05 16:21:46
2640 0100134011E32000 Skate City playable 2022-11-04 11:37:39
0100a8501b66e000 Skateboard Drifting with Maxwell Cat: The Game Simulator playable 2026-02-17 19:05:00
2641 0100B2F008BD8000 Skee-Ball playable 2020-11-16 04:44:07
2642 01001A900F862000 Skelattack playable 2021-06-09 15:26:26
2643 01008E700F952000 Skelittle: A Giant Party! playable 2021-06-09 19:08:34
2863 0100277011F1A000 Super Mario Bros.™ 35 online-broken menus 2022-08-07 16:27:25
2864 010015100B514000 Super Mario Bros.™ Wonder amd-vendor-bug playable 2024-09-06 13:21:21
2865 01009B90006DC000 Super Mario Maker™ 2 online-broken;ldn-broken playable 2024-08-25 11:05:19
2866 0100000000010000 Super Mario Odyssey™ nvdec;intel-vendor-bug;mac-bug;amd-vendor-bug nvdec;intel-vendor-bug;mac-bug playable 2026-05-13 18:32:12 2024-08-25 01:32:34
2867 010036B0034E4000 Super Mario Party™ gpu;Needs Update;ldn-works ingame 2024-06-21 05:10:16
2868 0100965017338000 Super Mario Party Jamboree mac-bug;gpu ingame 2025-02-17 02:09:20
2869 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
3160 0100E2E00CB14000 Tokyo School Life playable 2022-09-16 20:25:54
3161 010024601BB16000 Tomb Raider I-III Remastered Starring Lara Croft gpu;opengl ingame 2024-09-27 12:32:04
3162 0100D7F01E49C000 Tomba! Special Edition services-horizon nothing 2024-09-15 21:59:54
010051F0207B2000 Tomodachi Life: Living the Dream amd-vendor-bug;gpu;intel-vendor-bug;ldn-broken ingame 2026-05-13 18:32:12
0100CA502552A000 Tomodachi Life: Living the Dream – Welcome Edtion amd-vendor-bug;demo playable 2026-05-13 18:32:12
3163 0100D400100F8000 Tonight We Riot playable 2021-02-26 15:55:09
3164 0100CC00102B4000 Tony Hawk's™ Pro Skater™ 1 + 2 gpu;Needs Update ingame 2024-09-24 08:18:14
3165 010093F00E818000 Tools Up! crash ingame 2020-07-21 12:58:17
3307 0100AFA011068000 Voxel Pirates playable 2022-09-28 22:55:02
3308 0100BFB00D1F4000 Voxel Sword playable 2022-08-30 14:57:27
3309 01004E90028A2000 Vroom in the night sky Needs Update;vulkan-backend-bug playable 2023-02-20 02:32:29
0100BFC01D976000 Virtual Boy – Nintendo Classics services nothing 2026-02-17 11:26:59
3310 0100C7C00AE6C000 VSR: Void Space Racing playable 2021-01-27 14:08:59
3311 0100B130119D0000 Waifu Uncovered crash ingame 2023-02-27 01:17:46
3312 0100E29010A4A000 Wanba Warriors playable 2020-10-04 17:56:22

View File

@@ -5,7 +5,8 @@
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<!-- Only needed when using pre-release versions of Ryujinx.LibHac. -->
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/packages/projects/nuget/index.json" />
<add key="LibHacAlpha" value="https://git.ryujinx.app/api/v4/projects/17/packages/nuget/index.json" />
<add key="Ryujinx.UpdateClient" value="https://git.ryujinx.app/api/v4/projects/71/packages/nuget/index.json" />
</packageSources>
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
@@ -13,6 +14,10 @@
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="Ryujinx.UpdateClient">
<package pattern="Ryujinx.UpdateClient" />
<package pattern="Ryujinx.Systems.Update.Common" />
</packageSource>
<packageSource key="LibHacAlpha">
<package pattern="Ryujinx.LibHac" />
</packageSource>

View File

@@ -168,7 +168,7 @@ namespace ARMeilleure.Common
{
_allocated.Dispose();
foreach (nint page in _pages.Values)
foreach (IntPtr page in _pages.Values)
{
NativeAllocator.Instance.Free((void*)page);
}

View File

@@ -1,16 +0,0 @@
namespace Ryujinx.Audio.Backends.Apple
{
class AppleAudioBuffer
{
public readonly ulong DriverIdentifier;
public readonly ulong SampleCount;
public ulong SamplePlayed;
public AppleAudioBuffer(ulong driverIdentifier, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
SampleCount = sampleCount;
SamplePlayed = 0;
}
}
}

View File

@@ -1,196 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.Versioning;
using Ryujinx.Audio.Backends.Apple.Native;
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.Apple
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
public sealed class AppleHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<AppleHardwareDeviceSession, byte> _sessions;
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
public AppleHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<AppleHardwareDeviceSession, byte>();
_supportSurroundConfiguration = TestSurroundSupport();
Volume = 1f;
}
private bool TestSurroundSupport()
{
try
{
AudioStreamBasicDescription format =
GetAudioFormat(SampleFormat.PcmFloat, Constants.TargetSampleRate, 6);
int result = AudioQueueNewOutput(
ref format,
nint.Zero,
nint.Zero,
nint.Zero,
nint.Zero,
0,
out nint testQueue);
if (result == 0)
{
AudioChannelLayout layout = new AudioChannelLayout
{
AudioChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A,
AudioChannelBitmap = 0,
NumberChannelDescriptions = 0
};
int layoutResult = AudioQueueSetProperty(
testQueue,
kAudioQueueProperty_ChannelLayout,
ref layout,
(uint)Marshal.SizeOf<AudioChannelLayout>());
if (layoutResult == 0)
{
AudioQueueDispose(testQueue, true);
return true;
}
AudioQueueDispose(testQueue, true);
}
return false;
}
catch
{
return false;
}
}
public static bool IsSupported => OperatingSystem.IsMacOSVersionAtLeast(10, 5);
public ManualResetEvent GetUpdateRequiredEvent()
=> _updateRequiredEvent;
public ManualResetEvent GetPauseEvent()
=> _pauseEvent;
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager,
SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
channelCount = 2;
}
if (sampleRate == 0)
{
sampleRate = Constants.TargetSampleRate;
}
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on Apple backend!");
}
AppleHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);
return session;
}
internal bool Unregister(AppleHardwareDeviceSession session)
=> _sessions.TryRemove(session, out _);
internal static AudioStreamBasicDescription GetAudioFormat(SampleFormat sampleFormat, uint sampleRate,
uint channelCount)
{
uint formatFlags;
uint bitsPerChannel;
switch (sampleFormat)
{
case SampleFormat.PcmInt8:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 8;
break;
case SampleFormat.PcmInt16:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 16;
break;
case SampleFormat.PcmInt32:
formatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
bitsPerChannel = 32;
break;
case SampleFormat.PcmFloat:
formatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
bitsPerChannel = 32;
break;
default:
throw new ArgumentException($"Unsupported sample format {sampleFormat}");
}
uint bytesPerFrame = (bitsPerChannel / 8) * channelCount;
return new AudioStreamBasicDescription
{
SampleRate = sampleRate,
FormatID = kAudioFormatLinearPCM,
FormatFlags = formatFlags,
BytesPerPacket = bytesPerFrame,
FramesPerPacket = 1,
BytesPerFrame = bytesPerFrame,
ChannelsPerFrame = channelCount,
BitsPerChannel = bitsPerChannel,
Reserved = 0
};
}
public void Dispose()
{
GC.SuppressFinalize(this);
Dispose(true);
}
private void Dispose(bool disposing)
{
if (disposing)
{
foreach (AppleHardwareDeviceSession session in _sessions.Keys)
{
session.Dispose();
}
_pauseEvent.Dispose();
}
}
public bool SupportsDirection(Direction direction)
=> direction != Direction.Input;
public bool SupportsSampleRate(uint sampleRate) => true;
public bool SupportsSampleFormat(SampleFormat sampleFormat)
=> sampleFormat != SampleFormat.PcmInt24;
public bool SupportsChannelCount(uint channelCount)
=> channelCount != 6 || _supportSurroundConfiguration;
}
}

View File

@@ -1,285 +0,0 @@
using Ryujinx.Audio.Backends.Common;
using Ryujinx.Audio.Common;
using Ryujinx.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.Versioning;
using static Ryujinx.Audio.Backends.Apple.Native.AudioToolbox;
namespace Ryujinx.Audio.Backends.Apple
{
[SupportedOSPlatform("macos")]
[SupportedOSPlatform("ios")]
class AppleHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private const int NumBuffers = 3;
private readonly AppleHardwareDeviceDriver _driver;
private readonly ConcurrentQueue<AppleAudioBuffer> _queuedBuffers = new();
private readonly DynamicRingBuffer _ringBuffer = new();
private readonly ManualResetEvent _updateRequiredEvent;
private readonly AudioQueueOutputCallback _callbackDelegate;
private readonly GCHandle _gcHandle;
private nint _audioQueue;
private readonly nint[] _audioQueueBuffers = new nint[NumBuffers];
private readonly int[] _bufferBytesFilled = new int[NumBuffers];
private readonly int _bytesPerFrame;
private ulong _playedSampleCount;
private bool _started;
private float _volume = 1f;
private readonly object _lock = new();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void AudioQueueOutputCallback(
nint userData,
nint audioQueue,
nint buffer);
public AppleHardwareDeviceSession(
AppleHardwareDeviceDriver driver,
IVirtualMemoryManager memoryManager,
SampleFormat requestedSampleFormat,
uint requestedSampleRate,
uint requestedChannelCount)
: base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = driver.GetUpdateRequiredEvent();
_callbackDelegate = OutputCallback;
_bytesPerFrame = BackendHelper.GetSampleSize(requestedSampleFormat) * (int)requestedChannelCount;
_gcHandle = GCHandle.Alloc(this, GCHandleType.Normal);
SetupAudioQueue();
}
private void SetupAudioQueue()
{
lock (_lock)
{
AudioStreamBasicDescription format = AppleHardwareDeviceDriver.GetAudioFormat(
RequestedSampleFormat,
RequestedSampleRate,
RequestedChannelCount);
nint callbackPtr = Marshal.GetFunctionPointerForDelegate(_callbackDelegate);
nint userData = GCHandle.ToIntPtr(_gcHandle);
int result = AudioQueueNewOutput(
ref format,
callbackPtr,
userData,
nint.Zero,
nint.Zero,
0,
out _audioQueue);
if (result != 0)
{
throw new InvalidOperationException($"AudioQueueNewOutput failed: {result}");
}
uint framesPerBuffer = RequestedSampleRate / 100;
uint bufferSize = framesPerBuffer * (uint)_bytesPerFrame;
for (int i = 0; i < NumBuffers; i++)
{
AudioQueueAllocateBuffer(_audioQueue, bufferSize, out _audioQueueBuffers[i]);
_bufferBytesFilled[i] = 0;
PrimeBuffer(_audioQueueBuffers[i], i);
}
}
}
private unsafe void PrimeBuffer(nint bufferPtr, int bufferIndex)
{
AudioQueueBuffer* buffer = (AudioQueueBuffer*)bufferPtr;
int capacityBytes = (int)buffer->AudioDataBytesCapacity;
int framesPerBuffer = capacityBytes / _bytesPerFrame;
int availableFrames = _ringBuffer.Length / _bytesPerFrame;
int framesToRead = Math.Min(availableFrames, framesPerBuffer);
int bytesToRead = framesToRead * _bytesPerFrame;
Span<byte> dst = new((void*)buffer->AudioData, capacityBytes);
dst.Clear();
if (bytesToRead > 0)
{
Span<byte> audio = dst.Slice(0, bytesToRead);
_ringBuffer.Read(audio, 0, bytesToRead);
ApplyVolume(buffer->AudioData, bytesToRead);
}
buffer->AudioDataByteSize = (uint)capacityBytes;
_bufferBytesFilled[bufferIndex] = bytesToRead;
AudioQueueEnqueueBuffer(_audioQueue, bufferPtr, 0, nint.Zero);
}
private void OutputCallback(nint userData, nint audioQueue, nint bufferPtr)
{
if (!_started || bufferPtr == nint.Zero)
return;
int bufferIndex = Array.IndexOf(_audioQueueBuffers, bufferPtr);
if (bufferIndex < 0)
return;
int bytesPlayed = _bufferBytesFilled[bufferIndex];
if (bytesPlayed > 0)
{
ProcessPlayedSamples(bytesPlayed);
}
PrimeBuffer(bufferPtr, bufferIndex);
}
private void ProcessPlayedSamples(int bytesPlayed)
{
ulong samplesPlayed = GetSampleCount(bytesPlayed);
ulong remaining = samplesPlayed;
bool needUpdate = false;
while (remaining > 0 && _queuedBuffers.TryPeek(out AppleAudioBuffer buffer))
{
ulong needed = buffer.SampleCount - Interlocked.Read(ref buffer.SamplePlayed);
ulong take = Math.Min(needed, remaining);
ulong played = Interlocked.Add(ref buffer.SamplePlayed, take);
remaining -= take;
if (played == buffer.SampleCount)
{
_queuedBuffers.TryDequeue(out _);
needUpdate = true;
}
Interlocked.Add(ref _playedSampleCount, take);
}
if (needUpdate)
{
_updateRequiredEvent.Set();
}
}
private unsafe void ApplyVolume(nint dataPtr, int byteSize)
{
float volume = Math.Clamp(_volume * _driver.Volume, 0f, 1f);
if (volume >= 0.999f)
return;
int sampleCount = byteSize / BackendHelper.GetSampleSize(RequestedSampleFormat);
switch (RequestedSampleFormat)
{
case SampleFormat.PcmInt16:
short* s16 = (short*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s16[i] = (short)(s16[i] * volume);
break;
case SampleFormat.PcmFloat:
float* f32 = (float*)dataPtr;
for (int i = 0; i < sampleCount; i++)
f32[i] *= volume;
break;
case SampleFormat.PcmInt32:
int* s32 = (int*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s32[i] = (int)(s32[i] * volume);
break;
case SampleFormat.PcmInt8:
sbyte* s8 = (sbyte*)dataPtr;
for (int i = 0; i < sampleCount; i++)
s8[i] = (sbyte)(s8[i] * volume);
break;
}
}
public override void QueueBuffer(AudioBuffer buffer)
{
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_queuedBuffers.Enqueue(new AppleAudioBuffer(buffer.DataPointer, GetSampleCount(buffer)));
}
public override void Start()
{
lock (_lock)
{
if (_started)
return;
_started = true;
AudioQueueStart(_audioQueue, nint.Zero);
}
}
public override void Stop()
{
lock (_lock)
{
if (!_started)
return;
_started = false;
AudioQueuePause(_audioQueue);
}
}
public override ulong GetPlayedSampleCount()
=> Interlocked.Read(ref _playedSampleCount);
public override float GetVolume() => _volume;
public override void SetVolume(float volume) => _volume = volume;
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{
if (!_queuedBuffers.TryPeek(out AppleAudioBuffer driverBuffer))
return true;
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
public override void PrepareToClose() { }
public override void UnregisterBuffer(AudioBuffer buffer) { }
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Stop();
if (_audioQueue != nint.Zero)
{
AudioQueueStop(_audioQueue, true);
AudioQueueDispose(_audioQueue, true);
_audioQueue = nint.Zero;
}
if (_gcHandle.IsAllocated)
{
_gcHandle.Free();
}
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,102 +0,0 @@
using System.Runtime.InteropServices;
// ReSharper disable InconsistentNaming
namespace Ryujinx.Audio.Backends.Apple.Native
{
public static partial class AudioToolbox
{
[StructLayout(LayoutKind.Sequential)]
internal struct AudioStreamBasicDescription
{
public double SampleRate;
public uint FormatID;
public uint FormatFlags;
public uint BytesPerPacket;
public uint FramesPerPacket;
public uint BytesPerFrame;
public uint ChannelsPerFrame;
public uint BitsPerChannel;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct AudioChannelLayout
{
public uint AudioChannelLayoutTag;
public uint AudioChannelBitmap;
public uint NumberChannelDescriptions;
}
internal const uint kAudioFormatLinearPCM = 0x6C70636D;
internal const uint kAudioQueueProperty_ChannelLayout = 0x6171636c;
internal const uint kAudioChannelLayoutTag_MPEG_5_1_A = 0x650006;
internal const uint kAudioFormatFlagIsFloat = (1 << 0);
internal const uint kAudioFormatFlagIsSignedInteger = (1 << 2);
internal const uint kAudioFormatFlagIsPacked = (1 << 3);
internal const uint kAudioFormatFlagIsBigEndian = (1 << 1);
internal const uint kAudioFormatFlagIsAlignedHigh = (1 << 4);
internal const uint kAudioFormatFlagIsNonInterleaved = (1 << 5);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueNewOutput(
ref AudioStreamBasicDescription format,
nint callback,
nint userData,
nint callbackRunLoop,
nint callbackRunLoopMode,
uint flags,
out nint audioQueue);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueSetProperty(
nint audioQueue,
uint propertyID,
ref AudioChannelLayout layout,
uint layoutSize);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueDispose(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueAllocateBuffer(
nint audioQueue,
uint bufferByteSize,
out nint buffer);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueStart(nint audioQueue, nint startTime);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueuePause(nint audioQueue);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueStop(nint audioQueue, [MarshalAs(UnmanagedType.I1)] bool immediate);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueSetParameter(
nint audioQueue,
uint parameterID,
float value);
[LibraryImport("/System/Library/Frameworks/AudioToolbox.framework/AudioToolbox")]
internal static partial int AudioQueueEnqueueBuffer(
nint audioQueue,
nint buffer,
uint numPacketDescs,
nint packetDescs);
[StructLayout(LayoutKind.Sequential)]
internal struct AudioQueueBuffer
{
public uint AudioDataBytesCapacity;
public nint AudioData;
public uint AudioDataByteSize;
public nint UserData;
public uint PacketDescriptionCapacity;
public nint PacketDescriptions;
public uint PacketDescriptionCount;
}
internal const uint kAudioQueueParam_Volume = 1;
}
}

View File

@@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
</ItemGroup>
</Project>

View File

@@ -10,8 +10,7 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.OpenAL
{
// ReSharper disable once InconsistentNaming
public sealed class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ALDevice _device;
private readonly ALContext _context;
@@ -149,7 +148,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
Dispose(true);
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing)
{

View File

@@ -9,8 +9,7 @@ using System.Threading;
namespace Ryujinx.Audio.Backends.OpenAL
{
// ReSharper disable once InconsistentNaming
sealed class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly OpenALHardwareDeviceDriver _driver;
private readonly int _sourceId;
@@ -191,7 +190,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing && _driver.Unregister(this))
{

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Backends.SDL3
using unsafe SDL_AudioStreamCallbackPointer = delegate* unmanaged[Cdecl]<nint, SDL_AudioStream*, int, int, void>;
public sealed class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
private readonly ManualResetEvent _pauseEvent;
@@ -162,7 +162,7 @@ namespace Ryujinx.Audio.Backends.SDL3
Dispose(true);
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing)
{

View File

@@ -12,7 +12,10 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Audio.Backends.SDL3
{
sealed unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
unsafe class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly SDL3HardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
@@ -223,7 +226,7 @@ namespace Ryujinx.Audio.Backends.SDL3
return driverBuffer.DriverIdentifier != buffer.DataPointer;
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing && _driver.Unregister(this))
{

View File

@@ -130,7 +130,7 @@ namespace Ryujinx.Audio.Backends.SoundIo.Native
unsafe
{
int* frameCountPtr = &nativeFrameCount;
nint* arenasPtr = &arenas;
IntPtr* arenasPtr = &arenas;
CheckError(soundio_outstream_begin_write(_context, (nint)arenasPtr, (nint)frameCountPtr));
frameCount = *frameCountPtr;

View File

@@ -10,7 +10,7 @@ using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
namespace Ryujinx.Audio.Backends.SoundIo
{
public sealed class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly SoundIoContext _audioContext;
private readonly SoundIoDeviceContext _audioDevice;
@@ -227,7 +227,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
}
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing)
{

View File

@@ -11,7 +11,7 @@ using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
namespace Ryujinx.Audio.Backends.SoundIo
{
sealed class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
{
private readonly SoundIoHardwareDeviceDriver _driver;
private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
@@ -428,7 +428,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
}
}
private void Dispose(bool disposing)
protected virtual void Dispose(bool disposing)
{
if (disposing && _driver.Unregister(this))
{

View File

@@ -17,7 +17,7 @@ namespace Ryujinx.Audio.Renderer.Common
public uint MixesSize;
public uint SinksSize;
public uint PerformanceBufferSize;
public uint SplitterSize;
public uint Unknown24;
public uint RenderInfoSize;
#pragma warning disable IDE0051, CS0169 // Remove unused field

View File

@@ -433,12 +433,8 @@ namespace Ryujinx.Audio.Renderer.Server
public ResultCode UpdateSplitter(SplitterContext context)
{
long initialInputConsumed = _inputReader.Consumed;
if (context.Update(ref _inputReader))
{
_inputReader.SetConsumed(initialInputConsumed + _inputHeader.SplitterSize);
return ResultCode.Success;
}

View File

@@ -11,7 +11,9 @@ namespace Ryujinx.BuildValidationTasks
{
static readonly JsonSerializerOptions _jsonOptions = new()
{
WriteIndented = true, NewLine = "\n", Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
WriteIndented = true,
NewLine = "\n",
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
public LocalesValidationTask() { }
@@ -20,116 +22,77 @@ namespace Ryujinx.BuildValidationTasks
{
Console.WriteLine("Running Locale Validation Task...");
bool encounteredIssue = false;
string langPath = projectPath + "assets/Languages.json";
string path = projectPath + "assets/locales.json";
string data;
using (StreamReader sr = new(langPath))
using (StreamReader sr = new(path))
{
data = sr.ReadToEnd();
}
if (isGitRunner && data.Contains("\r\n"))
throw new FormatException("Languages.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
LocalesJson json;
LanguagesJson langJson;
if (isGitRunner && data.Contains("\r\n"))
throw new FormatException("locales.json is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
try
{
langJson = JsonSerializer.Deserialize<LanguagesJson>(data);
json = JsonSerializer.Deserialize<LocalesJson>(data);
}
catch (JsonException e)
{
throw new JsonException(e.Message); //shorter and easier stacktrace
}
foreach ((string code, string lang) in langJson.Languages)
bool encounteredIssue = false;
for (int i = 0; i < json.Locales.Count; i++)
{
if (string.IsNullOrEmpty(lang))
LocalesEntry locale = json.Locales[i];
foreach (string langCode in json.Languages.Where(lang => !locale.Translations.ContainsKey(lang)))
{
throw new JsonException($"{code} language name missing!");
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations.Add(langCode, string.Empty);
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
}
else
{
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
}
}
foreach (string langCode in json.Languages.Where(lang => locale.Translations.ContainsKey(lang) && lang != "en_US" && locale.Translations[lang] == locale.Translations["en_US"]))
{
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations[langCode] = string.Empty;
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
}
else
{
Console.WriteLine($"Lanugage '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
}
}
locale.Translations = locale.Translations.OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value);
json.Locales[i] = locale;
}
string folderPath = projectPath + "assets/Locales/";
if (isGitRunner && encounteredIssue)
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
string[] paths = Directory.GetFiles(folderPath, "*.json", SearchOption.AllDirectories);
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
foreach (string path in paths)
using (StreamWriter sw = new(path))
{
using (StreamReader sr = new(path))
{
data = sr.ReadToEnd();
}
if (isGitRunner && data.Contains("\r\n"))
throw new FormatException($"{Path.GetFileName(path)} is using CRLF line endings! It should be using LF line endings, rebuild locally to fix...");
LocalesJson json;
try
{
json = JsonSerializer.Deserialize<LocalesJson>(data);
}
catch (JsonException e)
{
throw new JsonException(e.Message); //shorter and easier stacktrace
}
for (int i = 0; i < json.Locales.Count; i++)
{
LocalesEntry locale = json.Locales[i];
foreach (string langCode in
langJson.Languages.Keys.Where(lang => !locale.Translations.ContainsKey(lang)))
{
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations.Add(langCode, string.Empty);
Console.WriteLine($"Added '{langCode}' to Locale '{locale.ID}'");
}
else
{
Console.WriteLine($"Missing '{langCode}' in Locale '{locale.ID}'!");
}
}
foreach (string langCode in langJson.Languages.Keys.Where(lang =>
locale.Translations.ContainsKey(lang) && lang != "en_US" &&
locale.Translations[lang] == locale.Translations["en_US"]))
{
encounteredIssue = true;
if (!isGitRunner)
{
locale.Translations[langCode] = string.Empty;
Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'! Resetting it...");
}
else
{
Console.WriteLine(
$"Language '{langCode}' is a duplicate of en_US in Locale '{locale.ID}'!");
}
}
locale.Translations = locale.Translations.OrderBy(pair => pair.Key)
.ToDictionary(pair => pair.Key, pair => pair.Value);
json.Locales[i] = locale;
}
if (isGitRunner && encounteredIssue)
throw new JsonException("1 or more locales are invalid! Rebuild locally to fix...");
string jsonString = JsonSerializer.Serialize(json, _jsonOptions);
using (StreamWriter sw = new(path))
{
sw.Write(jsonString);
}
sw.Write(jsonString);
}
Console.WriteLine("Finished Locale Validation Task!");
@@ -137,13 +100,10 @@ namespace Ryujinx.BuildValidationTasks
return true;
}
struct LanguagesJson
{
public Dictionary<string, string> Languages { get; set; }
}
struct LocalesJson
{
public Dictionary<string, string> Info { get; set; }
public List<string> Languages { get; set; }
public List<LocalesEntry> Locales { get; set; }
}

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<AntiAliasing>))]
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
public enum AntiAliasing
{
None,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<AspectRatio>))]
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
public enum AspectRatio
{
Fixed4x3,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<BackendThreading>))]
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
public enum BackendThreading
{
Auto,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsBackend>))]
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
public enum GraphicsBackend
{
Vulkan,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<GraphicsDebugLevel>))]
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
public enum GraphicsDebugLevel
{
None,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{
[JsonConverter(typeof(JsonStringEnumConverter<GamepadInputId>))]
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
public enum GamepadInputId : byte
{
Unbound,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
{
[JsonConverter(typeof(JsonStringEnumConverter<MotionInputBackendType>))]
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
public enum MotionInputBackendType : byte
{
Invalid,

View File

@@ -16,10 +16,5 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Enable Rumble
/// </summary>
public bool EnableRumble { get; set; }
/// <summary>
/// Enable HD Rumble support
/// </summary
public bool UseHDRumble { get; set; }
}
}

View File

@@ -1,14 +1,15 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid.Controller
{
[JsonConverter(typeof(JsonStringEnumConverter<StickInputId>))]
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
public enum StickInputId : byte
{
Unbound,
Left,
Right,
Count,
}
}

View File

@@ -1,3 +1,4 @@
using Ryujinx.Common.Utilities;
using System;
using System.Text.Json.Serialization;
@@ -5,7 +6,7 @@ namespace Ryujinx.Common.Configuration.Hid
{
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[Flags]
[JsonConverter(typeof(JsonStringEnumConverter<ControllerType>))]
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
public enum ControllerType
{
None,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonStringEnumConverter<InputBackendType>))]
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
public enum InputBackendType
{
Invalid,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
[JsonConverter(typeof(JsonStringEnumConverter<Key>))]
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
public enum Key
{
Unknown,

View File

@@ -1,9 +1,10 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration.Hid
{
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
[JsonConverter(typeof(JsonStringEnumConverter<PlayerIndex>))]
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
public enum PlayerIndex
{
Player1 = 0,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<MemoryManagerMode>))]
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
public enum MemoryManagerMode : byte
{
SoftwarePageTable,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Configuration
{
[JsonConverter(typeof(JsonStringEnumConverter<ScalingFilter>))]
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
public enum ScalingFilter
{
Bilinear,

View File

@@ -12,12 +12,20 @@ namespace Ryujinx.Common.Helper
private static partial nint GetConsoleWindow();
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32", SetLastError = true)]
[LibraryImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool FreeConsole();
private static partial bool ShowWindow(nint hWnd, int nCmdShow);
[SupportedOSPlatform("windows")]
[LibraryImport("user32")]
private static partial nint GetForegroundWindow();
[SupportedOSPlatform("windows")]
[LibraryImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool SetForegroundWindow(nint hWnd);
public static bool SetConsoleWindowStateSupported => OperatingSystem.IsWindows();
public static bool HasConsoleWindow => OperatingSystem.IsWindows() && GetConsoleWindow() != nint.Zero;
public static void SetConsoleWindowState(bool show)
{
@@ -34,31 +42,22 @@ namespace Ryujinx.Common.Helper
[SupportedOSPlatform("windows")]
private static void SetConsoleWindowStateWindows(bool show)
{
if (show)
const int SW_HIDE = 0;
const int SW_SHOW = 5;
nint hWnd = GetConsoleWindow();
if (hWnd == nint.Zero)
{
if (GetConsoleWindow() != nint.Zero)
{
Logger.SetConsoleTargetEnabled(true);
}
Logger.Warning?.Print(LogClass.Application, "Attempted to show/hide console window but console window does not exist");
return;
}
Logger.SetConsoleTargetEnabled(false);
DetachConsole();
}
SetForegroundWindow(hWnd);
[SupportedOSPlatform("windows")]
private static void DetachConsole()
{
if (GetConsoleWindow() == nint.Zero)
{
return;
}
hWnd = GetForegroundWindow();
if (!FreeConsole())
{
Logger.Warning?.Print(LogClass.Application, "Attempted to detach console window but the operation failed");
}
ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);
}
}
}

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonConverter(typeof(JsonStringEnumConverter<LogClass>))]
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
public enum LogClass
{
Application,
@@ -51,7 +52,6 @@ namespace Ryujinx.Common.Logging
ServiceNgct,
ServiceNifm,
ServiceNim,
ServiceNotification,
ServiceNs,
ServiceNsd,
ServiceNtc,

View File

@@ -1,8 +1,9 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Logging
{
[JsonConverter(typeof(JsonStringEnumConverter<LogLevel>))]
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
public enum LogLevel
{
Debug,
@@ -12,7 +13,6 @@ namespace Ryujinx.Common.Logging
Error,
Guest,
AccessLog,
NetLog,
Notice,
Trace,
}

View File

@@ -119,7 +119,6 @@ namespace Ryujinx.Common.Logging
public static Log? Error { get; private set; }
public static Log? Guest { get; private set; }
public static Log? AccessLog { get; private set; }
public static Log? NetLog { get; private set; }
public static Log? Stub { get; private set; }
public static Log? Trace { get; private set; }
public static Log Notice { get; } // Always enabled
@@ -137,7 +136,11 @@ namespace Ryujinx.Common.Logging
_time = Stopwatch.StartNew();
SetConsoleTargetEnabled(true);
// Logger should log to console by default
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
Notice = new Log(LogLevel.Notice);
@@ -170,21 +173,6 @@ namespace Ryujinx.Common.Logging
Updated += target.Log;
}
public static void SetConsoleTargetEnabled(bool enabled)
{
if (enabled)
{
AddTarget(new AsyncLogTargetWrapper(
new ConsoleLogTarget("console"),
1000,
AsyncLogTargetOverflowAction.Discard));
}
else
{
RemoveTarget("console");
}
}
public static void RemoveTarget(string target)
{
ILogTarget logTarget = GetTarget(target);
@@ -248,7 +236,6 @@ namespace Ryujinx.Common.Logging
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : null; break;
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : null; break;
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : null; break;
case LogLevel.NetLog : NetLog = enabled ? new Log(LogLevel.NetLog) : null; break;
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : null; break;
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : null; break;
case LogLevel.Notice : break;

View File

@@ -29,8 +29,8 @@ namespace Ryujinx.Common
public static string GetChangelogUrl(Version currentVersion, Version newVersion) =>
IsCanaryBuild
? $"https://git.ryujinx.app/projects/Ryubing/compare/Canary-{currentVersion}...Canary-{newVersion}"
: $"https://git.ryujinx.app/projects/Ryubing/releases/tag/{newVersion}";
? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}"
: $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}";
}

View File

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

View File

@@ -8,12 +8,12 @@ namespace Ryujinx.Common
public const string AmiiboTagsUrl = "https://raw.githubusercontent.com/Ryubing/Nfc/refs/heads/main/tags.json";
public const string FaqWikiUrl = "https://git.ryujinx.app/projects/Ryubing/wiki/FAQ-%26-Troubleshooting";
public const string FaqWikiUrl = "https://git.ryujinx.app/ryubing/ryujinx/-/wikis/FAQ-&-Troubleshooting";
public const string SetupGuideWikiUrl =
"https://git.ryujinx.app/projects/Ryubing/wiki/Setup-%26-Configuration-Guide";
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Setup-&-Configuration-Guide";
public const string MultiplayerWikiUrl =
"https://git.ryujinx.app/projects/Ryubing/wiki/Multiplayer-(LDN-Local-Wireless)-Guide";
"https://git.ryujinx.app/ryubing/ryujinx/-/wikis/Multiplayer-(LDN-Local-Wireless)-Guide";
}
}

View File

@@ -59,7 +59,6 @@ namespace Ryujinx.Common
//Mario Franchise
"010021d00812a000", // Arcade Archives VS. SUPER MARIO BROS.
"01007fe0221d8000", // Hello, Mario!
"01006d0017f7a000", // Mario & Luigi: Brothership
"010003000e146000", // Mario & Sonic at the Olympic Games Tokyo 2020
"010067300059a000", // Mario + Rabbids: Kingdom Battle
@@ -71,9 +70,6 @@ namespace Ryujinx.Common
"0100bde00862a000", // Mario Tennis Aces
"0100b99019412000", // Mario vs. Donkey Kong
"010049900f546000", // Super Mario 3D All-Stars
"010049900f546001", // Super Mario 3D All-Stars | Super Mario 64
"010049900f546002", // Super Mario 3D All-Stars | Super Mario Sunshine
"010049900f546003", // Super Mario 3D All-Stars | Super Mario Galaxy
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
"010049900F546001", // Super Mario 64
"0100ea80032ea000", // Super Mario Bros. U Deluxe
@@ -111,11 +107,6 @@ namespace Ryujinx.Common
"0100187003a36000", // Pokémon: Let's Go Eevee!
"010003f003a34000", // Pokémon: Let's Go Pikachu!
"0100f43008c44000", // Pokémon Legends: Z-A
"0100554023408000", // Pokémon FireRed Version (EN)
"01006fa0233f8000", // Pokémon FireRed Version (JP)
"0100fd6023430000", // Pokémon LeafGreen Version (DE)
"0100f1e0233fa000", // Pokémon LeafGreen Version (JP)
"01005b7008c52000", // Pokémon Champions
//Splatoon Franchise
"0100f8f0000a2000", // Splatoon 2 (EU)
@@ -125,14 +116,13 @@ namespace Ryujinx.Common
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
//NSO Membership games
"0100d870045b6000", // NES - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100c62011050000", // GB - Nintendo Switch Online
"010012f017576000", // GBA - Nintendo Switch Online
"0100c9a00ece6000", // N64 - Nintendo Switch Online
"0100e0601c632000", // N64 - Nintendo Switch Online 18+
"0100d870045b6000", // NES - Nintendo Switch Online
"0100b3c014bda000", // SEGA Genesis - Nintendo Switch Online
"0100bfc01d976000", // Virtual Boy - Nintendo Switch Online
"01008d300c50c000", // SNES - Nintendo Switch Online
"0100ccf019c8c000", // F-ZERO 99
"0100ad9012510000", // PAC-MAN 99
"010040600c5ce000", // Tetris 99
@@ -151,17 +141,12 @@ namespace Ryujinx.Common
"0100704000B3A000", // Snipperclips
"01006a800016e000", // Super Smash Bros. Ultimate
"0100a9400c9c2000", // Tokyo Mirage Sessions #FE Encore
"0100ca502552a000", // Tomodachi Life: Living the Dream - Welcome Edition
"010051f0207b2000", // Tomodachi Life: Living the Dream
//Bayonetta Franchise
"010076f0049a2000", // Bayonetta
"01007960049a0000", // Bayonetta 2
"01004a4010fea000", // Bayonetta 3
"0100cf5010fec000", // Bayonetta Origins: Cereza and the Lost Demon
// Famicom Detective Club Franchise
"010054601d54c000", // Emio - The Smiling Man: Famicom Detective Series (DEMO)
//Persona Franchise
"0100dcd01525a000", // Persona 3 Portable
@@ -186,9 +171,7 @@ namespace Ryujinx.Common
"0100453019aa8000", // Xenoblade Chronicles: X Definitive Edition
//Misc Games
"01003670066de000", // 36 Fragments of Midnight
"010056e00853a000", // A Hat in Time
"0100c9f00aaee000", // Ascendence
"0100fd1014726000", // Baldurs Gate: Dark Alliance
"01008c2019598000", // Bluey: The Video Game
"010096f00ff22000", // Borderlands 2: Game of the Year Edition
@@ -201,11 +184,8 @@ namespace Ryujinx.Common
"01001b300b9be000", // Diablo III: Eternal Collection
"010027400cdc6000", // Divinity Original 2 - Definitive Edition
"01008c8012920000", // Dying Light Platinum Edition
"0100d11013e6a000", // Eschatos
"01000490067ae000", // Frederic 2: Evil Strikes Back
"01001cc01b2d4000", // Goat Simulator 3
"01003620068ea000", // Hand of Fate 2
"01007ac00e012000", // HEXAGRAVITY
"0100f7e00c70e000", // Hogwarts Legacy
"010013c00e930000", // Hollow Knight: Silksong
"010085500130a000", // Lego City: Undercover
@@ -213,25 +193,15 @@ namespace Ryujinx.Common
"0100d71004694000", // Minecraft
"01007430037f6000", // Monopoly
"0100853015e86000", // No Man's Sky
"0100f85014ed0000", // No More Heroes
"0100463014ed4000", // No More Heroes 2
"0100f7d00a1bc000", // NO THING
"0100e570094e8000", // Owlboy
"01007bb017812000", // Portal
"0100abd01785c000", // Portal 2
"01009f100bc52000", // Psikyo Collection 1
"01009d400c4a8000", // Psikyo Collection 2
"01008e200c5c2000", // Muse Dash
"01005ff002e2a000", // Rayman Legends
"01007820196a6000", // Red Dead Redemption
"01007a800d520000", // REFUNCT
"0100e8300a67a000", // Risk
"01002f7013224000", // Rune Factory 5
"01008d100d43e000", // Saints Row IV
"0100de600beee000", // Saints Row: The Third - The Full Package
"01001180021fa000", // Shovel Knight: Specter of Torment
"010079f00671c000", // Sparkle 2: Evo
"010077b00e046000", // Spyro: Reignited Trilogy
"0100e1D01eb2e000", // Squeakross: Home Squeak Home
"0100e65002bb8000", // Stardew Valley
"0100d7a01b7a2000", // Star Wars: Bounty Hunter

View File

@@ -127,7 +127,7 @@ namespace Ryujinx.Common
public static string[] GetAllAvailableResources(string path, string ext = "")
{
return ResolveManifestPath(path).Item1.GetManifestResourceNames()
.Where(r => r.StartsWith(path.Replace('/', '.')) && r.EndsWith(ext))
.Where(r => r.EndsWith(ext))
.ToArray();
}

View File

@@ -22,11 +22,10 @@ namespace Ryujinx.Common.Utilities
}
// "dumpable" attribute of the calling process
private const int PR_GET_DUMPABLE = 3;
private const int PR_SET_DUMPABLE = 4;
[LibraryImport("libc", SetLastError = true)]
private static partial int prctl(int option, int arg2);
[DllImport("libc", SetLastError = true)]
private static extern int prctl(int option, int arg2);
public static void SetCoreDumpable(bool dumpable)
{
@@ -37,13 +36,5 @@ namespace Ryujinx.Common.Utilities
Debug.Assert(result == 0);
}
}
// Use the below line to display dumpable status in the console:
// Console.WriteLine($"{OsUtils.IsCoreDumpable()}");
public static bool IsCoreDumpable()
{
int result = prctl(PR_GET_DUMPABLE, 0);
return result == 1;
}
}
}

View File

@@ -0,0 +1,37 @@
#nullable enable
using Ryujinx.Common.Logging;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Ryujinx.Common.Utilities
{
/// <summary>
/// Specifies that value of <see cref="TEnum"/> will be serialized as string in JSONs
/// </summary>
/// <remarks>
/// Trimming friendly alternative to <see cref="JsonStringEnumConverter"/>.
/// Get rid of this converter if dotnet supports similar functionality out of the box.
/// </remarks>
/// <typeparam name="TEnum">Type of enum to serialize</typeparam>
public sealed class TypedStringEnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : struct, Enum
{
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string? enumValue = reader.GetString();
if (Enum.TryParse(enumValue, out TEnum value))
{
return value;
}
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
return default;
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
}

View File

@@ -30,9 +30,9 @@ namespace ARMeilleure.Common
/// <summary>
/// Base address for the page.
/// </summary>
public readonly nint Address;
public readonly IntPtr Address;
public AddressTablePage(bool isSparse, nint address)
public AddressTablePage(bool isSparse, IntPtr address)
{
IsSparse = isSparse;
Address = address;
@@ -47,20 +47,20 @@ namespace ARMeilleure.Common
public readonly SparseMemoryBlock Block;
private readonly TrackingEventDelegate _trackingEvent;
public TableSparseBlock(ulong size, Action<nint> ensureMapped, PageInitDelegate pageInit)
public TableSparseBlock(ulong size, Action<IntPtr> ensureMapped, PageInitDelegate pageInit)
{
SparseMemoryBlock block = new(size, pageInit, null);
_trackingEvent = (address, size, write) =>
{
ulong pointer = (ulong)block.Block.Pointer + address;
ensureMapped((nint)pointer);
ensureMapped((IntPtr)pointer);
return pointer;
};
bool added = NativeSignalHandler.AddTrackedRegion(
(nuint)block.Block.Pointer,
(nuint)(block.Block.Pointer + (nint)block.Block.Size),
(nuint)(block.Block.Pointer + (IntPtr)block.Block.Size),
Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)
@@ -116,7 +116,7 @@ namespace ARMeilleure.Common
}
/// <inheritdoc/>
public nint Base
public IntPtr Base
{
get
{
@@ -124,7 +124,7 @@ namespace ARMeilleure.Common
lock (_pages)
{
return (nint)GetRootPage();
return (IntPtr)GetRootPage();
}
}
}
@@ -240,7 +240,7 @@ namespace ARMeilleure.Common
long index = Levels[^1].GetValue(address);
EnsureMapped((nint)(page + index));
EnsureMapped((IntPtr)(page + index));
return ref page[index];
}
@@ -284,7 +284,7 @@ namespace ARMeilleure.Common
/// Ensure the given pointer is mapped in any overlapping sparse reservations.
/// </summary>
/// <param name="ptr">Pointer to be mapped</param>
private void EnsureMapped(nint ptr)
private void EnsureMapped(IntPtr ptr)
{
if (Sparse)
{
@@ -299,7 +299,7 @@ namespace ARMeilleure.Common
{
SparseMemoryBlock sparse = reserved.Block;
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (nint)sparse.Block.Size)
if (ptr >= sparse.Block.Pointer && ptr < sparse.Block.Pointer + (IntPtr)sparse.Block.Size)
{
sparse.EnsureMapped((ulong)(ptr - sparse.Block.Pointer));
@@ -319,15 +319,15 @@ namespace ARMeilleure.Common
/// </summary>
/// <param name="level">Level to get the fill value for</param>
/// <returns>The fill value</returns>
private nint GetFillValue(int level)
private IntPtr GetFillValue(int level)
{
if (_fillBottomLevel != null && level == Levels.Length - 2)
{
return (nint)_fillBottomLevelPtr;
return (IntPtr)_fillBottomLevelPtr;
}
else
{
return nint.Zero;
return IntPtr.Zero;
}
}
@@ -379,7 +379,7 @@ namespace ARMeilleure.Common
/// <param name="fill">Fill value</param>
/// <param name="leaf"><see langword="true"/> if leaf; otherwise <see langword="false"/></param>
/// <returns>Allocated block</returns>
private nint Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
private IntPtr Allocate<T>(int length, T fill, bool leaf) where T : unmanaged
{
int size = sizeof(T) * length;
@@ -405,7 +405,7 @@ namespace ARMeilleure.Common
}
}
page = new AddressTablePage(true, block.Block.Pointer + (nint)_sparseReservedOffset);
page = new AddressTablePage(true, block.Block.Pointer + (IntPtr)_sparseReservedOffset);
_sparseReservedOffset += (ulong)size;
@@ -413,7 +413,7 @@ namespace ARMeilleure.Common
}
else
{
nint address = (nint)NativeAllocator.Instance.Allocate((uint)size);
IntPtr address = (IntPtr)NativeAllocator.Instance.Allocate((uint)size);
page = new AddressTablePage(false, address);
Span<T> span = new((void*)page.Address, length);

View File

@@ -17,8 +17,7 @@ namespace Ryujinx.Cpu.LightningJit.Cache
private static readonly int _pageMask = _pageSize - 1;
private const int CodeAlignment = 4; // Bytes.
// TODO: JIT Cache size should be application dependent, not global.
private const int CacheSize = 1024 * (1024 * 1024); // Megabytes * Size of Megabytes (since its in bytes).
private const int CacheSize = 256 * 1024 * 1024;
private static JitCacheInvalidation _jitCacheInvalidator;
@@ -34,14 +33,6 @@ namespace Ryujinx.Cpu.LightningJit.Cache
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial nint FlushInstructionCache(nint hProcess, nint lpAddress, nuint dwSize);
[SupportedOSPlatform("macos")]
[LibraryImport("libSystem.dylib", EntryPoint = "sys_icache_invalidate")]
internal static partial void SysICacheInvalidate(nint start, nuint len);
[SupportedOSPlatform("linux")]
[LibraryImport("libgcc_s.so.1", EntryPoint = "__clear_cache")]
internal static partial void ClearCache(nint begin, nint end);
public static void Initialize(IJitMemoryAllocator allocator)
{

View File

@@ -42,7 +42,6 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64;
public readonly bool SupportsShaderNonUniformIndexing;
public readonly bool SupportsTextureGatherOffsets;
public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsVertexStoreAndAtomics;
@@ -111,7 +110,6 @@ namespace Ryujinx.Graphics.GAL
bool supportsShaderBallot,
bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64,
bool supportsShaderNonUniformIndexing,
bool supportsTextureGatherOffsets,
bool supportsTextureShadowLod,
bool supportsVertexStoreAndAtomics,
@@ -174,7 +172,6 @@ namespace Ryujinx.Graphics.GAL
SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64;
SupportsShaderNonUniformIndexing = supportsShaderNonUniformIndexing;
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;

View File

@@ -49,9 +49,12 @@ namespace Ryujinx.Graphics.Gpu.Image
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048;
private const ulong GiB = 1024 * 1024 * 1024;
private ulong MaxTextureSizeCapacity = 2 * GiB;
private ulong MaxTextureSizeCapacity = 4UL * GiB;
private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
private const ulong DefaultTextureSizeCapacity = 1 * GiB;
private const ulong TextureSizeCapacity6GiB = 4 * GiB;
private const ulong TextureSizeCapacity8GiB = 6 * GiB;
private const ulong TextureSizeCapacity12GiB = 12 * GiB;
private const float MemoryScaleFactor = 0.50f;
private ulong _maxCacheMemoryUsage = DefaultTextureSizeCapacity;
@@ -70,22 +73,31 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <remarks>
/// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
///
/// Reads the current Device total CPU Memory, to determine the maximum amount of VRAM available. Capped to 50% of Current GPU Memory.
/// Reads the current Device total CPU Memory, to determine the maximum amount of Vram available. Capped to 50% of Current GPU Memory.
/// </remarks>
/// <param name="context">The GPU context that the cache belongs to</param>
/// <param name="cpuMemorySize">The amount of physical CPU Memory available on the device.</param>
/// <param name="cpuMemorySize">The amount of physical CPU Memory Avaiable on the device.</param>
public void Initialize(GpuContext context, ulong cpuMemorySize)
{
ulong cpuMemorySizeGiB = cpuMemorySize / GiB;
ulong MaximumGpuMemoryGiB = context.Capabilities.MaximumGpuMemory / GiB;
ulong TextureSizeCapacity = cpuMemorySize - (2 * GiB);
MaxTextureSizeCapacity =
context.Capabilities.MaximumGpuMemory == 0 || cpuMemorySizeGiB < 6 && MaximumGpuMemoryGiB < 6
? DefaultTextureSizeCapacity
: cpuMemorySizeGiB < 12
? TextureSizeCapacity
: cpuMemorySize;
if (cpuMemorySizeGiB < 6 || context.Capabilities.MaximumGpuMemory == 0)
{
_maxCacheMemoryUsage = DefaultTextureSizeCapacity;
return;
}
else if (cpuMemorySizeGiB == 6)
{
MaxTextureSizeCapacity = TextureSizeCapacity6GiB;
}
else if (cpuMemorySizeGiB == 8)
{
MaxTextureSizeCapacity = TextureSizeCapacity8GiB;
}
else
{
MaxTextureSizeCapacity = TextureSizeCapacity12GiB;
}
ulong cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);

View File

@@ -658,7 +658,7 @@ namespace Ryujinx.Graphics.Gpu.Image
bool canImport = Storage.Info.IsLinear && Storage.Info.Stride >= Storage.Info.Width * Storage.Info.FormatInfo.BytesPerPixel;
nint hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
IntPtr hostPointer = canImport ? _physicalMemory.GetHostPointer(Storage.Range) : 0;
if (hostPointer != 0 && _context.Renderer.PrepareHostMapping(hostPointer, Storage.Size))
{

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
private const uint CodeGenVersion = 7354;
private const uint CodeGenVersion = 7353;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";

View File

@@ -231,8 +231,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
public bool QueryHostSupportsShaderNonUniformIndexing() => _context.Capabilities.SupportsShaderNonUniformIndexing;
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;

View File

@@ -551,7 +551,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
level,
x,
width,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -579,7 +579,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
layer,
width,
1,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -609,7 +609,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
y,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -643,7 +643,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
width,
height,
1,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -675,7 +675,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
y,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -744,7 +744,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
level,
0,
width,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -773,7 +773,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
0,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -807,7 +807,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
width,
height,
depth,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize,
data);
}
@@ -843,7 +843,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
0,
width,
height,
(InternalFormat) format.PixelFormat,
format.PixelFormat,
mipSize / 6,
data + faceOffset);
}

View File

@@ -184,7 +184,6 @@ namespace Ryujinx.Graphics.OpenGL
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
supportsShaderFloat64: true,
supportsShaderNonUniformIndexing: false,
supportsTextureGatherOffsets: true,
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
supportsVertexStoreAndAtomics: true,

View File

@@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.OpenGL
public void Map(BufferHandle handle, int size)
{
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle);
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
nint ptr = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, size, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
_maps[handle] = ptr;
}
@@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.BindBuffer(BufferTarget.CopyWriteBuffer, _copyBufferHandle);
GL.BufferStorage(BufferTarget.CopyWriteBuffer, requiredSize, nint.Zero, BufferStorageFlags.MapReadBit | BufferStorageFlags.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.CopyWriteBuffer, nint.Zero, requiredSize, BufferAccessMask.MapReadBit | BufferAccessMask.MapPersistentBit);
}
}

View File

@@ -924,8 +924,8 @@ namespace Ryujinx.Graphics.OpenGL
GL.Disable(EnableCap.CullFace);
return;
}
GL.CullFace((TriangleFace) face.Convert());
GL.CullFace(face.Convert());
GL.Enable(EnableCap.CullFace);
}
@@ -1085,12 +1085,12 @@ namespace Ryujinx.Graphics.OpenGL
{
if (frontMode == backMode)
{
GL.PolygonMode((TriangleFace) MaterialFace.FrontAndBack, frontMode.Convert());
GL.PolygonMode(MaterialFace.FrontAndBack, frontMode.Convert());
}
else
{
GL.PolygonMode((TriangleFace) MaterialFace.Front, frontMode.Convert());
GL.PolygonMode((TriangleFace) MaterialFace.Back, backMode.Convert());
GL.PolygonMode(MaterialFace.Front, frontMode.Convert());
GL.PolygonMode(MaterialFace.Back, backMode.Convert());
}
}

View File

@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.CompileShader(shaderHandle);
break;
case TargetLanguage.Spirv:
GL.ShaderBinary(1, ref shaderHandle, ShaderBinaryFormat.ShaderBinaryFormatSpirV, shader.BinaryCode, shader.BinaryCode.Length);
GL.ShaderBinary(1, ref shaderHandle, (BinaryFormat)All.ShaderBinaryFormatSpirVArb, shader.BinaryCode, shader.BinaryCode.Length);
GL.SpecializeShader(shaderHandle, "main", 0, (int[])null, (int[])null);
break;
}

View File

@@ -32,7 +32,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
GL.BufferStorage(BufferTarget.QueryBuffer, sizeof(long), (nint)(&defaultValue), BufferStorageFlags.MapReadBit | BufferStorageFlags.MapWriteBit | BufferStorageFlags.MapPersistentBit);
}
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), MapBufferAccessMask.MapReadBit | MapBufferAccessMask.MapWriteBit | MapBufferAccessMask.MapPersistentBit);
_bufferMap = GL.MapBufferRange(BufferTarget.QueryBuffer, nint.Zero, sizeof(long), BufferAccessMask.MapReadBit | BufferAccessMask.MapWriteBit | BufferAccessMask.MapPersistentBit);
}
public void Reset()

View File

@@ -1,12 +0,0 @@
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
public readonly record struct Capture(int Index, string FileName, DateTime Timestamp)
{
public void SetComments(string comments)
{
RenderDoc.SetCaptureFileComments(FileName, comments);
}
}
}

View File

@@ -1,100 +0,0 @@
// ReSharper disable UnusedMember.Global
namespace Ryujinx.Graphics.RenderDocApi
{
public enum CaptureOption
{
/// <summary>
/// specifies whether the application is allowed to enable vsync. Default is on.
/// </summary>
AllowVsync = 0,
/// <summary>
/// specifies whether the application is allowed to enter exclusive fullscreen. Default is on.
/// </summary>
AllowFullscreen = 1,
/// <summary>
/// specifies whether (where possible) API-specific debugging is enabled. Default is off.
/// </summary>
ApiValidation = 2,
/// <summary>
/// specifies whether each API call should save a callstack. Default is off.
/// </summary>
CaptureCallstacks = 3,
/// <summary>
/// specifies whether, if <see cref="CaptureCallstacks"/> is enabled, callstacks are only saved on actions. Default is off.
/// </summary>
CaptureCallstacksOnlyDraws = 4,
/// <summary>
/// specifies a delay in seconds after launching a process to pause, to allow debuggers to attach. <br/>
/// This will only apply to child processes since the delay happens at process startup. Default is 0.
/// </summary>
DelayForDebugger = 5,
/// <summary>
/// specifies whether any mapped memory updates should be bounds-checked for overruns,
/// and uninitialised buffers are initialized to <code>0xDDDDDDDD</code> to catch use of uninitialised data.
/// Only supported on D3D11 and OpenGL. Default is off.
/// </summary>
/// <remarks>
/// This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
/// not do the same kind of interception &amp; checking, and undefined contents are really undefined.
/// </remarks>
VerifyBufferAccess = 6,
/// <summary>
/// Hooks any system API calls that create child processes, and injects
/// RenderDoc into them recursively with the same options.
/// </summary>
HookIntoChildren = 7,
/// <summary>
/// specifies whether all live resources at the time of capture should be included in the capture,
/// even if they are not referenced by the frame. Default is off.
/// </summary>
RefAllSources = 8,
/// <summary>
/// By default, RenderDoc skips saving initial states for resources where the
/// previous contents don't appear to be used, assuming that writes before
/// reads indicate previous contents aren't used.
/// </summary>
/// <remarks>
/// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
/// getting it will be ignored, to allow compatibility with older versions.
/// In v1.1 the option acts as if it's always enabled.
/// </remarks>
SaveAllInitials = 9,
/// <summary>
/// In APIs that allow for the recording of command lists to be replayed later,
/// RenderDoc may choose to not capture command lists before a frame capture is
/// triggered, to reduce overheads. This means any command lists recorded once
/// and replayed many times will not be available and may cause a failure to
/// capture.
/// </summary>
/// <remarks>
/// NOTE: This is only true for APIs where multithreading is difficult or
/// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
/// and always capture all command lists since the API is heavily oriented
/// around it and the overheads have been reduced by API design.
/// </remarks>
CaptureAllCmdLists = 10,
/// <summary>
/// Mute API debugging output when the <see cref="ApiValidation"/> option is enabled.
/// </summary>
DebugOutputMute = 11,
/// <summary>
/// Allow vendor extensions to be used even when they may be
/// incompatible with RenderDoc and cause corrupted replays or crashes.
/// </summary>
AllowUnsupportedVendorExtensions = 12,
/// <summary>
/// Define a soft memory limit which some APIs may aim to keep overhead under where
/// possible. Anything above this limit will where possible be saved directly to disk during
/// capture.<br/>
/// This will cause increased disk space use (which may cause a capture to fail if disk space is
/// exhausted) as well as slower capture times.
/// <br/><br/>
/// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
/// limit.
/// <br/><br/>
/// Units are in MBs, suggested values would range from 200MB to 1000MB.
/// </summary>
SoftMemoryLimit = 13,
}
}

View File

@@ -1,83 +0,0 @@
// ReSharper disable UnusedMember.Global
namespace Ryujinx.Graphics.RenderDocApi
{
public enum InputButton
{
// '0' - '9' matches ASCII values
Key0 = 0x30,
Key1 = 0x31,
Key2 = 0x32,
Key3 = 0x33,
Key4 = 0x34,
Key5 = 0x35,
Key6 = 0x36,
Key7 = 0x37,
Key8 = 0x38,
Key9 = 0x39,
// 'A' - 'Z' matches ASCII values
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
F = 0x46,
G = 0x47,
H = 0x48,
I = 0x49,
J = 0x4A,
K = 0x4B,
L = 0x4C,
M = 0x4D,
N = 0x4E,
O = 0x4F,
P = 0x50,
Q = 0x51,
R = 0x52,
S = 0x53,
T = 0x54,
U = 0x55,
V = 0x56,
W = 0x57,
X = 0x58,
Y = 0x59,
Z = 0x5A,
// leave the rest of the ASCII range free
// in case we want to use it later
NonPrintable = 0x100,
Divide,
Multiply,
Subtract,
Plus,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
Home,
End,
Insert,
Delete,
PageUp,
PageDn,
Backspace,
Tab,
PrtScrn,
Pause,
Max,
}
}

View File

@@ -1,39 +0,0 @@
// ReSharper disable UnusedMember.Global
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
[Flags]
public enum OverlayBits
{
/// <summary>
/// This single bit controls whether the overlay is enabled or disabled globally
/// </summary>
Enabled = 1 << 0,
/// <summary>
/// Show the average framerate over several seconds as well as min/max
/// </summary>
FrameRate = 1 << 1,
/// <summary>
/// Show the current frame number
/// </summary>
FrameNumber = 1 << 2,
/// <summary>
/// Show a list of recent captures, and how many captures have been made
/// </summary>
CaptureList = 1 << 3,
/// <summary>
/// Default values for the overlay mask
/// </summary>
Default = Enabled | FrameRate | FrameNumber | CaptureList,
/// <summary>
/// Enable all bits
/// </summary>
All = ~0,
/// <summary>
/// Disable all bits
/// </summary>
None = 0
}
}

View File

@@ -1,5 +0,0 @@
# Ryujinx.Graphics.RenderDocApi
This is a C# binding for RenderDoc's application API.
This is a source-inclusion of https://github.com/utkumaden/RenderdocSharp.
I didn't use the NuGet package as I had a few minor changes I wanted to make, and I want to learn from it as well via hands-on experience.

View File

@@ -1,639 +0,0 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace Ryujinx.Graphics.RenderDocApi
{
public static unsafe partial class RenderDoc
{
/// <summary>
/// True if the API is available.
/// </summary>
public static bool IsAvailable => Api != null;
/// <summary>
/// Set the minimum version of the API you require.
/// </summary>
/// <remarks>Set this before you do anything else with the RenderDoc API, including <see cref="IsAvailable"/>.</remarks>
public static RenderDocVersion MinimumRequired { get; set; } = RenderDocVersion.Version_1_0_0;
/// <summary>
/// Set to true to assert versions.
/// </summary>
public static bool AssertVersionEnabled { get; set; } = true;
/// <summary>
/// Version of the API available.
/// </summary>
[MemberNotNullWhen(true, nameof(IsAvailable))]
public static Version? Version
{
get
{
if (!IsAvailable)
return null;
int major, minor, build;
Api->GetApiVersion(&major, &minor, &build);
return new Version(major, minor, build);
}
}
/// <summary>
/// The current mask which determines what sections of the overlay render on each window.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static OverlayBits OverlayBits
{
get => Api->GetOverlayBits();
set
{
Api->MaskOverlayBits(~value, value);
}
}
/// <summary>
/// The template for new captures.<br/>
/// The template can either be a relative or absolute path, which determines where captures will be saved and how they will be named.
/// If the path template is 'my_captures/example', then captures saved will be e.g.
/// 'my_captures/example_frame123.rdc' and 'my_captures/example_frame456.rdc'.<br/>
/// Relative paths will be saved relative to the processs current working directory.<br/>
/// </summary>
/// <remarks>The default template is in a folder controlled by the UI - initially the system temporary folder, and the filename is the executables filename.</remarks>
[RenderDocApiVersion(1, 0)]
public static string CaptureFilePathTemplate
{
get
{
byte* ptr = Api->GetCaptureFilePathTemplate();
return Marshal.PtrToStringUTF8((nint)ptr)!;
}
set
{
fixed (byte* ptr = value.ToNullTerminatedByteArray())
{
Api->SetCaptureFilePathTemplate(ptr);
}
}
}
/// <summary>
/// The amount of frame captures that have been made.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static int CaptureCount => Api->GetNumCaptures();
/// <summary>
/// Checks if the RenderDoc UI is currently connected to this process.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsTargetControlConnected => Api is not null && Api->IsTargetControlConnected() != 0;
/// <summary>
/// Checks if the current frame is capturing.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static bool IsFrameCapturing => Api is not null && Api->IsFrameCapturing() != 0;
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="integer">the unsigned integer value to set for the option.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, uint integer)
{
return Api is not null && Api->SetCaptureOptionU32(option, integer) != 0;
}
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="boolean">the value to set for the option, converted to a 0 or 1 before setting.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, bool boolean)
=> SetCaptureOption(option, boolean ? 1 : 0);
/// <summary>
/// Set one of the options for tweaking some behaviors of capturing.
/// </summary>
/// <param name="option">specifies which capture option should be set.</param>
/// <param name="single">the floating point value to set for the option.</param>
/// <remarks>Note that each option only takes effect from after it is set - so it is advised to set these options as early as possible, ideally before any graphics API has been initialized.</remarks>
/// <returns>
/// true, if the <paramref name="option"/> is valid, and the value set on the option is within valid ranges.<br/>
/// false, if the option is not a <see cref="CaptureOption"/>, or the value is not valid for the option.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool SetCaptureOption(CaptureOption option, float single)
{
return Api is not null && Api->SetCaptureOptionF32(option, single) != 0;
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <param name="integer">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, <see cref="int.MaxValue"/>.</param>
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out uint integer)
{
integer = Api->GetCaptureOptionU32(option);
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>, writing it to an out parameter.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <param name="single">the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum. Otherwise, -<see cref="float.MaxValue"/>.</param>
[RenderDocApiVersion(1, 0)]
public static void GetCaptureOption(CaptureOption option, out float single)
{
single = Api->GetCaptureOptionF32(option);
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>,
/// converted to a boolean.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, converted to bool, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns null.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static bool? GetCaptureOptionBool(CaptureOption option)
{
if (Api is null) return false;
uint returnVal = GetCaptureOptionU32(option);
if (returnVal == uint.MaxValue)
return null;
return returnVal is not 0;
}
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns <see cref="int.MaxValue"/>.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static uint GetCaptureOptionU32(CaptureOption option) => Api->GetCaptureOptionU32(option);
/// <summary>
/// Gets the current value of one of the different options in <see cref="CaptureOption"/>.
/// </summary>
/// <param name="option">specifies which capture option should be retrieved.</param>
/// <returns>
/// the value of the capture option, if the option is a valid <see cref="CaptureOption"/> enum.
/// Otherwise, returns -<see cref="float.MaxValue"/>.
/// </returns>
[RenderDocApiVersion(1, 0)]
public static float GetCaptureOptionF32(CaptureOption option) => Api->GetCaptureOptionF32(option);
/// <summary>
/// Changes the key bindings in-application for changing the focussed window.
/// </summary>
/// <param name="buttons">lists the keys to bind.</param>
[RenderDocApiVersion(1, 0)]
public static void SetFocusToggleKeys(ReadOnlySpan<InputButton> buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetFocusToggleKeys(ptr, buttons.Length);
}
}
/// <summary>
/// Changes the key bindings in-application for triggering a capture on the current window.
/// </summary>
/// <param name="buttons">lists the keys to bind.</param>
[RenderDocApiVersion(1, 0)]
public static void SetCaptureKeys(ReadOnlySpan<InputButton> buttons)
{
if (Api is null) return;
fixed (InputButton* ptr = buttons)
{
Api->SetCaptureKeys(ptr, buttons.Length);
}
}
/// <summary>
/// Attempts to remove RenderDoc and its hooks from the target process.<br/>
/// It must be called as early as possible in the process, and will have undefined results
/// if any graphics API functions have been called.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void RemoveHooks()
{
if (Api is null) return;
Api->RemoveHooks();
}
/// <summary>
/// Remove RenderDocs crash handler from the target process.<br/>
/// If you have your own crash handler that you want to handle any exceptions,
/// RenderDocs handler could interfere; so it can be disabled.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void UnloadCrashHandler()
{
if (Api is null) return;
Api->UnloadCrashHandler();
}
/// <summary>
/// Trigger a capture as if the user had pressed one of the capture hotkeys.<br/>
/// The capture will be taken from the next frame presented to whichever window is considered current.
/// </summary>
[RenderDocApiVersion(1, 0)]
public static void TriggerCapture()
{
if (Api is null) return;
Api->TriggerCapture();
}
/// <summary>
/// Gets the details of all frame capture in the current session.
/// This simply calls <see cref="GetCapture"/> for each index available as specified by <see cref="CaptureCount"/>.
/// </summary>
/// <returns>An immutable array of structs representing RenderDoc Captures.</returns>
public static ImmutableArray<Capture> GetCaptures()
{
if (Api is null) return [];
int captureCount = CaptureCount;
if (captureCount is 0) return [];
ImmutableArray<Capture>.Builder captures = ImmutableArray.CreateBuilder<Capture>(captureCount);
for (int captureIndex = 0; captureIndex < captureCount; captureIndex++)
{
if (GetCapture(captureIndex) is { } capture)
captures.Add(capture);
}
return captures.DrainToImmutable();
}
/// <summary>
/// Gets the details of a particular frame capture, as specified by an index from 0 to <see cref="CaptureCount"/> - 1.
/// </summary>
/// <param name="index">specifies which capture to return the details of. Must be less than the value returned by <see cref="CaptureCount"/>.</param>
/// <returns>A struct representing a RenderDoc Capture.</returns>
[RenderDocApiVersion(1, 0)]
public static Capture? GetCapture(int index)
{
if (Api is null) return null;
int length = 0;
if (Api->GetCapture(index, null, &length, null) == 0)
{
return null;
}
Span<byte> bytes = stackalloc byte[length + 1];
long timestamp;
fixed (byte* ptr = bytes)
Api->GetCapture(index, ptr, &length, &timestamp);
string fileName = Encoding.UTF8.GetString(bytes[length..]);
return new Capture(index, fileName, DateTimeOffset.FromUnixTimeSeconds(timestamp).DateTime);
}
/// <summary>
/// Determine the closest matching replay UI executable for the current RenderDoc module, and launch it.
/// </summary>
/// <param name="connectTargetControl">if the UI should immediately connect to the application.</param>
/// <param name="commandLine">string to be appended to the command line, e.g. a capture filename. If this parameter is null, the command line will be unmodified.</param>
/// <returns>true if the UI was successfully launched; false otherwise.</returns>
[RenderDocApiVersion(1, 0)]
public static bool LaunchReplayUI(bool connectTargetControl, string? commandLine = null)
{
if (Api is null) return false;
if (commandLine == null)
{
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, null) != 0;
}
fixed (byte* ptr = commandLine.ToNullTerminatedByteArray())
{
return Api->LaunchReplayUI(connectTargetControl ? 1u : 0u, ptr) != 0;
}
}
/// <summary>
/// Explicitly sets which window is considered active.<br/>
/// The active window is the one that will be captured when the keybind to trigger a capture is pressed.
/// </summary>
/// <param name="hDevice">a handle to the API device object that will be set active. Must be valid.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. Must be valid.</param>
[RenderDocApiVersion(1, 0)]
public static void SetActiveWindow(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->SetActiveWindow((void*)hDevice, (void*)hWindow);
}
/// <summary>
/// Immediately begin a capture for the specified device/window combination.
/// </summary>
/// <param name="hDevice">a handle to the API device object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
[RenderDocApiVersion(1, 0)]
public static void StartFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return;
Api->StartFrameCapture((void*)hDevice, (void*)hWindow);
}
/// <summary>
/// Immediately end an active capture for the specified device/window combination.
/// </summary>
/// <param name="hDevice">a handle to the API device object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <returns>true if the capture succeeded; false otherwise.</returns>
[RenderDocApiVersion(1, 0)]
public static bool EndFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return false;
return Api->EndFrameCapture((void*)hDevice, (void*)hWindow) != 0;
}
/// <summary>
/// Trigger multiple sequential frame captures as if the user had pressed one of the capture hotkeys before each frame.<br/>
/// The captures will be taken from the next frames presented to whichever window is considered current.<br/>
/// Each capture will be taken independently and saved to a separate file, with no reference to the other frames.
/// </summary>
/// <param name="numFrames">the number of frames to capture.</param>
/// <remarks>Requires RenderDoc API version 1.1</remarks>
[RenderDocApiVersion(1, 1)]
public static void TriggerMultiFrameCapture(uint numFrames)
{
if (Api is null) return;
AssertAtLeast(1, 1);
Api->TriggerMultiFrameCapture(numFrames);
}
/// <summary>
/// Adds an arbitrary comments field to the most recent capture,
/// which will then be displayed in the UI to anyone opening the capture.
/// <br/><br/>
/// This is equivalent to calling <see cref="SetCaptureFileComments"/> with a null first (fileName) parameter.
/// </summary>
/// <param name="comments">the comments to set in the capture file.</param>
/// <remarks>Requires RenderDoc API version 1.2</remarks>
public static void SetMostRecentCaptureFileComments(string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
}
/// <summary>
/// Adds an arbitrary comments field to an existing capture on disk,
/// which will then be displayed in the UI to anyone opening the capture.
/// </summary>
/// <param name="fileName">the path to the capture file to set comments in. If this path is null or an empty string, the most recent capture file that has been created will be used.</param>
/// <param name="comments">the comments to set in the capture file.</param>
/// <remarks>Requires RenderDoc API version 1.2</remarks>
[RenderDocApiVersion(1, 2)]
public static void SetCaptureFileComments(string? fileName, string comments)
{
if (Api is null) return;
AssertAtLeast(1, 2);
byte[] commentBytes = comments.ToNullTerminatedByteArray();
fixed (byte* pcomment = commentBytes)
{
if (fileName is null)
{
Api->SetCaptureFileComments((byte*)nint.Zero, pcomment);
}
else
{
byte[] fileBytes = fileName.ToNullTerminatedByteArray();
fixed (byte* pfile = fileBytes)
{
Api->SetCaptureFileComments(pfile, pcomment);
}
}
}
}
/// <summary>
/// Similar to <see cref="EndFrameCapture"/>, but the capture contents will be discarded immediately, and not processed and written to disk.<br/>
/// This will be more efficient than <see cref="EndFrameCapture"/> if the frame capture is not needed.
/// </summary>
/// <param name="hDevice">a handle to the API device object that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <param name="hWindow">a handle to the platform window handle that will be set active. May be <see cref="nint.Zero"/> to wildcard match.</param>
/// <returns>true if the capture was discarded; false if there was an error or no capture was in progress.</returns>
/// <remarks>Requires RenderDoc API version 1.4</remarks>
[RenderDocApiVersion(1, 4)]
public static bool DiscardFrameCapture(nint hDevice, nint hWindow)
{
if (Api is null) return false;
AssertAtLeast(1, 4);
return Api->DiscardFrameCapture((void*)hDevice, (void*)hWindow) != 0;
}
/// <summary>
/// Requests that the currently connected replay UI raise its window to the top.<br/>
/// This is only possible if an instance of the replay UI is currently connected, otherwise this method does nothing.<br/>
/// This can be used in conjunction with <see cref="IsTargetControlConnected"/> and <see cref="LaunchReplayUI"/>,<br/> to intelligently handle showing the UI after making a capture.<br/><br/>
/// Given OS differences, it is not guaranteed that the UI will be successfully raised even if the request is passed on.<br/>
/// On some systems it may only be highlighted or otherwise indicated to the user.
/// </summary>
/// <returns>true if the request was passed onto the UI successfully; false if there is no UI connected or some other error occurred.</returns>
/// <remarks>Requires RenderDoc API version 1.5</remarks>
[RenderDocApiVersion(1, 5)]
public static bool ShowReplayUI()
{
if (Api is null) return false;
AssertAtLeast(1, 5);
return Api->ShowReplayUI() != 0;
}
/// <summary>
/// Sets a given title for the currently in-progress capture, which will be displayed in the UI.<br/>
/// This can be used either with a user-defined capture using a manual start and end,
/// or an automatic capture triggered by <see cref="TriggerCapture"/> or a keypress.<br/>
/// If multiple captures are ongoing at once, the title will be applied to the first capture to end only.<br/>
/// Any subsequent captures will not get any title unless the function is called again.
/// This function can only be called while a capture is in-progress,
/// after <see cref="StartFrameCapture"/> and before <see cref="EndFrameCapture"/>.<br/>
/// If it is called elsewhere it will have no effect.
/// If it is called multiple times within a capture, only the last title will have any effect.
/// </summary>
/// <param name="title">The title to set for the in-progress capture.</param>
/// <remarks>Requires RenderDoc API version 1.6</remarks>
[RenderDocApiVersion(1, 6)]
public static void SetCaptureTitle(string title)
{
if (Api is null) return;
AssertAtLeast(1, 6);
fixed (byte* ptr = title.ToNullTerminatedByteArray())
Api->SetCaptureTitle(ptr);
}
#region Dynamic Library loading
/// <summary>
/// Reload the internal RenderDoc API structure. Useful for manually refreshing <see cref="Api"/> while using process injection.
/// </summary>
/// <param name="ignoreAlreadyLoaded">Ignores the existing API function structure and overwrites it with a re-request.</param>
/// <param name="requiredVersion">The version of the RenderDoc API required by your application.</param>
public static void ReloadApi(bool ignoreAlreadyLoaded = false, RenderDocVersion? requiredVersion = null)
{
if (_loaded && !ignoreAlreadyLoaded)
return;
lock (typeof(RenderDoc))
{
// Prevent double loads.
if (_loaded && !ignoreAlreadyLoaded)
return;
if (requiredVersion.HasValue)
MinimumRequired = requiredVersion.Value;
_loaded = true;
_api = GetApi(MinimumRequired);
if (_api != null)
AssertAtLeast(MinimumRequired);
}
}
private static RenderDocApi* _api = null;
private static bool _loaded;
private static RenderDocApi* Api
{
get
{
ReloadApi();
return _api;
}
}
private static readonly Regex _dynamicLibraryPattern = RenderDocApiDynamicLibraryRegex();
private static RenderDocApi* GetApi(RenderDocVersion minimumRequired = RenderDocVersion.Version_1_0_0)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
string moduleName = module.FileName ?? string.Empty;
if (!_dynamicLibraryPattern.IsMatch(moduleName))
continue;
if (!NativeLibrary.TryLoad(moduleName, out nint moduleHandle))
return null;
if (!NativeLibrary.TryGetExport(moduleHandle, "RENDERDOC_GetAPI", out nint procAddress))
return null;
var RENDERDOC_GetApi = (delegate* unmanaged[Cdecl]<RenderDocVersion, RenderDocApi**, int>)procAddress;
RenderDocApi* api;
return RENDERDOC_GetApi(minimumRequired, &api) != 0 ? api : null;
}
return null;
}
private static void AssertAtLeast(RenderDocVersion rdv, [CallerMemberName] string callee = "")
{
Version ver = rdv.SystemVersion;
AssertAtLeast(ver.Major, ver.Minor, ver.Build, callee);
}
private static void AssertAtLeast(int major, int minor, int patch = 0, [CallerMemberName] string callee = "")
{
if (!AssertVersionEnabled)
return;
if (Version!.Major < major)
goto fail;
if (Version.Major > major)
goto success;
if (Version.Minor < minor)
goto fail;
if (Version.Minor > minor)
goto success;
if (Version.Build < patch)
goto fail;
success:
return;
fail:
Version minVersion =
typeof(RenderDoc).GetMethod(callee)!.GetCustomAttribute<RenderDocApiVersionAttribute>()!.MinVersion;
throw new NotSupportedException(
$"This API was introduced in RenderDoc API {minVersion}. Current API version is {Version}.");
}
private static byte[] ToNullTerminatedByteArray(this string str, Encoding? encoding = null)
{
encoding ??= Encoding.UTF8;
return encoding.GetBytes(str + '\0');
}
[GeneratedRegex(@"(lib)?renderdoc(\.dll|\.so|\.dylib)(\.\d+)?",
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
private static partial Regex RenderDocApiDynamicLibraryRegex();
#endregion
}
}

View File

@@ -1,51 +0,0 @@
namespace Ryujinx.Graphics.RenderDocApi
{
#pragma warning disable CS0649
internal unsafe struct RenderDocApi
{
public delegate* unmanaged[Cdecl]<int*, int*, int*, void> GetApiVersion;
public delegate* unmanaged[Cdecl]<CaptureOption, uint, int> SetCaptureOptionU32;
public delegate* unmanaged[Cdecl]<CaptureOption, float, int> SetCaptureOptionF32;
public delegate* unmanaged[Cdecl]<CaptureOption, uint> GetCaptureOptionU32;
public delegate* unmanaged[Cdecl]<CaptureOption, float> GetCaptureOptionF32;
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetFocusToggleKeys;
public delegate* unmanaged[Cdecl]<InputButton*, int, void> SetCaptureKeys;
public delegate* unmanaged[Cdecl]<OverlayBits> GetOverlayBits;
public delegate* unmanaged[Cdecl]<OverlayBits, OverlayBits, void> MaskOverlayBits;
public delegate* unmanaged[Cdecl]<void> RemoveHooks;
public delegate* unmanaged[Cdecl]<void> UnloadCrashHandler;
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureFilePathTemplate;
public delegate* unmanaged[Cdecl]<byte*> GetCaptureFilePathTemplate;
public delegate* unmanaged[Cdecl]<int> GetNumCaptures;
public delegate* unmanaged[Cdecl]<int, byte*, int*, long*, uint> GetCapture;
public delegate* unmanaged[Cdecl]<void> TriggerCapture;
public delegate* unmanaged[Cdecl]<uint> IsTargetControlConnected;
public delegate* unmanaged[Cdecl]<uint, byte*, uint> LaunchReplayUI;
public delegate* unmanaged[Cdecl]<void*, void*, void> SetActiveWindow;
public delegate* unmanaged[Cdecl]<void*, void*, void> StartFrameCapture;
public delegate* unmanaged[Cdecl]<uint> IsFrameCapturing;
public delegate* unmanaged[Cdecl]<void*, void*, uint> EndFrameCapture;
// 1.1
public delegate* unmanaged[Cdecl]<uint, void> TriggerMultiFrameCapture;
// 1.2
public delegate* unmanaged[Cdecl]<byte*, byte*, void> SetCaptureFileComments;
// 1.3
public delegate* unmanaged[Cdecl]<void*, void*, uint> DiscardFrameCapture;
// 1.5
public delegate* unmanaged[Cdecl]<uint> ShowReplayUI;
// 1.6
public delegate* unmanaged[Cdecl]<byte*, void> SetCaptureTitle;
}
#pragma warning restore CS0649
}

View File

@@ -1,16 +0,0 @@
using System;
namespace Ryujinx.Graphics.RenderDocApi
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
public sealed class RenderDocApiVersionAttribute : Attribute
{
public Version MinVersion { get; }
public RenderDocApiVersionAttribute(int major, int minor, int patch = 0)
{
MinVersion = new Version(major, minor, patch);
}
}
}

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