Compare commits

...

9 Commits

Author SHA1 Message Date
Mary Guillemard
bc4d99a078 ci: try to fix toctou on release creation
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:58:03 +01:00
Mary Guillemard
ec6cb0abb4
infra: Make Avalonia the default UI (#6375)
* misc: Move Ryujinx project to Ryujinx.Gtk3

This breaks release CI for now but that's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* misc: Move Ryujinx.Ava project to Ryujinx

This breaks CI for now, but it's fine.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* infra: Make Avalonia the default UI

Should fix CI after the previous changes.

GTK3 isn't build by the release job anymore, only by PR CI.

This also ensure that the test-ava update package is still generated to
allow update from the old testing channel.

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix missing copy in create_app_bundle.sh

Signed-off-by: Mary Guillemard <mary@mary.zone>

* Fix syntax error

Signed-off-by: Mary Guillemard <mary@mary.zone>

---------

Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 12:51:05 +01:00
Mary Guillemard
53b5985da6
Avalonia: only enable gamescope workaround under it (#6374)
Signed-off-by: Mary Guillemard <mary@mary.zone>
2024-03-02 11:16:46 +01:00
gdkchan
87f238be60
Change packed aliasing formats to UInt (#6358) 2024-02-24 19:02:20 -03:00
dependabot[bot]
4d0dbbfae2
nuget: bump System.Drawing.Common from 8.0.1 to 8.0.2 (#6308)
Bumps [System.Drawing.Common](https://github.com/dotnet/winforms) from 8.0.1 to 8.0.2.
- [Release notes](https://github.com/dotnet/winforms/releases)
- [Changelog](https://github.com/dotnet/winforms/blob/main/docs/release-activity.md)
- [Commits](https://github.com/dotnet/winforms/compare/v8.0.1...v8.0.2)

---
updated-dependencies:
- dependency-name: System.Drawing.Common
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-24 01:17:24 +01:00
jhorv
08384ee5a8
IPC code gen improvements (#6352)
* HipcGenerator: skip do-nothing call to MemoryMarshal.Cast<byte, byte>() in generated code

* HipcGenerator: fix method name typos

* HipcGenerator: make generated methods use stackalloc for `isBufferMapAlias` bool array

* HipcGenerator: make generated GetCommandHandlers() method return a FrozenDictionary<int, CommandHandler>

* HipcGenerator: return `FrozenDictionary<,>.Empty` when there are no command implementations, otherwise create `FrozenDictionary` from a `IEnumerable<KeyValuePair<,>>`` instead of a `Dictionary<,>``
2024-02-23 21:12:24 -03:00
gdkchan
d4d0a48bfe
Migrate Audio service to new IPC (#6285)
* Migrate audren to new IPC

* Migrate audout

* Migrate audin

* Migrate hwopus

* Bye bye old audio service

* Switch volume control to IHardwareDeviceDriver

* Somewhat unrelated changes

* Remove Concentus reference from HLE

* Implement OpenAudioRendererForManualExecution

* Remove SetVolume/GetVolume methods that are not necessary

* Remove SetVolume/GetVolume methods that are not necessary (2)

* Fix incorrect volume update

* PR feedback

* PR feedback

* Stub audrec

* Init outParameter

* Make FinalOutputRecorderParameter/Internal readonly

* Make FinalOutputRecorder IDisposable

* Fix HardwareOpusDecoderManager parameter buffers

* Opus work buffer size and error handling improvements

* Add AudioInProtocolName enum

* Fix potential divisions by zero
2024-02-22 16:58:33 -03:00
riperiperi
57d8afd0c9
OpenGL: Mask out all color outputs with no fragment shader (#6341)
* OpenGL: Mask out all color outputs with no fragment shader

This appears to match Vulkan's behaviour, which is needed for stencil shadows in Penny's Big Breakaway. It's far from the only issue, you can try the Full Bindless PR if you want to see it in a more intact state.

* Remove unused member
2024-02-22 18:43:19 +01:00
gdkchan
c43fb92bbf
Ensure service init runs after Horizon constructor (#6342) 2024-02-22 13:55:29 -03:00
374 changed files with 4407 additions and 4460 deletions

View File

@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'" # Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
dotnet_diagnostic.CA1862.severity = none dotnet_diagnostic.CA1862.severity = none
[src/Ryujinx.HLE/HOS/Services/**.cs] [src/Ryujinx/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for services # Disable "mark members as static" rule for ViewModels
dotnet_diagnostic.CA1822.severity = none dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Ava/UI/ViewModels/**.cs] [src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for ViewModels # Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Tests/Cpu/*.cs] [src/Ryujinx.Tests/Cpu/*.cs]

2
.github/labeler.yml vendored
View File

@ -20,7 +20,7 @@ gpu:
gui: gui:
- changed-files: - changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Ava/**'] - any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**']
horizon: horizon:
- changed-files: - changed-files:

View File

@ -67,15 +67,15 @@ jobs:
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Publish Ryujinx.Ava - name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Set executable bit - name: Set executable bit
run: | run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Upload Ryujinx artifact - name: Upload Ryujinx artifact
@ -92,11 +92,11 @@ jobs:
path: publish_sdl2_headless path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
- name: Upload Ryujinx.Ava artifact - name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }} name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_ava path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest' if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
build_macos: build_macos:
@ -140,19 +140,19 @@ jobs:
shell: bash shell: bash
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx.Ava - name: Publish macOS Ryujinx
run: | run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" ./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2 - name: Publish macOS Ryujinx.Headless.SDL2
run: | run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact - name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz" path: "publish/*.tar.gz"
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact - name: Upload Ryujinx.Headless.SDL2 artifact

View File

@ -39,24 +39,24 @@ jobs:
return core.error(`No artifacts found`); return core.error(`No artifacts found`);
} }
let body = `Download the artifacts for this pull request:\n`; let body = `Download the artifacts for this pull request:\n`;
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`; let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`; let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`; let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) { for (const art of artifacts) {
if(art.name.includes('Debug')) { if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('ava-ryujinx')) { } else if(art.name.includes('gtk-ryujinx')) {
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) { } else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else { } else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`; body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} }
} }
hidden_avalonia_artifacts += `\n</details>`; hidden_gtk_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`; hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`; hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts; body += hidden_gtk_artifacts;
body += hidden_headless_artifacts; body += hidden_headless_artifacts;
body += hidden_debug_artifacts; body += hidden_debug_artifacts;

View File

@ -44,6 +44,17 @@ jobs:
sha: context.sha sha: context.sha
}) })
- name: Create release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release: release:
name: Release for ${{ matrix.platform.name }} name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }} runs-on: ${{ matrix.platform.os }}
@ -86,14 +97,13 @@ jobs:
- name: Publish - name: Publish
run: | run: |
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
- name: Packing Windows builds - name: Packing Windows builds
if: matrix.platform.os == 'windows-latest' if: matrix.platform.os == 'windows-latest'
run: | run: |
pushd publish_gtk pushd publish_ava
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
@ -102,6 +112,7 @@ jobs:
popd popd
pushd publish_ava pushd publish_ava
mv publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish 7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd popd
shell: bash shell: bash
@ -109,7 +120,7 @@ jobs:
- name: Packing Linux builds - name: Packing Linux builds
if: matrix.platform.os == 'ubuntu-latest' if: matrix.platform.os == 'ubuntu-latest'
run: | run: |
pushd publish_gtk pushd publish_ava
chmod +x publish/Ryujinx.sh publish/Ryujinx chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd
@ -120,6 +131,7 @@ jobs:
popd popd
pushd publish_ava pushd publish_ava
mv publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd popd
@ -183,7 +195,7 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash shell: bash
- name: Publish macOS Ryujinx.Ava - name: Publish macOS Ryujinx
run: | 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 ./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

View File

@ -45,7 +45,7 @@
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build32" /> <PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.1" /> <PackageVersion Include="System.Drawing.Common" Version="8.0.2" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" /> <PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430 VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
EndProject EndProject
@ -69,7 +69,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject EndProject

View File

@ -6,10 +6,6 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2" RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi fi
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx" RYUJINX_BIN="Ryujinx"
fi fi

View File

@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS" mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources" mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executables first # Copy executable and nsure executable can be executed
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx" chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Then all libraries # Then all libraries

View File

@ -22,9 +22,9 @@ EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ]; if [ "$VERSION" == "1.1.0" ];
then then
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else else
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
fi fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app" ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
@ -38,9 +38,9 @@ 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 true $EXTRA_ARGS)
dotnet restore dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava 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.Ava 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) # Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME"
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx" 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" gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME" rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd popd
echo "Done" echo "Done"

View File

@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _stillRunning; private bool _stillRunning;
private readonly Thread _updaterThread; private readonly Thread _updaterThread;
private float _volume;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public OpenALHardwareDeviceDriver() public OpenALHardwareDeviceDriver()
{ {
_device = ALC.OpenDevice(""); _device = ALC.OpenDevice("");
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
Name = "HardwareDeviceDriver.OpenAL", Name = "HardwareDeviceDriver.OpenAL",
}; };
_volume = 1f;
_updaterThread.Start(); _updaterThread.Start();
} }
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
} }
} }
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{ {
if (channelCount == 0) if (channelCount == 0)
{ {
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
throw new ArgumentException($"{channelCount}"); throw new ArgumentException($"{channelCount}");
} }
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0); _sessions.TryAdd(session, 0);

View File

@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _isActive; private bool _isActive;
private readonly Queue<OpenALAudioBuffer> _queuedBuffers; private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
private ulong _playedSampleCount; private ulong _playedSampleCount;
private float _volume;
private readonly object _lock = new(); private readonly object _lock = new();
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{ {
_driver = driver; _driver = driver;
_queuedBuffers = new Queue<OpenALAudioBuffer>(); _queuedBuffers = new Queue<OpenALAudioBuffer>();
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
_targetFormat = GetALFormat(); _targetFormat = GetALFormat();
_isActive = false; _isActive = false;
_playedSampleCount = 0; _playedSampleCount = 0;
SetVolume(requestedVolume); SetVolume(1f);
} }
private ALFormat GetALFormat() private ALFormat GetALFormat()
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
public override void SetVolume(float volume) public override void SetVolume(float volume)
{ {
lock (_lock) _volume = volume;
{
AL.Source(_sourceId, ALSourcef.Gain, volume); UpdateMasterVolume(_driver.Volume);
}
} }
public override float GetVolume() public override float GetVolume()
{ {
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); return _volume;
}
return volume; public void UpdateMasterVolume(float newVolume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
}
} }
public override void Start() public override void Start()

View File

@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly bool _supportSurroundConfiguration; private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
// TODO: Add this to SDL2-CS // TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec. // NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054 #pragma warning disable SYSLIB1054
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
{ {
_supportSurroundConfiguration = spec.channels >= 6; _supportSurroundConfiguration = spec.channels >= 6;
} }
Volume = 1f;
} }
public static bool IsSupported => IsSupportedInternal(); public static bool IsSupported => IsSupportedInternal();
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
return _pauseEvent; return _pauseEvent;
} }
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{ {
if (channelCount == 0) if (channelCount == 0)
{ {
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
} }
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0); _sessions.TryAdd(session, 0);

View File

@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private float _volume; private float _volume;
private readonly ushort _nativeSampleFormat; private readonly ushort _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{ {
_driver = driver; _driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue; _sampleCount = uint.MaxValue;
_started = false; _started = false;
_volume = requestedVolume; _volume = 1f;
} }
private void EnsureAudioStreamSetup(AudioBuffer buffer) private void EnsureAudioStreamSetup(AudioBuffer buffer)
@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
streamSpan.Clear(); streamSpan.Clear();
// Apply volume to written data // Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
} }
ulong sampleCount = GetSampleCount(samples.Length); ulong sampleCount = GetSampleCount(samples.Length);

View File

@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions; private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
private int _disposeState; private int _disposeState;
private float _volume = 1f;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public SoundIoHardwareDeviceDriver() public SoundIoHardwareDeviceDriver()
{ {
_audioContext = SoundIoContext.Create(); _audioContext = SoundIoContext.Create();
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _pauseEvent; return _pauseEvent;
} }
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{ {
if (channelCount == 0) if (channelCount == 0)
{ {
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
sampleRate = Constants.TargetSampleRate; sampleRate = Constants.TargetSampleRate;
} }
volume = Math.Clamp(volume, 0, 1);
if (direction != Direction.Output) if (direction != Direction.Output)
{ {
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
} }
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0); _sessions.TryAdd(session, 0);

View File

@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly DynamicRingBuffer _ringBuffer; private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount; private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent; private readonly ManualResetEvent _updateRequiredEvent;
private float _volume;
private int _disposeState; private int _disposeState;
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{ {
_driver = driver; _driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>(); _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
_ringBuffer = new DynamicRingBuffer(); _ringBuffer = new DynamicRingBuffer();
_volume = 1f;
SetupOutputStream(requestedVolume); SetupOutputStream(driver.Volume);
} }
private void SetupOutputStream(float requestedVolume) private void SetupOutputStream(float requestedVolume)
@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override float GetVolume() public override float GetVolume()
{ {
return _outputStream.Volume; return _volume;
} }
public override void PrepareToClose() { } public override void PrepareToClose() { }
@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override void SetVolume(float volume) public override void SetVolume(float volume)
{ {
_outputStream.SetVolume(volume); _volume = volume;
_outputStream.SetVolume(_driver.Volume * volume);
}
public void UpdateMasterVolume(float newVolume)
{
_outputStream.SetVolume(newVolume * _volume);
} }
public override void Start() public override void Start()

View File

@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public static bool IsSupported => true; public static bool IsSupported => true;
public float Volume
{
get => _realDriver.Volume;
set => _realDriver.Volume = value;
}
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{ {
_realDriver = realDevice; _realDriver = realDevice;
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
throw new ArgumentException("No valid sample format configuration found!"); throw new ArgumentException("No valid sample format configuration found!");
} }
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{ {
if (channelCount == 0) if (channelCount == 0)
{ {
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
sampleRate = Constants.TargetSampleRate; sampleRate = Constants.TargetSampleRate;
} }
volume = Math.Clamp(volume, 0, 1);
if (!_realDriver.SupportsDirection(direction)) if (!_realDriver.SupportsDirection(direction))
{ {
if (direction == Direction.Input) if (direction == Direction.Input)
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat); SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume); IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat) if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
{ {

View File

@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
public static bool IsSupported => true; public static bool IsSupported => true;
public float Volume { get; set; }
public DummyHardwareDeviceDriver() public DummyHardwareDeviceDriver()
{ {
_updateRequiredEvent = new ManualResetEvent(false); _updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true); _pauseEvent = new ManualResetEvent(true);
Volume = 1f;
} }
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume) public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{ {
if (sampleRate == 0) if (sampleRate == 0)
{ {
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
if (direction == Direction.Output) if (direction == Direction.Output)
{ {
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume); return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
} }
return new DummyHardwareDeviceSessionInput(this, memoryManager); return new DummyHardwareDeviceSessionInput(this, memoryManager);

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
private ulong _playedSampleCount; private ulong _playedSampleCount;
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{ {
_volume = requestedVolume; _volume = 1f;
_manager = manager; _manager = manager;
} }

View File

@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
/// </summary> /// </summary>
/// <param name="filtered">If true, filter disconnected devices</param> /// <param name="filtered">If true, filter disconnected devices</param>
/// <returns>The list of all audio inputs name</returns> /// <returns>The list of all audio inputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioIns(bool filtered) public string[] ListAudioIns(bool filtered)
{ {
if (filtered) if (filtered)
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
return new[] { Constants.DefaultDeviceInputName }; return new[] { Constants.DefaultDeviceInputName };
} }
#pragma warning restore CA1822
/// <summary> /// <summary>
/// Open a new <see cref="AudioInputSystem"/>. /// Open a new <see cref="AudioInputSystem"/>.
@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
/// <param name="inputDeviceName">The input device name wanted by the user</param> /// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param> /// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param> /// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioIn(out string outputDeviceName, public ResultCode OpenAudioIn(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration, out AudioOutputConfiguration outputConfiguration,
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
IVirtualMemoryManager memoryManager, IVirtualMemoryManager memoryManager,
string inputDeviceName, string inputDeviceName,
SampleFormat sampleFormat, SampleFormat sampleFormat,
ref AudioInputConfiguration parameter, ref AudioInputConfiguration parameter)
ulong appletResourceUserId,
uint processHandle)
{ {
int sessionId = AcquireSessionId(); int sessionId = AcquireSessionId();

View File

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
private readonly byte[] _buffer; private readonly byte[] _buffer;
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume) public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
{ {
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume); _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
_channelCount = channelCount; _channelCount = channelCount;
_sampleRate = sampleRate; _sampleRate = sampleRate;
_currentBufferTag = 0; _currentBufferTag = 0;

View File

@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
Output, Output,
} }
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f); float Volume { get; set; }
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
ManualResetEvent GetUpdateRequiredEvent(); ManualResetEvent GetUpdateRequiredEvent();
ManualResetEvent GetPauseEvent(); ManualResetEvent GetPauseEvent();

View File

@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
/// Get the list of all audio outputs name. /// Get the list of all audio outputs name.
/// </summary> /// </summary>
/// <returns>The list of all audio outputs name</returns> /// <returns>The list of all audio outputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioOuts() public string[] ListAudioOuts()
{ {
return new[] { Constants.DefaultDeviceOutputName }; return new[] { Constants.DefaultDeviceOutputName };
} }
#pragma warning restore CA1822
/// <summary> /// <summary>
/// Open a new <see cref="AudioOutputSystem"/>. /// Open a new <see cref="AudioOutputSystem"/>.
@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
/// <param name="inputDeviceName">The input device name wanted by the user</param> /// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param> /// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param> /// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <param name="volume">The volume level to request</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioOut(out string outputDeviceName, public ResultCode OpenAudioOut(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration, out AudioOutputConfiguration outputConfiguration,
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
IVirtualMemoryManager memoryManager, IVirtualMemoryManager memoryManager,
string inputDeviceName, string inputDeviceName,
SampleFormat sampleFormat, SampleFormat sampleFormat,
ref AudioInputConfiguration parameter, ref AudioInputConfiguration parameter)
ulong appletResourceUserId,
uint processHandle,
float volume)
{ {
int sessionId = AcquireSessionId(); int sessionId = AcquireSessionId();
_sessionsBufferEvents[sessionId].Clear(); _sessionsBufferEvents[sessionId].Clear();
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
return result; return result;
} }
/// <summary>
/// Sets the volume for all output devices.
/// </summary>
/// <param name="volume">The volume to set.</param>
public void SetVolume(float volume)
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
session?.SetVolume(volume);
}
}
}
/// <summary>
/// Gets the volume for all output devices.
/// </summary>
/// <returns>A float indicating the volume level.</returns>
public float GetVolume()
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
if (session != null)
{
return session.GetVolume();
}
}
}
return 0.0f;
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_event = new ManualResetEvent(false); _event = new ManualResetEvent(false);
} }
#pragma warning disable IDE0051 // Remove unused private member
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
{ {
// Get the real device driver (In case the compat layer is on top of it). // Get the real device driver (In case the compat layer is on top of it).
@ -59,9 +58,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
return 2; return 2;
} }
#pragma warning restore IDE0051
public void Start(IHardwareDeviceDriver deviceDriver, float volume) public void Start(IHardwareDeviceDriver deviceDriver)
{ {
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < OutputDevices.Length; i++) for (int i = 0; i < OutputDevices.Length; i++)
{ {
// TODO: Don't hardcode sample rate. // TODO: Don't hardcode sample rate.
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume); OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
} }
_mailbox = new Mailbox<MailboxMessage>(); _mailbox = new Mailbox<MailboxMessage>();
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_mailbox.SendResponse(MailboxMessage.Stop); _mailbox.SendResponse(MailboxMessage.Stop);
} }
public float GetVolume()
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
if (outputDevice != null)
{
return outputDevice.GetVolume();
}
}
}
return 0f;
}
public void SetVolume(float volume)
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
outputDevice?.SetVolume(volume);
}
}
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
if (disposing) if (disposing)
{ {
_event.Dispose(); _event.Dispose();
_mailbox?.Dispose();
} }
} }
} }

View File

@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary> /// <summary>
/// Start the <see cref="AudioProcessor"/> and worker thread. /// Start the <see cref="AudioProcessor"/> and worker thread.
/// </summary> /// </summary>
private void StartLocked(float volume) private void StartLocked()
{ {
_isRunning = true; _isRunning = true;
// TODO: virtual device mapping (IAudioDevice) // TODO: virtual device mapping (IAudioDevice)
Processor.Start(_deviceDriver, volume); Processor.Start(_deviceDriver);
_workerThread = new Thread(SendCommands) _workerThread = new Thread(SendCommands)
{ {
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Register a new <see cref="AudioRenderSystem"/>. /// Register a new <see cref="AudioRenderSystem"/>.
/// </summary> /// </summary>
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param> /// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
private void Register(AudioRenderSystem renderer, float volume) private void Register(AudioRenderSystem renderer)
{ {
lock (_sessionLock) lock (_sessionLock)
{ {
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
if (!_isRunning) if (!_isRunning)
{ {
StartLocked(volume); StartLocked();
} }
} }
} }
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
ulong appletResourceUserId, ulong appletResourceUserId,
ulong workBufferAddress, ulong workBufferAddress,
ulong workBufferSize, ulong workBufferSize,
uint processHandle, uint processHandle)
float volume)
{ {
int sessionId = AcquireSessionId(); int sessionId = AcquireSessionId();
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
{ {
renderer = audioRenderer; renderer = audioRenderer;
Register(renderer, volume); Register(renderer);
} }
else else
{ {
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
return result; return result;
} }
public float GetVolume()
{
if (Processor != null)
{
return Processor.GetVolume();
}
return 0f;
}
public void SetVolume(float volume)
{
Processor?.SetVolume(volume);
}
public void Dispose() public void Dispose()
{ {
GC.SuppressFinalize(this); GC.SuppressFinalize(this);

View File

@ -1,237 +0,0 @@
using Avalonia;
using Avalonia.Threading;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal partial class Program
{
public static double WindowScaleFactor { get; set; }
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
private const uint MbIconwarning = 0x30;
public static void Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
_ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);
}
PreviewerDetached = true;
Initialize(args);
LoggerAdapter.Register();
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
}
public static AppBuilder BuildAvaloniaApp()
{
return AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
EnableIme = true,
EnableInputFocusProxy = true,
RenderingMode = new[] { X11RenderingMode.Glx, X11RenderingMode.Software },
})
.With(new Win32PlatformOptions
{
WinUICompositionBackdropCornerRadius = 8.0f,
RenderingMode = new[] { Win32RenderingMode.AngleEgl, Win32RenderingMode.Software },
})
.UseSkia();
}
private static void Initialize(string[] args)
{
// Parse arguments
CommandLineState.ParseArguments(args);
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// Hook unhandled exception and process exit events.
AppDomain.CurrentDomain.UnhandledException += (sender, e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (sender, e) => Exit();
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize the logger system.
LoggerModule.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action => Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Input);
ReloadConfig();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
// Check if keys exists.
if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
{
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{
MainWindow.ShowKeyErrorOnLoad = true;
}
}
if (CommandLineState.LaunchPathArg != null)
{
MainWindow.DeferLoadApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
}
public static void ReloadConfig()
{
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
ConfigurationPath = localConfigurationPath;
}
else if (File.Exists(appDataConfigurationPath))
{
ConfigurationPath = appDataConfigurationPath;
}
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
// Check if graphics backend was overridden
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
}
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
{
string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message);
if (Logger.Error == null)
{
Logger.Notice.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
{
Exit();
}
}
public static void Exit()
{
DiscordIntegrationModule.Exit();
Logger.Shutdown();
}
}
}

View File

@ -1,167 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<SigningCertificate Condition=" '$(SigningCertificate)' == '' ">-</SigningCertificate>
<RootNamespace>Ryujinx.Ava</RootNamespace>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
<TieredPGO>true</TieredPGO>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<Exec Command="codesign --entitlements '$(ProjectDir)..\..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
</Target>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<!--
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
See:
https://github.com/amwx/FluentAvalonia/issues/481
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
-->
<PropertyGroup>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" />
<PackageReference Include="Avalonia.Diagnostics" Condition="'$(Configuration)'=='Debug'" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
<PackageReference Include="Avalonia.Svg" />
<PackageReference Include="Avalonia.Svg.Skia" />
<PackageReference Include="DynamicData" />
<PackageReference Include="FluentAvaloniaUI" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
<!--NOTE: DO NOT REMOVE, THIS IS REQUIRED AS A RESULT OF A TRIMMING ISSUE IN AVALONIA -->
<PackageReference Include="System.Drawing.Common" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
<ProjectReference Include="..\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</Content>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="UI\**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Fonts\SegoeFluentIcons.ttf" />
<AvaloniaResource Include="Assets\Styles\Themes.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
<AvaloniaResource Include="Assets\Styles\Styles.xaml" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Locales\el_GR.json" />
<None Remove="Assets\Locales\en_US.json" />
<None Remove="Assets\Locales\es_ES.json" />
<None Remove="Assets\Locales\fr_FR.json" />
<None Remove="Assets\Locales\he_IL.json" />
<None Remove="Assets\Locales\de_DE.json" />
<None Remove="Assets\Locales\it_IT.json" />
<None Remove="Assets\Locales\ja_JP.json" />
<None Remove="Assets\Locales\ko_KR.json" />
<None Remove="Assets\Locales\pl_PL.json" />
<None Remove="Assets\Locales\pt_BR.json" />
<None Remove="Assets\Locales\ru_RU.json" />
<None Remove="Assets\Locales\tr_TR.json" />
<None Remove="Assets\Locales\uk_UA.json" />
<None Remove="Assets\Locales\zh_CN.json" />
<None Remove="Assets\Locales\zh_TW.json" />
<None Remove="Assets\Styles\Styles.xaml" />
<None Remove="Assets\Styles\Themes.xaml" />
<None Remove="Assets\Icons\Controller_JoyConLeft.svg" />
<None Remove="Assets\Icons\Controller_JoyConPair.svg" />
<None Remove="Assets\Icons\Controller_JoyConRight.svg" />
<None Remove="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Locales\el_GR.json" />
<EmbeddedResource Include="Assets\Locales\en_US.json" />
<EmbeddedResource Include="Assets\Locales\es_ES.json" />
<EmbeddedResource Include="Assets\Locales\fr_FR.json" />
<EmbeddedResource Include="Assets\Locales\he_IL.json" />
<EmbeddedResource Include="Assets\Locales\de_DE.json" />
<EmbeddedResource Include="Assets\Locales\it_IT.json" />
<EmbeddedResource Include="Assets\Locales\ja_JP.json" />
<EmbeddedResource Include="Assets\Locales\ko_KR.json" />
<EmbeddedResource Include="Assets\Locales\pl_PL.json" />
<EmbeddedResource Include="Assets\Locales\pt_BR.json" />
<EmbeddedResource Include="Assets\Locales\ru_RU.json" />
<EmbeddedResource Include="Assets\Locales\tr_TR.json" />
<EmbeddedResource Include="Assets\Locales\uk_UA.json" />
<EmbeddedResource Include="Assets\Locales\zh_CN.json" />
<EmbeddedResource Include="Assets\Locales\zh_TW.json" />
<EmbeddedResource Include="Assets\Styles\Styles.xaml" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConLeft.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConPair.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_JoyConRight.svg" />
<EmbeddedResource Include="Assets\Icons\Controller_ProCon.svg" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup>
</Project>

View File

@ -674,9 +674,9 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1), 1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1), 2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1), 4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2), 8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4), 16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
_ => format, _ => format,
}; };
} }

View File

@ -1117,7 +1117,7 @@ namespace Ryujinx.Graphics.OpenGL
prg.Bind(); prg.Bind();
} }
if (prg.HasFragmentShader && _fragmentOutputMap != (uint)prg.FragmentOutputMap) if (_fragmentOutputMap != (uint)prg.FragmentOutputMap)
{ {
_fragmentOutputMap = (uint)prg.FragmentOutputMap; _fragmentOutputMap = (uint)prg.FragmentOutputMap;

View File

@ -30,7 +30,6 @@ namespace Ryujinx.Graphics.OpenGL
private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete; private ProgramLinkStatus _status = ProgramLinkStatus.Incomplete;
private int[] _shaderHandles; private int[] _shaderHandles;
public bool HasFragmentShader;
public int FragmentOutputMap { get; } public int FragmentOutputMap { get; }
public Program(ShaderSource[] shaders, int fragmentOutputMap) public Program(ShaderSource[] shaders, int fragmentOutputMap)
@ -40,6 +39,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1); GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
_shaderHandles = new int[shaders.Length]; _shaderHandles = new int[shaders.Length];
bool hasFragmentShader = false;
for (int index = 0; index < shaders.Length; index++) for (int index = 0; index < shaders.Length; index++)
{ {
@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.OpenGL
if (shader.Stage == ShaderStage.Fragment) if (shader.Stage == ShaderStage.Fragment)
{ {
HasFragmentShader = true; hasFragmentShader = true;
} }
int shaderHandle = GL.CreateShader(shader.Stage.Convert()); int shaderHandle = GL.CreateShader(shader.Stage.Convert());
@ -71,7 +71,7 @@ namespace Ryujinx.Graphics.OpenGL
GL.LinkProgram(Handle); GL.LinkProgram(Handle);
FragmentOutputMap = fragmentOutputMap; FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
} }
public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap) public Program(ReadOnlySpan<byte> code, bool hasFragmentShader, int fragmentOutputMap)
@ -91,8 +91,7 @@ namespace Ryujinx.Graphics.OpenGL
} }
} }
HasFragmentShader = hasFragmentShader; FragmentOutputMap = hasFragmentShader ? fragmentOutputMap : 0;
FragmentOutputMap = fragmentOutputMap;
} }
public void Bind() public void Bind()

View File

@ -25,7 +25,7 @@ namespace Ryujinx.Modules
private readonly string _buildUrl; private readonly string _buildUrl;
private bool _restartQuery; private bool _restartQuery;
public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { } public UpdateDialog(MainWindow mainWindow, Version newVersion, string buildUrl) : this(new Builder("Ryujinx.Gtk3.Modules.Updater.UpdateDialog.glade"), mainWindow, newVersion, buildUrl) { }
private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog")) private UpdateDialog(Builder builder, MainWindow mainWindow, Version newVersion, string buildUrl) : base(builder.GetRawOwnedObject("UpdateDialog"))
{ {
@ -34,7 +34,7 @@ namespace Ryujinx.Modules
_mainWindow = mainWindow; _mainWindow = mainWindow;
_buildUrl = buildUrl; _buildUrl = buildUrl;
Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Icon = new Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png");
MainText.Text = "Do you want to update Ryujinx to the latest version?"; MainText.Text = "Do you want to update Ryujinx to the latest version?";
SecondaryText.Text = $"{Program.Version} -> {newVersion}"; SecondaryText.Text = $"{Program.Version} -> {newVersion}";

View File

@ -1,75 +1,93 @@
using Avalonia.Controls; using Gtk;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using ICSharpCode.SharpZipLib.GZip; using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip;
using Ryujinx.Ava;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities; using Ryujinx.Common.Utilities;
using Ryujinx.UI.Common.Helper; using Ryujinx.UI;
using Ryujinx.UI.Common.Models.Github; using Ryujinx.UI.Common.Models.Github;
using Ryujinx.UI.Widgets;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ryujinx.Modules namespace Ryujinx.Modules
{ {
internal static class Updater public static class Updater
{ {
private const string GitHubApiUrl = "https://api.github.com"; private const string GitHubApiUrl = "https://api.github.com";
private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private const int ConnectionCount = 4;
internal static bool Running;
private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish"); private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
private const int ConnectionCount = 4;
private static string _buildVer; private static string _buildVer;
private static string _platformExt; private static string _platformExt;
private static string _buildUrl; private static string _buildUrl;
private static long _buildSize; private static long _buildSize;
private static bool _updateSuccessful;
private static bool _running;
private static readonly string[] _windowsDependencyDirs = Array.Empty<string>(); private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public static async Task BeginParse(Window mainWindow, bool showVersionUpToDate) // On Windows, GtkSharp.Dependencies adds these extra dirs that must be cleaned during updates.
private static readonly string[] _windowsDependencyDirs = { "bin", "etc", "lib", "share" };
private static HttpClient ConstructHttpClient()
{ {
if (_running) HttpClient result = new();
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
{
if (Running)
{ {
return; return;
} }
_running = true; Running = true;
mainWindow.UpdateMenuItem.Sensitive = false;
int artifactIndex = -1;
// Detect current platform // Detect current platform
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
_platformExt = "macos_universal.app.tar.gz"; _platformExt = "osx_x64.zip";
artifactIndex = 1;
} }
else if (OperatingSystem.IsWindows()) else if (OperatingSystem.IsWindows())
{ {
_platformExt = "win_x64.zip"; _platformExt = "win_x64.zip";
artifactIndex = 2;
} }
else if (OperatingSystem.IsLinux()) else if (OperatingSystem.IsLinux())
{ {
var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
_platformExt = $"linux_{arch}.tar.gz"; _platformExt = $"linux_{arch}.tar.gz";
artifactIndex = 0;
}
if (artifactIndex == -1)
{
GtkDialog.CreateErrorDialog("Your platform is not supported!");
return;
} }
Version newVersion; Version newVersion;
@ -81,14 +99,9 @@ namespace Ryujinx.Modules
} }
catch catch
{ {
GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!");
Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return; return;
} }
@ -96,15 +109,16 @@ namespace Ryujinx.Modules
try try
{ {
using HttpClient jsonClient = ConstructHttpClient(); using HttpClient jsonClient = ConstructHttpClient();
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest"; string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
// Fetch latest build information
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl); string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
_buildVer = fetched.Name; _buildVer = fetched.Name;
foreach (var asset in fetched.Assets) foreach (var asset in fetched.Assets)
{ {
if (asset.Name.StartsWith("test-ava-ryujinx") && asset.Name.EndsWith(_platformExt)) if (asset.Name.StartsWith("gtk-ryujinx") && asset.Name.EndsWith(_platformExt))
{ {
_buildUrl = asset.BrowserDownloadUrl; _buildUrl = asset.BrowserDownloadUrl;
@ -112,13 +126,9 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false;
return; return;
} }
@ -126,29 +136,20 @@ namespace Ryujinx.Modules
} }
} }
// If build not done, assume no new update are available. if (_buildUrl == null)
if (_buildUrl is null)
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false;
return; return;
} }
} }
catch (Exception exception) catch (Exception exception)
{ {
Logger.Error?.Print(LogClass.Application, exception.Message); Logger.Error?.Print(LogClass.Application, exception.Message);
GtkDialog.CreateErrorDialog("An error occurred when trying to get release information from GitHub Release. This can be caused if a new release is being compiled by GitHub Actions. Try again in a few minutes.");
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
_running = false;
return; return;
} }
@ -159,13 +160,8 @@ namespace Ryujinx.Modules
} }
catch catch
{ {
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); GtkDialog.CreateWarningDialog("Failed to convert the received Ryujinx version from GitHub Release.", "Cancelling Update!");
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from GitHub Release!");
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
_running = false;
return; return;
} }
@ -174,12 +170,11 @@ namespace Ryujinx.Modules
{ {
if (showVersionUpToDate) if (showVersionUpToDate)
{ {
await ContentDialogHelper.CreateUpdaterInfoDialog( GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
"");
} }
_running = false; Running = false;
mainWindow.UpdateMenuItem.Sensitive = true;
return; return;
} }
@ -202,39 +197,13 @@ namespace Ryujinx.Modules
_buildSize = -1; _buildSize = -1;
} }
await Dispatcher.UIThread.InvokeAsync(async () =>
{
// Show a message asking the user if they want to update // Show a message asking the user if they want to update
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl);
LocaleManager.Instance[LocaleKeys.RyujinxUpdater], updateDialog.Show();
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
$"{Program.Version} -> {newVersion}");
if (shouldUpdate)
{
await UpdateRyujinx(mainWindow, _buildUrl);
}
else
{
_running = false;
}
});
} }
private static HttpClient ConstructHttpClient() public static void UpdateRyujinx(UpdateDialog updateDialog, string downloadUrl)
{ {
HttpClient result = new();
// Required by GitHub to interact with APIs.
result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
return result;
}
private static async Task UpdateRyujinx(Window parent, string downloadUrl)
{
_updateSuccessful = false;
// Empty update dir, although it shouldn't ever have anything inside it // Empty update dir, although it shouldn't ever have anything inside it
if (Directory.Exists(_updateDir)) if (Directory.Exists(_updateDir))
{ {
@ -245,93 +214,22 @@ namespace Ryujinx.Modules
string updateFile = Path.Combine(_updateDir, "update.bin"); string updateFile = Path.Combine(_updateDir, "update.bin");
TaskDialog taskDialog = new() // Download the update .zip
{ updateDialog.MainText.Text = "Downloading Update...";
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater], updateDialog.ProgressBar.Value = 0;
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading], updateDialog.ProgressBar.MaxValue = 100;
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
ShowProgressBar = true,
XamlRoot = parent,
};
taskDialog.Opened += (s, e) =>
{
if (_buildSize >= 0) if (_buildSize >= 0)
{ {
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile); DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile);
} }
else else
{ {
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
}
};
await taskDialog.ShowAsync(true);
if (_updateSuccessful)
{
bool shouldRestart = true;
if (!OperatingSystem.IsMacOS())
{
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
}
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath);
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (!Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.Ava.exe" : "Ryujinx.Ava";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (string argument in CommandLineState.Arguments)
{
processStart.ArgumentList.Add(argument);
}
Process.Start(processStart);
}
Environment.Exit(0);
}
} }
} }
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{ {
// Multi-Threaded Updater // Multi-Threaded Updater
long chunkSize = _buildSize / ConnectionCount; long chunkSize = _buildSize / ConnectionCount;
@ -355,7 +253,6 @@ namespace Ryujinx.Modules
// TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient. // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
using WebClient client = new(); using WebClient client = new();
#pragma warning restore SYSLIB0014 #pragma warning restore SYSLIB0014
webClients.Add(client); webClients.Add(client);
if (i == ConnectionCount - 1) if (i == ConnectionCount - 1)
@ -375,7 +272,7 @@ namespace Ryujinx.Modules
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage); Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage); Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
}; };
client.DownloadDataCompleted += (_, args) => client.DownloadDataCompleted += (_, args) =>
@ -386,8 +283,6 @@ namespace Ryujinx.Modules
{ {
webClients[index].Dispose(); webClients[index].Dispose();
taskDialog.Hide();
return; return;
} }
@ -405,24 +300,18 @@ namespace Ryujinx.Modules
File.WriteAllBytes(updateFile, mergedFileBytes); File.WriteAllBytes(updateFile, mergedFileBytes);
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
if (OperatingSystem.IsMacOS())
{
using Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile });
xattrProcess.WaitForExit();
}
try try
{ {
InstallUpdate(taskDialog, updateFile); InstallUpdate(updateDialog, updateFile);
} }
catch (Exception e) catch (Exception e)
{ {
Logger.Warning?.Print(LogClass.Application, e.Message); Logger.Warning?.Print(LogClass.Application, e.Message);
Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater."); Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return;
} }
} }
}; };
@ -441,14 +330,14 @@ namespace Ryujinx.Modules
webClient.CancelAsync(); webClient.CancelAsync();
} }
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile); DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
return; return;
} }
} }
} }
private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile) private static void DoUpdateWithSingleThreadWorker(UpdateDialog updateDialog, string downloadUrl, string updateFile)
{ {
using HttpClient client = new(); using HttpClient client = new();
// We do not want to timeout while downloading // We do not want to timeout while downloading
@ -474,48 +363,49 @@ namespace Ryujinx.Modules
byteWritten += readSize; byteWritten += readSize;
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
updateFileStream.Write(buffer, 0, readSize); updateFileStream.Write(buffer, 0, readSize);
} }
InstallUpdate(taskDialog, updateFile); InstallUpdate(updateDialog, updateFile);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
private static double GetPercentage(double value, double max)
{ {
return max == 0 ? 0 : value / max * 100; Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile))
}
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
{
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
{ {
Name = "Updater.SingleThreadWorker", Name = "Updater.SingleThreadWorker",
}; };
worker.Start(); worker.Start();
} }
[SupportedOSPlatform("linux")] private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile)
[SupportedOSPlatform("macos")]
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
{ {
using Stream inStream = File.OpenRead(archivePath); // Extract Update
using GZipInputStream gzipStream = new(inStream); updateDialog.MainText.Text = "Extracting Update...";
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII); updateDialog.ProgressBar.Value = 0;
if (OperatingSystem.IsLinux())
{
using Stream inStream = File.OpenRead(updateFile);
using Stream gzipStream = new GZipInputStream(inStream);
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
updateDialog.ProgressBar.MaxValue = inStream.Length;
await Task.Run(() =>
{
TarEntry tarEntry; TarEntry tarEntry;
while ((tarEntry = tarStream.GetNextEntry()) is not null) if (!OperatingSystem.IsWindows())
{
while ((tarEntry = tarStream.GetNextEntry()) != null)
{ {
if (tarEntry.IsDirectory) if (tarEntry.IsDirectory)
{ {
continue; continue;
} }
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name); string outPath = Path.Combine(_updateDir, tarEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath)); Directory.CreateDirectory(Path.GetDirectoryName(outPath));
@ -525,67 +415,49 @@ namespace Ryujinx.Modules
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode); File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc)); File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() => TarEntry entry = tarEntry;
{
if (tarEntry is null)
{
return;
}
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal); Application.Invoke(delegate
{
updateDialog.ProgressBar.Value += entry.Size;
}); });
} }
} }
});
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath) updateDialog.ProgressBar.Value = inStream.Length;
}
else
{ {
using Stream inStream = File.OpenRead(archivePath); using Stream inStream = File.OpenRead(updateFile);
using ZipFile zipFile = new(inStream); using ZipFile zipFile = new(inStream);
updateDialog.ProgressBar.MaxValue = zipFile.Count;
double count = 0; await Task.Run(() =>
{
foreach (ZipEntry zipEntry in zipFile) foreach (ZipEntry zipEntry in zipFile)
{ {
count++;
if (zipEntry.IsDirectory) if (zipEntry.IsDirectory)
{ {
continue; continue;
} }
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name); string outPath = Path.Combine(_updateDir, zipEntry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(outPath)); Directory.CreateDirectory(Path.GetDirectoryName(outPath));
using Stream zipStream = zipFile.GetInputStream(zipEntry); using Stream zipStream = zipFile.GetInputStream(zipEntry);
using FileStream outStream = File.OpenWrite(outPath); using FileStream outStream = File.OpenWrite(outPath);
zipStream.CopyTo(outStream); zipStream.CopyTo(outStream);
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc)); File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
Dispatcher.UIThread.Post(() => Application.Invoke(delegate
{ {
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value++;
}); });
} }
} });
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
{
// Extract Update
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
}
else if (OperatingSystem.IsWindows())
{
ExtractZipFile(taskDialog, updateFile, _updateDir);
}
else
{
throw new NotSupportedException();
} }
// Delete downloaded zip // Delete downloaded zip
@ -593,46 +465,49 @@ namespace Ryujinx.Modules
List<string> allFiles = EnumerateFilesToDelete().ToList(); List<string> allFiles = EnumerateFilesToDelete().ToList();
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming]; updateDialog.MainText.Text = "Renaming Old Files...";
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = 0;
updateDialog.ProgressBar.MaxValue = allFiles.Count;
// NOTE: On macOS, replacement is delayed to the restart phase.
if (!OperatingSystem.IsMacOS())
{
// Replace old files // Replace old files
double count = 0; await Task.Run(() =>
{
foreach (string file in allFiles) foreach (string file in allFiles)
{ {
count++;
try try
{ {
File.Move(file, file + ".ryuold"); File.Move(file, file + ".ryuold");
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value++;
}); });
} }
catch catch
{ {
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); Logger.Warning?.Print(LogClass.Application, "Updater was unable to rename file: " + file);
} }
} }
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; updateDialog.MainText.Text = "Adding New Files...";
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); updateDialog.ProgressBar.Value = 0;
updateDialog.ProgressBar.MaxValue = Directory.GetFiles(_updatePublishDir, "*", SearchOption.AllDirectories).Length;
}); });
MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); MoveAllFilesOver(_updatePublishDir, _homeDir, updateDialog);
});
Directory.Delete(_updateDir, true); Directory.Delete(_updateDir, true);
}
_updateSuccessful = true; updateDialog.MainText.Text = "Update Complete!";
updateDialog.SecondaryText.Text = "Do you want to restart Ryujinx now?";
updateDialog.Modal = true;
taskDialog.Hide(); updateDialog.ProgressBar.Hide();
updateDialog.YesButton.Show();
updateDialog.NoButton.Show();
} }
public static bool CanUpdate(bool showWarnings) public static bool CanUpdate(bool showWarnings)
@ -642,11 +517,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
);
} }
return false; return false;
@ -656,11 +527,7 @@ namespace Ryujinx.Modules
{ {
if (showWarnings) if (showWarnings)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("You cannot update a Dirty build of Ryujinx!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
} }
return false; return false;
@ -672,19 +539,11 @@ namespace Ryujinx.Modules
{ {
if (ReleaseInformation.IsFlatHubBuild) if (ReleaseInformation.IsFlatHubBuild)
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
);
} }
else else
{ {
Dispatcher.UIThread.InvokeAsync(() => GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
);
} }
} }
@ -698,7 +557,7 @@ namespace Ryujinx.Modules
var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir. var files = Directory.EnumerateFiles(_homeDir); // All files directly in base dir.
// Determine and exclude user files only when the updater is running, not when cleaning old files // Determine and exclude user files only when the updater is running, not when cleaning old files
if (_running && !OperatingSystem.IsMacOS()) if (Running)
{ {
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list. // Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName); var oldFiles = Directory.EnumerateFiles(_homeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
@ -724,9 +583,8 @@ namespace Ryujinx.Modules
return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); return files.Where(f => !new FileInfo(f).Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System));
} }
private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog) private static void MoveAllFilesOver(string root, string dest, UpdateDialog dialog)
{ {
int total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
foreach (string directory in Directory.GetDirectories(root)) foreach (string directory in Directory.GetDirectories(root))
{ {
string dirName = Path.GetFileName(directory); string dirName = Path.GetFileName(directory);
@ -736,29 +594,29 @@ namespace Ryujinx.Modules
Directory.CreateDirectory(Path.Combine(dest, dirName)); Directory.CreateDirectory(Path.Combine(dest, dirName));
} }
MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog); MoveAllFilesOver(directory, Path.Combine(dest, dirName), dialog);
} }
double count = 0;
foreach (string file in Directory.GetFiles(root)) foreach (string file in Directory.GetFiles(root))
{ {
count++;
File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true); File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
Dispatcher.UIThread.InvokeAsync(() => Application.Invoke(delegate
{ {
taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal); dialog.ProgressBar.Value++;
}); });
} }
} }
public static void CleanupUpdate() public static void CleanupUpdate()
{ {
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories)) foreach (string file in EnumerateFilesToDelete())
{
if (Path.GetExtension(file).EndsWith(".ryuold"))
{ {
File.Delete(file); File.Delete(file);
} }
} }
} }
} }
}

378
src/Ryujinx.Gtk3/Program.cs Normal file
View File

@ -0,0 +1,378 @@
using Gtk;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.UI;
using Ryujinx.UI.Common;
using Ryujinx.UI.Common.Configuration;
using Ryujinx.UI.Common.Helper;
using Ryujinx.UI.Common.SystemInfo;
using Ryujinx.UI.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Ryujinx
{
partial class Program
{
public static double WindowScaleFactor { get; private set; }
public static string Version { get; private set; }
public static string ConfigurationPath { get; set; }
public static string CommandLineProfile { get; set; }
private const string X11LibraryName = "libX11";
[LibraryImport(X11LibraryName)]
private static partial int XInitThreads();
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(IntPtr hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
[LibraryImport("libc", SetLastError = true)]
private static partial int setenv([MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value, int overwrite);
private const uint MbIconWarning = 0x30;
static Program()
{
if (OperatingSystem.IsLinux())
{
NativeLibrary.SetDllImportResolver(typeof(Program).Assembly, (name, assembly, path) =>
{
if (name != X11LibraryName)
{
return IntPtr.Zero;
}
if (!NativeLibrary.TryLoad("libX11.so.6", assembly, path, out IntPtr result))
{
if (!NativeLibrary.TryLoad("libX11.so", assembly, path, out result))
{
return IntPtr.Zero;
}
}
return result;
});
}
}
static void Main(string[] args)
{
Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MbIconWarning);
}
// Parse arguments
CommandLineState.ParseArguments(args);
// Hook unhandled exception and process exit events.
GLib.ExceptionManager.UnhandledException += (GLib.UnhandledExceptionArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) => ProcessUnhandledException(e.ExceptionObject as Exception, e.IsTerminating);
AppDomain.CurrentDomain.ProcessExit += (object sender, EventArgs e) => Exit();
// Make process DPI aware for proper window sizing on high-res screens.
ForceDpiAware.Windows();
WindowScaleFactor = ForceDpiAware.GetWindowScaleFactor();
// Delete backup files after updating.
Task.Run(Updater.CleanupUpdate);
Console.Title = $"Ryujinx Console {Version}";
// NOTE: GTK3 doesn't init X11 in a multi threaded way.
// This ends up causing race condition and abort of XCB when a context is created by SPB (even if SPB do call XInitThreads).
if (OperatingSystem.IsLinux())
{
if (XInitThreads() == 0)
{
throw new NotSupportedException("Failed to initialize multi-threading support.");
}
Environment.SetEnvironmentVariable("GDK_BACKEND", "x11");
setenv("GDK_BACKEND", "x11", 1);
}
if (OperatingSystem.IsMacOS())
{
string baseDirectory = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
string resourcesDataDir;
if (Path.GetFileName(baseDirectory) == "MacOS")
{
resourcesDataDir = Path.Combine(Directory.GetParent(baseDirectory).FullName, "Resources");
}
else
{
resourcesDataDir = baseDirectory;
}
static void SetEnvironmentVariableNoCaching(string key, string value)
{
int res = setenv(key, value, 1);
Debug.Assert(res != -1);
}
// On macOS, GTK3 needs XDG_DATA_DIRS to be set, otherwise it will try searching for "gschemas.compiled" in system directories.
SetEnvironmentVariableNoCaching("XDG_DATA_DIRS", Path.Combine(resourcesDataDir, "share"));
// On macOS, GTK3 needs GDK_PIXBUF_MODULE_FILE to be set, otherwise it will try searching for "loaders.cache" in system directories.
SetEnvironmentVariableNoCaching("GDK_PIXBUF_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gdk-pixbuf-2.0", "2.10.0", "loaders.cache"));
SetEnvironmentVariableNoCaching("GTK_IM_MODULE_FILE", Path.Combine(resourcesDataDir, "lib", "gtk-3.0", "3.0.0", "immodules.cache"));
}
string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("Path", $"{Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin")};{systemPath}");
// Setup base data directory.
AppDataManager.Initialize(CommandLineState.BaseDirPathArg);
// Initialize the configuration.
ConfigurationState.Initialize();
// Initialize the logger system.
LoggerModule.Initialize();
// Initialize Discord integration.
DiscordIntegrationModule.Initialize();
// Initialize SDL2 driver
SDL2Driver.MainThreadDispatcher = action =>
{
Application.Invoke(delegate
{
action();
});
};
// Sets ImageSharp Jpeg Encoder Quality.
SixLabors.ImageSharp.Configuration.Default.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder()
{
Quality = 100,
});
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
ConfigurationPath = File.Exists(localConfigurationPath)
? localConfigurationPath
: File.Exists(appDataConfigurationPath)
? appDataConfigurationPath
: null;
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
ConfigurationPath = appDataConfigurationPath;
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
// Check if graphics backend was overridden.
if (CommandLineState.OverrideGraphicsBackend != null)
{
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
}
}
// Check if HideCursor was overridden.
if (CommandLineState.OverrideHideCursor is not null)
{
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
{
"never" => HideCursorMode.Never,
"onidle" => HideCursorMode.OnIdle,
"always" => HideCursorMode.Always,
_ => ConfigurationState.Instance.HideCursor.Value,
};
}
// Check if docked mode was overridden.
if (CommandLineState.OverrideDockedMode.HasValue)
{
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
}
// Logging system information.
PrintSystemInfo();
// Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading;
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
// Initialize Gtk.
Application.Init();
// Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys"));
bool hasCommonProdKeys = AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"));
if (!hasSystemProdKeys && !hasCommonProdKeys)
{
UserErrorDialog.CreateUserErrorDialog(UserError.NoKeys);
}
// Show the main window UI.
MainWindow mainWindow = new();
mainWindow.Show();
if (OperatingSystem.IsLinux())
{
int currentVmMaxMapCount = LinuxHelper.VmMaxMapCount;
if (LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
{
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({currentVmMaxMapCount})");
if (LinuxHelper.PkExecPath is not null)
{
var buttonTexts = new Dictionary<int, string>()
{
{ 0, "Yes, until the next restart" },
{ 1, "Yes, permanently" },
{ 2, "No" },
};
ResponseType response = GtkDialog.CreateCustomDialog(
"Ryujinx - Low limit for memory mappings detected",
$"Would you like to increase the value of vm.max_map_count to {LinuxHelper.RecommendedVmMaxMapCount}?",
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.",
buttonTexts,
MessageType.Question);
int rc;
switch ((int)response)
{
case 0:
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
}
break;
case 1:
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
if (rc == 0)
{
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
}
else
{
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
}
break;
}
}
else
{
GtkDialog.CreateWarningDialog(
"Max amount of memory mappings is lower than recommended.",
$"The current value of vm.max_map_count ({currentVmMaxMapCount}) is lower than {LinuxHelper.RecommendedVmMaxMapCount}." +
"Some games might try to create more memory mappings than currently allowed. " +
"Ryujinx will crash as soon as this limit gets exceeded.\n\n" +
"You might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.");
}
}
}
if (CommandLineState.LaunchPathArg != null)
{
mainWindow.RunApplication(CommandLineState.LaunchPathArg, CommandLineState.StartFullscreenArg);
}
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
{
Updater.BeginParse(mainWindow, false).ContinueWith(task =>
{
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
}, TaskContinuationOptions.OnlyOnFaulted);
}
Application.Run();
}
private static void PrintSystemInfo()
{
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels();
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: Custom Path {AppDataManager.BaseDirPath}");
}
else
{
Logger.Notice.Print(LogClass.Application, $"Launch Mode: {AppDataManager.Mode}");
}
}
private static void ProcessUnhandledException(Exception ex, bool isTerminating)
{
string message = $"Unhandled exception caught: {ex}";
Logger.Error?.PrintMsg(LogClass.Application, message);
if (Logger.Error == null)
{
Logger.Notice.PrintMsg(LogClass.Application, message);
}
if (isTerminating)
{
Exit();
}
}
public static void Exit()
{
DiscordIntegrationModule.Exit();
Logger.Shutdown();
}
}
}

View File

@ -0,0 +1,104 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
<!-- As we already provide GTK3 on Windows via GtkSharp.Dependencies this is redundant. -->
<SkipGtkInstall>true</SkipGtkInstall>
<TieredPGO>true</TieredPGO>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
<PublishSingleFile>true</PublishSingleFile>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ryujinx.GtkSharp" />
<PackageReference Include="GtkSharp.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="GtkSharp.Dependencies.osx" Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="OpenTK.Core" />
<PackageReference Include="OpenTK.Graphics" />
<PackageReference Include="SPB" />
<PackageReference Include="SharpZipLib" />
<PackageReference Include="SixLabors.ImageSharp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Input\Ryujinx.Input.csproj" />
<ProjectReference Include="..\Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
<ProjectReference Include="..\Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Ryujinx.Graphics.Gpu\Ryujinx.Graphics.Gpu.csproj" />
<ProjectReference Include="..\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>alsoft.ini</TargetPath>
</Content>
<Content Include="..\..\distribution\legal\THIRDPARTY.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>THIRDPARTY.md</TargetPath>
</Content>
<Content Include="..\..\LICENSE.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>LICENSE.txt</TargetPath>
</Content>
</ItemGroup>
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
<Content Include="..\..\distribution\linux\Ryujinx.sh">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>mime\Ryujinx.xml</TargetPath>
</Content>
</ItemGroup>
<!-- Due to .net core 3.1 embedded resource loading -->
<PropertyGroup>
<EmbeddedResourceUseDependentUponConvention>false</EmbeddedResourceUseDependentUponConvention>
<ApplicationIcon>Ryujinx.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="UI\MainWindow.glade" />
<None Remove="UI\Widgets\ProfileDialog.glade" />
<None Remove="UI\Windows\CheatWindow.glade" />
<None Remove="UI\Windows\ControllerWindow.glade" />
<None Remove="UI\Windows\DlcWindow.glade" />
<None Remove="UI\Windows\SettingsWindow.glade" />
<None Remove="UI\Windows\TitleUpdateWindow.glade" />
<None Remove="Modules\Updater\UpdateDialog.glade" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\MainWindow.glade" />
<EmbeddedResource Include="UI\Widgets\ProfileDialog.glade" />
<EmbeddedResource Include="UI\Windows\CheatWindow.glade" />
<EmbeddedResource Include="UI\Windows\ControllerWindow.glade" />
<EmbeddedResource Include="UI\Windows\DlcWindow.glade" />
<EmbeddedResource Include="UI\Windows\SettingsWindow.glade" />
<EmbeddedResource Include="UI\Windows\TitleUpdateWindow.glade" />
<EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
</ItemGroup>
</Project>

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@ -8,7 +8,7 @@ namespace Ryujinx.UI.Applet
{ {
public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null) public ErrorAppletDialog(Window parentWindow, DialogFlags dialogFlags, MessageType messageType, string[] buttons) : base(parentWindow, dialogFlags, messageType, ButtonsType.None, null)
{ {
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png");
int responseId = 0; int responseId = 0;

View File

@ -143,7 +143,7 @@ namespace Ryujinx.UI
#pragma warning restore CS0649, IDE0044, CS0169, IDE0051 #pragma warning restore CS0649, IDE0044, CS0169, IDE0051
public MainWindow() : this(new Builder("Ryujinx.UI.MainWindow.glade")) { } public MainWindow() : this(new Builder("Ryujinx.Gtk3.UI.MainWindow.glade")) { }
private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin")) private MainWindow(Builder builder) : base(builder.GetRawOwnedObject("_mainWin"))
{ {

View File

@ -189,7 +189,7 @@ namespace Ryujinx.UI.Widgets
_dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null) _dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
{ {
Title = "Ryujinx - NCA Section Extractor", Title = "Ryujinx - NCA Section Extractor",
Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"), Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.Gtk3.UI.Common.Resources.Logo_Ryujinx.png"),
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...", SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}...",
WindowPosition = WindowPosition.Center, WindowPosition = WindowPosition.Center,
}; };

View File

@ -15,7 +15,7 @@ namespace Ryujinx.UI.Widgets
[GUI] Label _errorMessage; [GUI] Label _errorMessage;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public ProfileDialog() : this(new Builder("Ryujinx.UI.Widgets.ProfileDialog.glade")) { } public ProfileDialog() : this(new Builder("Ryujinx.Gtk3.UI.Widgets.ProfileDialog.glade")) { }
private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog")) private ProfileDialog(Builder builder) : base(builder.GetRawOwnedObject("_profileDialog"))
{ {

View File

@ -22,7 +22,7 @@ namespace Ryujinx.UI.Windows
[GUI] Button _saveButton; [GUI] Button _saveButton;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { } public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Gtk3.UI.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { }
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow")) private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
{ {

View File

@ -117,7 +117,7 @@ namespace Ryujinx.UI.Windows
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.UI.Windows.ControllerWindow.glade"), controllerId) { } public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { }
private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin"))
{ {

View File

@ -32,7 +32,7 @@ namespace Ryujinx.UI.Windows
[GUI] TreeSelection _dlcTreeSelection; [GUI] TreeSelection _dlcTreeSelection;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { } public DlcWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.DlcWindow.glade"), virtualFileSystem, titleId, titleName) { }
private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow")) private DlcWindow(Builder builder, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_dlcWindow"))
{ {

View File

@ -120,7 +120,7 @@ namespace Ryujinx.UI.Windows
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { } public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin")) private SettingsWindow(MainWindow parent, Builder builder, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : base(builder.GetRawOwnedObject("_settingsWin"))
{ {

View File

@ -38,7 +38,7 @@ namespace Ryujinx.UI.Windows
[GUI] RadioButton _noUpdateRadioButton; [GUI] RadioButton _noUpdateRadioButton;
#pragma warning restore CS0649, IDE0044 #pragma warning restore CS0649, IDE0044
public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { } public TitleUpdateWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : this(new Builder("Ryujinx.Gtk3.UI.Windows.TitleUpdateWindow.glade"), parent, virtualFileSystem, titleId, titleName) { }
private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow")) private TitleUpdateWindow(Builder builder, MainWindow parent, VirtualFileSystem virtualFileSystem, string titleId, string titleName) : base(builder.GetRawOwnedObject("_titleUpdateWindow"))
{ {

View File

@ -4,12 +4,6 @@ using LibHac.Fs;
using LibHac.Fs.Shim; using LibHac.Fs.Shim;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using Ryujinx.Audio;
using Ryujinx.Audio.Input;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Output;
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel;
@ -20,7 +14,6 @@ using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy; using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using Ryujinx.HLE.HOS.Services.Caps; using Ryujinx.HLE.HOS.Services.Caps;
using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
@ -61,11 +54,6 @@ namespace Ryujinx.HLE.HOS
internal ITickSource TickSource { get; } internal ITickSource TickSource { get; }
internal SurfaceFlinger SurfaceFlinger { get; private set; } internal SurfaceFlinger SurfaceFlinger { get; private set; }
internal AudioManager AudioManager { get; private set; }
internal AudioOutputManager AudioOutputManager { get; private set; }
internal AudioInputManager AudioInputManager { get; private set; }
internal AudioRendererManager AudioRendererManager { get; private set; }
internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; }
public SystemStateMgr State { get; private set; } public SystemStateMgr State { get; private set; }
@ -79,8 +67,6 @@ namespace Ryujinx.HLE.HOS
internal ServerBase SmServer { get; private set; } internal ServerBase SmServer { get; private set; }
internal ServerBase BsdServer { get; private set; } internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
internal ServerBase AudOutServer { get; private set; }
internal ServerBase FsServer { get; private set; } internal ServerBase FsServer { get; private set; }
internal ServerBase HidServer { get; private set; } internal ServerBase HidServer { get; private set; }
internal ServerBase NvDrvServer { get; private set; } internal ServerBase NvDrvServer { get; private set; }
@ -248,60 +234,9 @@ namespace Ryujinx.HLE.HOS
HostSyncpoint = new NvHostSyncpt(device); HostSyncpoint = new NvHostSyncpt(device);
SurfaceFlinger = new SurfaceFlinger(device); SurfaceFlinger = new SurfaceFlinger(device);
InitializeAudioRenderer(TickSource);
InitializeServices();
} }
private void InitializeAudioRenderer(ITickSource tickSource) public void InitializeServices()
{
AudioManager = new AudioManager();
AudioOutputManager = new AudioOutputManager();
AudioInputManager = new AudioInputManager();
AudioRendererManager = new AudioRendererManager(tickSource);
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver);
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
{
KEvent registerBufferEvent = new(KernelContext);
audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
}
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
{
KEvent registerBufferEvent = new(KernelContext);
audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
}
AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents);
IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
for (int i = 0; i < systemEvents.Length; i++)
{
KEvent systemEvent = new(KernelContext);
systemEvents[i] = new AudioKernelEvent(systemEvent);
}
AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver);
AudioManager.Start();
}
private void InitializeServices()
{ {
SmRegistry = new SmRegistry(); SmRegistry = new SmRegistry();
SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry)); SmServer = new ServerBase(KernelContext, "SmServer", () => new IUserInterface(KernelContext, SmRegistry));
@ -311,8 +246,6 @@ namespace Ryujinx.HLE.HOS
SmServer.InitDone.WaitOne(); SmServer.InitDone.WaitOne();
BsdServer = new ServerBase(KernelContext, "BsdServer"); BsdServer = new ServerBase(KernelContext, "BsdServer");
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
FsServer = new ServerBase(KernelContext, "FsServer"); FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer"); HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer"); NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
@ -330,7 +263,13 @@ namespace Ryujinx.HLE.HOS
HorizonFsClient fsClient = new(this); HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable(); ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager)); var services = ServiceTable.GetServices(new HorizonOptions
(Device.Configuration.IgnoreMissingServices,
LibHacHorizonManager.BcatClient,
fsClient,
AccountManager,
Device.AudioDeviceDriver,
TickSource));
foreach (var service in services) foreach (var service in services)
{ {
@ -385,17 +324,6 @@ namespace Ryujinx.HLE.HOS
} }
} }
public void SetVolume(float volume)
{
AudioOutputManager.SetVolume(volume);
AudioRendererManager.SetVolume(volume);
}
public float GetVolume()
{
return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume();
}
public void ReturnFocus() public void ReturnFocus()
{ {
AppletState.SetFocus(true); AppletState.SetFocus(true);
@ -459,11 +387,7 @@ namespace Ryujinx.HLE.HOS
// "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop. // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
if (IsPaused) if (IsPaused)
{ {
AudioManager.StopUpdates();
TogglePauseEmulation(false); TogglePauseEmulation(false);
AudioRendererManager.StopSendingCommands();
} }
KProcess terminationProcess = new(KernelContext); KProcess terminationProcess = new(KernelContext);
@ -514,12 +438,6 @@ namespace Ryujinx.HLE.HOS
// This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade.
INvDrvServices.Destroy(); INvDrvServices.Destroy();
AudioManager.Dispose();
AudioOutputManager.Dispose();
AudioInputManager.Dispose();
AudioRendererManager.Dispose();
if (LibHacHorizonManager.ApplicationClient != null) if (LibHacHorizonManager.ApplicationClient != null)
{ {
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure(); LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();

View File

@ -0,0 +1,25 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
readonly struct ExternalEvent : IExternalEvent
{
private readonly KWritableEvent _writableEvent;
public ExternalEvent(KWritableEvent writableEvent)
{
_writableEvent = writableEvent;
}
public readonly void Signal()
{
_writableEvent.Signal();
}
public readonly void Clear()
{
_writableEvent.Clear();
}
}
}

View File

@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System; using System;
using System.Buffers; using System.Buffers;
using System.Threading; using System.Threading;
@ -3142,6 +3143,37 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
} }
#pragma warning restore CA1822 #pragma warning restore CA1822
// Not actual syscalls, used by HLE services and such.
public IExternalEvent GetExternalEvent(int handle)
{
KWritableEvent writableEvent = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KWritableEvent>(handle);
if (writableEvent == null)
{
return null;
}
return new ExternalEvent(writableEvent);
}
public IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle)
{
return KernelStatic.GetCurrentProcess().HandleTable.GetKProcess(handle).CpuMemory;
}
public ulong GetTransferMemoryAddress(int handle)
{
KTransferMemory transferMemory = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KTransferMemory>(handle);
if (transferMemory == null)
{
return 0;
}
return transferMemory.Address;
}
private static bool IsPointingInsideKernel(ulong address) private static bool IsPointingInsideKernel(ulong address)
{ {
return (address + 0x1000000000) < 0xffffff000; return (address + 0x1000000000) < 0xffffff000;

View File

@ -1,108 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.Audio.Integration;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
class AudioIn : IAudioIn
{
private readonly AudioInputSystem _system;
private readonly uint _processHandle;
private readonly KernelContext _kernelContext;
public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle)
{
_system = system;
_kernelContext = kernelContext;
_processHandle = processHandle;
}
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
{
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
}
public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle)
{
return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle);
}
public bool ContainsBuffer(ulong bufferTag)
{
return _system.ContainsBuffer(bufferTag);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_system.Dispose();
_kernelContext.Syscall.CloseHandle((int)_processHandle);
}
}
public bool FlushBuffers()
{
return _system.FlushBuffers();
}
public uint GetBufferCount()
{
return _system.GetBufferCount();
}
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
{
return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount);
}
public AudioDeviceState GetState()
{
return _system.GetState();
}
public float GetVolume()
{
return _system.GetVolume();
}
public KEvent RegisterBufferEvent()
{
IWritableEvent outEvent = _system.RegisterBufferEvent();
if (outEvent is AudioKernelEvent kernelEvent)
{
return kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
public void SetVolume(float volume)
{
_system.SetVolume(volume);
}
public ResultCode Start()
{
return (ResultCode)_system.Start();
}
public ResultCode Stop()
{
return (ResultCode)_system.Stop();
}
}
}

View File

@ -1,200 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
class AudioInServer : DisposableIpcService
{
private readonly IAudioIn _impl;
public AudioInServer(IAudioIn impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetAudioInState() -> u32 state
public ResultCode GetAudioInState(ServiceCtx context)
{
context.ResponseData.Write((uint)_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(1)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(2)]
// Stop()
public ResultCode StopAudioIn(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(3)]
// AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>)
public ResultCode AppendAudioInBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(4)]
// RegisterBufferEvent() -> handle<copy>
public ResultCode RegisterBufferEvent(ServiceCtx context)
{
KEvent bufferEvent = _impl.RegisterBufferEvent();
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags)
public ResultCode GetReleasedAudioInBuffers(ServiceCtx context)
{
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
using WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(6)]
// ContainsAudioInBuffer(u64 tag) -> b8
public ResultCode ContainsAudioInBuffer(ServiceCtx context)
{
ulong bufferTag = context.RequestData.ReadUInt64();
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>)
public ResultCode AppendUacInBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
}
[CommandCmif(8)] // 3.0.0+
// AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>)
public ResultCode AppendAudioInBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(9)] // 3.0.0+
// GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x22();
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(10)] // 3.0.0+
// AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>)
public ResultCode AppendUacInBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
}
[CommandCmif(11)] // 4.0.0+
// GetAudioInBufferCount() -> u32
public ResultCode GetAudioInBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetBufferCount());
return ResultCode.Success;
}
[CommandCmif(12)] // 4.0.0+
// SetAudioInVolume(s32)
public ResultCode SetAudioInVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
_impl.SetVolume(volume);
return ResultCode.Success;
}
[CommandCmif(13)] // 4.0.0+
// GetAudioInVolume() -> s32
public ResultCode GetAudioInVolume(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetVolume());
return ResultCode.Success;
}
[CommandCmif(14)] // 6.0.0+
// FlushAudioInBuffers() -> b8
public ResultCode FlushAudioInBuffers(ServiceCtx context)
{
context.ResponseData.Write(_impl.FlushBuffers());
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

View File

@ -1,34 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
interface IAudioIn : IDisposable
{
AudioDeviceState GetState();
ResultCode Start();
ResultCode Stop();
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
// NOTE: This is broken by design... not quite sure what it's used for (if anything in production).
ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle);
KEvent RegisterBufferEvent();
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
bool ContainsBuffer(ulong bufferTag);
uint GetBufferCount();
bool FlushBuffers();
void SetVolume(float volume);
float GetVolume();
}
}

View File

@ -1,40 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioInManager : IAudioInManager
{
private readonly AudioInManagerImpl _impl;
public AudioInManager(AudioInManagerImpl impl)
{
_impl = impl;
}
public string[] ListAudioIns(bool filtered)
{
return _impl.ListAudioIns(filtered);
}
public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle)
{
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle);
if (result == ResultCode.Success)
{
obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle);
}
else
{
obj = null;
}
return result;
}
}
}

View File

@ -1,243 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audin:u")]
class AudioInManagerServer : IpcService
{
private const int AudioInNameSize = 0x100;
private readonly IAudioInManager _impl;
public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { }
public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioIns() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioIns(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(false);
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
public ResultCode OpenAudioIn(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
[CommandCmif(2)] // 3.0.0+
// ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioInsAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(false);
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(3)] // 3.0.0+
// OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 0x22> name)
public ResultCode OpenAudioInAuto(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
[CommandCmif(4)] // 3.0.0+
// ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(true);
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(5)] // 5.0.0+
// OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context)
{
// NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
#pragma warning disable IDE0059 // Remove unnecessary value assignment
bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1;
#pragma warning restore IDE0059
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0051, IDE0059 // Remove unused private member
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0051, IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
}
}

View File

@ -1,108 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Output;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
class AudioOut : IAudioOut
{
private readonly AudioOutputSystem _system;
private readonly uint _processHandle;
private readonly KernelContext _kernelContext;
public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle)
{
_system = system;
_kernelContext = kernelContext;
_processHandle = processHandle;
}
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
{
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
}
public bool ContainsBuffer(ulong bufferTag)
{
return _system.ContainsBuffer(bufferTag);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_system.Dispose();
_kernelContext.Syscall.CloseHandle((int)_processHandle);
}
}
public bool FlushBuffers()
{
return _system.FlushBuffers();
}
public uint GetBufferCount()
{
return _system.GetBufferCount();
}
public ulong GetPlayedSampleCount()
{
return _system.GetPlayedSampleCount();
}
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
{
return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount);
}
public AudioDeviceState GetState()
{
return _system.GetState();
}
public float GetVolume()
{
return _system.GetVolume();
}
public KEvent RegisterBufferEvent()
{
IWritableEvent outEvent = _system.RegisterBufferEvent();
if (outEvent is AudioKernelEvent kernelEvent)
{
return kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
public void SetVolume(float volume)
{
_system.SetVolume(volume);
}
public ResultCode Start()
{
return (ResultCode)_system.Start();
}
public ResultCode Stop()
{
return (ResultCode)_system.Stop();
}
}
}

View File

@ -1,181 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
class AudioOutServer : DisposableIpcService
{
private readonly IAudioOut _impl;
public AudioOutServer(IAudioOut impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetAudioOutState() -> u32 state
public ResultCode GetAudioOutState(ServiceCtx context)
{
context.ResponseData.Write((uint)_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(1)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(2)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(3)]
// AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer)
public ResultCode AppendAudioOutBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(4)]
// RegisterBufferEvent() -> handle<copy>
public ResultCode RegisterBufferEvent(ServiceCtx context)
{
KEvent bufferEvent = _impl.RegisterBufferEvent();
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags)
public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context)
{
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(6)]
// ContainsAudioOutBuffer(u64 tag) -> b8
public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
{
ulong bufferTag = context.RequestData.ReadUInt64();
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(8)] // 3.0.0+
// GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x22();
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(9)] // 4.0.0+
// GetAudioOutBufferCount() -> u32
public ResultCode GetAudioOutBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetBufferCount());
return ResultCode.Success;
}
[CommandCmif(10)] // 4.0.0+
// GetAudioOutPlayedSampleCount() -> u64
public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetPlayedSampleCount());
return ResultCode.Success;
}
[CommandCmif(11)] // 4.0.0+
// FlushAudioOutBuffers() -> b8
public ResultCode FlushAudioOutBuffers(ServiceCtx context)
{
context.ResponseData.Write(_impl.FlushBuffers());
return ResultCode.Success;
}
[CommandCmif(12)] // 6.0.0+
// SetAudioOutVolume(s32)
public ResultCode SetAudioOutVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
_impl.SetVolume(volume);
return ResultCode.Success;
}
[CommandCmif(13)] // 6.0.0+
// GetAudioOutVolume() -> s32
public ResultCode GetAudioOutVolume(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetVolume());
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

View File

@ -1,33 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
interface IAudioOut : IDisposable
{
AudioDeviceState GetState();
ResultCode Start();
ResultCode Stop();
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
KEvent RegisterBufferEvent();
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
bool ContainsBuffer(ulong bufferTag);
uint GetBufferCount();
ulong GetPlayedSampleCount();
bool FlushBuffers();
void SetVolume(float volume);
float GetVolume();
}
}

View File

@ -1,40 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Output;
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioOutManager : IAudioOutManager
{
private readonly AudioOutManagerImpl _impl;
public AudioOutManager(AudioOutManagerImpl impl)
{
_impl = impl;
}
public string[] ListAudioOuts()
{
return _impl.ListAudioOuts();
}
public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume)
{
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume);
if (result == ResultCode.Success)
{
obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle);
}
else
{
obj = null;
}
return result;
}
}
}

View File

@ -1,166 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audout:u")]
class AudioOutManagerServer : IpcService
{
private const int AudioOutNameSize = 0x100;
private readonly IAudioOutManager _impl;
public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { }
public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioOuts() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioOuts(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOuts();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
position += AudioOutNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 5> name_in)
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
public ResultCode OpenAudioOut(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioOutServer(obj));
}
return resultCode;
}
[CommandCmif(2)] // 3.0.0+
// ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioOutsAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOuts();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
position += AudioOutNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(3)] // 3.0.0+
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 0x21> name_in)
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
public ResultCode OpenAudioOutAuto(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioOutServer(obj));
}
return resultCode;
}
}
}

View File

@ -1,174 +0,0 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioDevice : IAudioDevice
{
private readonly VirtualDeviceSession[] _sessions;
#pragma warning disable IDE0052 // Remove unread private member
private readonly ulong _appletResourceId;
private readonly int _revision;
#pragma warning restore IDE0052
private readonly bool _isUsbDeviceSupported;
private readonly VirtualDeviceSessionRegistry _registry;
private readonly KEvent _systemEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
{
_registry = registry;
_appletResourceId = appletResourceId;
_revision = revision;
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(revision);
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
_sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
// TODO: support the 3 different events correctly when we will have hot plugable audio devices.
_systemEvent = new KEvent(context);
_systemEvent.ReadableEvent.Signal();
}
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
{
result = null;
foreach (VirtualDeviceSession session in _sessions)
{
if (session.Device.Name.Equals(name))
{
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
return false;
}
result = session;
return true;
}
}
return false;
}
public string GetActiveAudioDeviceName()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.Name;
}
public uint GetActiveChannelCount()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.ChannelCount;
}
public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name))
{
volume = result.Volume;
}
else
{
volume = 0.0f;
}
return ResultCode.Success;
}
public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
{
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
{
result = _sessions[0];
}
result.Volume = volume;
}
return ResultCode.Success;
}
public string GetActiveAudioOutputDeviceName()
{
return _registry.ActiveDevice.GetOutputDeviceName();
}
public string[] ListAudioDeviceName()
{
int deviceCount = _sessions.Length;
if (!_isUsbDeviceSupported)
{
deviceCount--;
}
string[] result = new string[deviceCount];
int i = 0;
foreach (VirtualDeviceSession session in _sessions)
{
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
continue;
}
result[i] = session.Device.Name;
i++;
}
return result;
}
public string[] ListAudioOutputDeviceName()
{
int deviceCount = _sessions.Length;
string[] result = new string[deviceCount];
for (int i = 0; i < deviceCount; i++)
{
result[i] = _sessions[i].Device.GetOutputDeviceName();
}
return result;
}
public KEvent QueryAudioDeviceInputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceOutputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceSystemEvent()
{
return _systemEvent;
}
}
}

View File

@ -1,320 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioDeviceServer : IpcService
{
private const int AudioDeviceNameSize = 0x100;
private readonly IAudioDevice _impl;
public AudioDeviceServer(IAudioDevice impl)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioDeviceName(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
ulong position = context.Request.SendBuff[0].Position;
ulong size = context.Request.SendBuff[0].Size;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
[CommandCmif(2)]
// GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong size = context.Request.SendBuff[0].Size;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return result;
}
[CommandCmif(3)]
// GetActiveAudioDeviceName() -> buffer<bytes, 6>
public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
{
string name = _impl.GetActiveAudioDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(4)]
// QueryAudioDeviceSystemEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
{
KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetActiveChannelCount() -> u32
public ResultCode GetActiveChannelCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetActiveChannelCount());
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(6)] // 3.0.0+
// ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioDeviceName();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
(ulong position, ulong size) = context.Request.GetBufferType0x21();
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
[CommandCmif(8)] // 3.0.0+
// GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x21();
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return ResultCode.Success;
}
[CommandCmif(10)] // 3.0.0+
// GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
{
string name = _impl.GetActiveAudioDeviceName();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(11)] // 3.0.0+
// QueryAudioDeviceInputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
{
KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(12)] // 3.0.0+
// QueryAudioDeviceOutputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
{
KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(13)] // 13.0.0+
// GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
{
string name = _impl.GetActiveAudioOutputDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(14)] // 13.0.0+
// ListAudioOutputDeviceName() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOutputDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
}
}

View File

@ -1,25 +0,0 @@
using Ryujinx.Audio.Integration;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioKernelEvent : IWritableEvent
{
public KEvent Event { get; }
public AudioKernelEvent(KEvent evnt)
{
Event = evnt;
}
public void Clear()
{
Event.WritableEvent.Clear();
}
public void Signal()
{
Event.WritableEvent.Signal();
}
}
}

View File

@ -1,122 +0,0 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRenderer : IAudioRenderer
{
private readonly AudioRenderSystem _impl;
public AudioRenderer(AudioRenderSystem impl)
{
_impl = impl;
}
public ResultCode ExecuteAudioRendererRendering()
{
return (ResultCode)_impl.ExecuteAudioRendererRendering();
}
public uint GetMixBufferCount()
{
return _impl.GetMixBufferCount();
}
public uint GetRenderingTimeLimit()
{
return _impl.GetRenderingTimeLimit();
}
public uint GetSampleCount()
{
return _impl.GetSampleCount();
}
public uint GetSampleRate()
{
return _impl.GetSampleRate();
}
public int GetState()
{
if (_impl.IsActive())
{
return 0;
}
return 1;
}
public ResultCode QuerySystemEvent(out KEvent systemEvent)
{
ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
if (resultCode == ResultCode.Success)
{
if (outEvent is AudioKernelEvent kernelEvent)
{
systemEvent = kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
else
{
systemEvent = null;
}
return resultCode;
}
public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
{
return (ResultCode)_impl.Update(output, performanceOutput, input);
}
public void SetRenderingTimeLimit(uint percent)
{
_impl.SetRenderingTimeLimitPercent(percent);
}
public ResultCode Start()
{
_impl.Start();
return ResultCode.Success;
}
public ResultCode Stop()
{
_impl.Stop();
return ResultCode.Success;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
}
}
public void SetVoiceDropParameter(float voiceDropParameter)
{
_impl.SetVoiceDropParameter(voiceDropParameter);
}
public float GetVoiceDropParameter()
{
return _impl.GetVoiceDropParameter();
}
}
}

View File

@ -1,215 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRendererServer : DisposableIpcService
{
private readonly IAudioRenderer _impl;
public AudioRendererServer(IAudioRenderer impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleRate());
return ResultCode.Success;
}
[CommandCmif(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleCount());
return ResultCode.Success;
}
[CommandCmif(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetMixBufferCount());
return ResultCode.Success;
}
[CommandCmif(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(4)]
// RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
public ResultCode RequestUpdate(ServiceCtx context)
{
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size;
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.RentCleared(outputSize);
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.RentCleared(performanceOutputSize);
Memory<byte> output = outputOwner.Memory;
Memory<byte> performanceOutput = performanceOutputOwner.Memory;
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
}
return result;
}
[CommandCmif(5)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(6)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(7)]
// QuerySystemEvent() -> handle<copy, event>
public ResultCode QuerySystemEvent(ServiceCtx context)
{
ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
if (result == ResultCode.Success)
{
if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
}
return result;
}
[CommandCmif(8)]
// SetAudioRendererRenderingTimeLimit(u32 limit)
public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = context.RequestData.ReadUInt32();
_impl.SetRenderingTimeLimit(limit);
return ResultCode.Success;
}
[CommandCmif(9)]
// GetAudioRendererRenderingTimeLimit() -> u32 limit
public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = _impl.GetRenderingTimeLimit();
context.ResponseData.Write(limit);
return ResultCode.Success;
}
[CommandCmif(10)] // 3.0.0+
// RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
public ResultCode RequestUpdateAuto(ServiceCtx context)
{
(ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21();
(ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0);
(ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1);
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
Memory<byte> output = new byte[outputSize];
Memory<byte> performanceOutput = new byte[performanceOutputSize];
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
return result;
}
[CommandCmif(11)] // 3.0.0+
// ExecuteAudioRendererRendering()
public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
{
return _impl.ExecuteAudioRendererRendering();
}
[CommandCmif(12)] // 15.0.0+
// SetVoiceDropParameter(f32 voiceDropParameter)
public ResultCode SetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = context.RequestData.ReadSingle();
_impl.SetVoiceDropParameter(voiceDropParameter);
return ResultCode.Success;
}
[CommandCmif(13)] // 15.0.0+
// GetVoiceDropParameter() -> f32 voiceDropParameter
public ResultCode GetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = _impl.GetVoiceDropParameter();
context.ResponseData.Write(voiceDropParameter);
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

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