Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5a900f38c5 | ||
|
a3a63d4394 | ||
|
3924bd1a43 | ||
|
50458b2472 | ||
|
dda0f26067 | ||
|
4e1a60328e | ||
|
2505a1abcd | ||
|
bc4d99a078 | ||
|
ec6cb0abb4 | ||
|
53b5985da6 | ||
|
87f238be60 | ||
|
4d0dbbfae2 | ||
|
08384ee5a8 | ||
|
d4d0a48bfe |
@@ -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'"
|
||||
dotnet_diagnostic.CA1862.severity = none
|
||||
|
||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||
# Disable "mark members as static" rule for services
|
||||
[src/Ryujinx/UI/ViewModels/**.cs]
|
||||
# Disable "mark members as static" rule for ViewModels
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
|
||||
# Disable "mark members as static" rule for ViewModels
|
||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||
# Disable "mark members as static" rule for services
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
||||
[src/Ryujinx.Tests/Cpu/*.cs]
|
||||
|
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -7,7 +7,7 @@ updates:
|
||||
labels:
|
||||
- "infra"
|
||||
reviewers:
|
||||
- marysaka
|
||||
- TSRBerry
|
||||
commit-message:
|
||||
prefix: "ci"
|
||||
|
||||
@@ -19,7 +19,7 @@ updates:
|
||||
labels:
|
||||
- "infra"
|
||||
reviewers:
|
||||
- marysaka
|
||||
- TSRBerry
|
||||
commit-message:
|
||||
prefix: nuget
|
||||
groups:
|
||||
|
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@@ -20,7 +20,7 @@ gpu:
|
||||
|
||||
gui:
|
||||
- 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:
|
||||
- changed-files:
|
||||
|
7
.github/reviewers.yml
vendored
7
.github/reviewers.yml
vendored
@@ -1,31 +1,24 @@
|
||||
audio:
|
||||
- marysaka
|
||||
|
||||
cpu:
|
||||
- gdkchan
|
||||
- riperiperi
|
||||
- marysaka
|
||||
- LDj3SNuD
|
||||
|
||||
gpu:
|
||||
- gdkchan
|
||||
- riperiperi
|
||||
- marysaka
|
||||
|
||||
gui:
|
||||
- Ack77
|
||||
- emmauss
|
||||
- TSRBerry
|
||||
- marysaka
|
||||
|
||||
horizon:
|
||||
- gdkchan
|
||||
- Ack77
|
||||
- marysaka
|
||||
- TSRBerry
|
||||
|
||||
infra:
|
||||
- marysaka
|
||||
- TSRBerry
|
||||
|
||||
default:
|
||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@@ -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
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Ava
|
||||
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
|
||||
- name: Publish Ryujinx.Gtk3
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/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'
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
@@ -92,11 +92,11 @@ jobs:
|
||||
path: publish_sdl2_headless
|
||||
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
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_ava
|
||||
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_gtk
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
build_macos:
|
||||
@@ -140,19 +140,19 @@ jobs:
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
- name: Publish macOS Ryujinx
|
||||
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
|
||||
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"
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_ava/*.tar.gz"
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
|
10
.github/workflows/nightly_pr_comment.yml
vendored
10
.github/workflows/nightly_pr_comment.yml
vendored
@@ -39,24 +39,24 @@ jobs:
|
||||
return core.error(`No artifacts found`);
|
||||
}
|
||||
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_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||
for (const art of artifacts) {
|
||||
if(art.name.includes('Debug')) {
|
||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('ava-ryujinx')) {
|
||||
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('gtk-ryujinx')) {
|
||||
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||
} else {
|
||||
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_debug_artifacts += `\n</details>`;
|
||||
body += hidden_avalonia_artifacts;
|
||||
body += hidden_gtk_artifacts;
|
||||
body += hidden_headless_artifacts;
|
||||
body += hidden_debug_artifacts;
|
||||
|
||||
|
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -44,6 +44,17 @@ jobs:
|
||||
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:
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
@@ -86,14 +97,13 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
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_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
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
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
|
||||
popd
|
||||
|
||||
@@ -102,6 +112,7 @@ jobs:
|
||||
popd
|
||||
|
||||
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
|
||||
popd
|
||||
shell: bash
|
||||
@@ -109,7 +120,7 @@ jobs:
|
||||
- name: Packing Linux builds
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_gtk
|
||||
pushd publish_ava
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
@@ -120,6 +131,7 @@ jobs:
|
||||
popd
|
||||
|
||||
pushd publish_ava
|
||||
mv publish/Ryujinx 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
|
||||
popd
|
||||
@@ -183,10 +195,10 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
- name: Publish macOS Ryujinx
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
||||
|
||||
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||
run: |
|
||||
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
|
||||
|
@@ -42,10 +42,10 @@
|
||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.16.0" />
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="2.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0" />
|
||||
<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.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
|
@@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32228.430
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
||||
EndProject
|
||||
@@ -69,7 +69,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
|
||||
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}"
|
||||
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
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
|
||||
EndProject
|
||||
|
@@ -6,10 +6,6 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Ava"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
|
||||
RYUJINX_BIN="Ryujinx"
|
||||
fi
|
||||
|
@@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
|
||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
|
||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||
|
||||
# Copy executables first
|
||||
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
# Copy executable and nsure executable can be executed
|
||||
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||
|
||||
# Then all libraries
|
||||
|
@@ -22,9 +22,9 @@ EXTRA_ARGS=$8
|
||||
|
||||
if [ "$VERSION" == "1.1.0" ];
|
||||
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
|
||||
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
|
||||
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||
fi
|
||||
|
||||
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 restore
|
||||
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" 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
|
||||
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
|
||||
|
||||
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||
@@ -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"
|
||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||
rm "$RELEASE_TAR_FILE_NAME"
|
||||
|
||||
# Create legacy update package for Avalonia to not left behind old testers.
|
||||
if [ "$VERSION" != "1.1.0" ];
|
||||
then
|
||||
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
echo "Done"
|
@@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
private bool _stillRunning;
|
||||
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()
|
||||
{
|
||||
_device = ALC.OpenDevice("");
|
||||
@@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
Name = "HardwareDeviceDriver.OpenAL",
|
||||
};
|
||||
|
||||
_volume = 1f;
|
||||
|
||||
_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)
|
||||
{
|
||||
@@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
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);
|
||||
|
||||
|
@@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
private bool _isActive;
|
||||
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
||||
private ulong _playedSampleCount;
|
||||
private float _volume;
|
||||
|
||||
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;
|
||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||
@@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
_targetFormat = GetALFormat();
|
||||
_isActive = false;
|
||||
_playedSampleCount = 0;
|
||||
SetVolume(requestedVolume);
|
||||
SetVolume(1f);
|
||||
}
|
||||
|
||||
private ALFormat GetALFormat()
|
||||
@@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
AL.Source(_sourceId, ALSourcef.Gain, volume);
|
||||
}
|
||||
_volume = volume;
|
||||
|
||||
UpdateMasterVolume(_driver.Volume);
|
||||
}
|
||||
|
||||
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()
|
||||
|
@@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
|
||||
private readonly bool _supportSurroundConfiguration;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
// TODO: Add this to SDL2-CS
|
||||
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
||||
#pragma warning disable SYSLIB1054
|
||||
@@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
{
|
||||
_supportSurroundConfiguration = spec.channels >= 6;
|
||||
}
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
@@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
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)
|
||||
{
|
||||
@@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
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);
|
||||
|
||||
|
@@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
private float _volume;
|
||||
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;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
@@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||
_sampleCount = uint.MaxValue;
|
||||
_started = false;
|
||||
_volume = requestedVolume;
|
||||
_volume = 1f;
|
||||
}
|
||||
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
@@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
streamSpan.Clear();
|
||||
|
||||
// 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);
|
||||
|
@@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||
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()
|
||||
{
|
||||
_audioContext = SoundIoContext.Create();
|
||||
@@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
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)
|
||||
{
|
||||
@@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
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);
|
||||
|
||||
|
@@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
private readonly DynamicRingBuffer _ringBuffer;
|
||||
private ulong _playedSampleCount;
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private float _volume;
|
||||
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;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
_volume = 1f;
|
||||
|
||||
SetupOutputStream(requestedVolume);
|
||||
SetupOutputStream(driver.Volume);
|
||||
}
|
||||
|
||||
private void SetupOutputStream(float requestedVolume)
|
||||
@@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _outputStream.Volume;
|
||||
return _volume;
|
||||
}
|
||||
|
||||
public override void PrepareToClose() { }
|
||||
@@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||
|
||||
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()
|
||||
|
@@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => _realDriver.Volume;
|
||||
set => _realDriver.Volume = value;
|
||||
}
|
||||
|
||||
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
||||
{
|
||||
_realDriver = realDevice;
|
||||
@@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
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)
|
||||
{
|
||||
@@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (!_realDriver.SupportsDirection(direction))
|
||||
{
|
||||
if (direction == Direction.Input)
|
||||
@@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||
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)
|
||||
{
|
||||
|
@@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
public static bool IsSupported => true;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
public DummyHardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_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)
|
||||
{
|
||||
@@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
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);
|
||||
|
@@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
|
||||
/// </summary>
|
||||
/// <param name="filtered">If true, filter disconnected devices</param>
|
||||
/// <returns>The list of all audio inputs name</returns>
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public string[] ListAudioIns(bool filtered)
|
||||
{
|
||||
if (filtered)
|
||||
@@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
|
||||
|
||||
return new[] { Constants.DefaultDeviceInputName };
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
|
||||
/// <summary>
|
||||
/// 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="sampleFormat">The sample format to use</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>
|
||||
public ResultCode OpenAudioIn(out string outputDeviceName,
|
||||
out AudioOutputConfiguration outputConfiguration,
|
||||
@@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
|
||||
IVirtualMemoryManager memoryManager,
|
||||
string inputDeviceName,
|
||||
SampleFormat sampleFormat,
|
||||
ref AudioInputConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
uint processHandle)
|
||||
ref AudioInputConfiguration parameter)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
|
@@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
|
||||
|
||||
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;
|
||||
_sampleRate = sampleRate;
|
||||
_currentBufferTag = 0;
|
||||
|
@@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
|
||||
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 GetPauseEvent();
|
||||
|
@@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
|
||||
/// Get the list of all audio outputs name.
|
||||
/// </summary>
|
||||
/// <returns>The list of all audio outputs name</returns>
|
||||
#pragma warning disable CA1822 // Mark member as static
|
||||
public string[] ListAudioOuts()
|
||||
{
|
||||
return new[] { Constants.DefaultDeviceOutputName };
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
|
||||
/// <summary>
|
||||
/// 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="sampleFormat">The sample format to use</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>
|
||||
public ResultCode OpenAudioOut(out string outputDeviceName,
|
||||
out AudioOutputConfiguration outputConfiguration,
|
||||
@@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
|
||||
IVirtualMemoryManager memoryManager,
|
||||
string inputDeviceName,
|
||||
SampleFormat sampleFormat,
|
||||
ref AudioInputConfiguration parameter,
|
||||
ulong appletResourceUserId,
|
||||
uint processHandle,
|
||||
float volume)
|
||||
ref AudioInputConfiguration parameter)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
_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]);
|
||||
|
||||
@@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
|
||||
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()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
@@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
_event = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
// 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.
|
||||
return 2;
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
||||
public void Start(IHardwareDeviceDriver deviceDriver)
|
||||
{
|
||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||
|
||||
@@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
for (int i = 0; i < OutputDevices.Length; i++)
|
||||
{
|
||||
// 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>();
|
||||
@@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
_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()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
@@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||
if (disposing)
|
||||
{
|
||||
_event.Dispose();
|
||||
_mailbox?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// <summary>
|
||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||
/// </summary>
|
||||
private void StartLocked(float volume)
|
||||
private void StartLocked()
|
||||
{
|
||||
_isRunning = true;
|
||||
|
||||
// TODO: virtual device mapping (IAudioDevice)
|
||||
Processor.Start(_deviceDriver, volume);
|
||||
Processor.Start(_deviceDriver);
|
||||
|
||||
_workerThread = new Thread(SendCommands)
|
||||
{
|
||||
@@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||
private void Register(AudioRenderSystem renderer, float volume)
|
||||
private void Register(AudioRenderSystem renderer)
|
||||
{
|
||||
lock (_sessionLock)
|
||||
{
|
||||
@@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
StartLocked(volume);
|
||||
StartLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
ulong appletResourceUserId,
|
||||
ulong workBufferAddress,
|
||||
ulong workBufferSize,
|
||||
uint processHandle,
|
||||
float volume)
|
||||
uint processHandle)
|
||||
{
|
||||
int sessionId = AcquireSessionId();
|
||||
|
||||
@@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
renderer = audioRenderer;
|
||||
|
||||
Register(renderer, volume);
|
||||
Register(renderer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
return result;
|
||||
}
|
||||
|
||||
public float GetVolume()
|
||||
{
|
||||
if (Processor != null)
|
||||
{
|
||||
return Processor.GetVolume();
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
public void SetVolume(float volume)
|
||||
{
|
||||
Processor?.SetVolume(volume);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
@@ -44,5 +46,11 @@ namespace Ryujinx.Common.Utilities
|
||||
CopyDirectory(sourceDir, destinationDir, true);
|
||||
Directory.Delete(sourceDir, true);
|
||||
}
|
||||
|
||||
public static string SanitizeFileName(string fileName)
|
||||
{
|
||||
var reservedChars = new HashSet<char>(Path.GetInvalidFileNameChars());
|
||||
return string.Concat(fileName.Select(c => reservedChars.Contains(c) ? '_' : c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,12 +16,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("macos")]
|
||||
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public class HvMemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
public const int PageMask = PageSize - 1;
|
||||
|
||||
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||
|
||||
@@ -39,8 +35,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly HvAddressSpace _addressSpace;
|
||||
|
||||
internal HvAddressSpace AddressSpace => _addressSpace;
|
||||
@@ -62,6 +56,8 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the Hypervisor memory manager.
|
||||
/// </summary>
|
||||
@@ -73,7 +69,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
_backingMemory = backingMemory;
|
||||
_pageTable = new PageTable<ulong>();
|
||||
_invalidAccessHandler = invalidAccessHandler;
|
||||
_addressSpaceSize = addressSpaceSize;
|
||||
AddressSpaceSize = addressSpaceSize;
|
||||
|
||||
ulong asSize = PageSize;
|
||||
int asBits = PageBits;
|
||||
@@ -92,42 +88,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the virtual address is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address</param>
|
||||
/// <returns>True if the virtual address is part of the addressable space</returns>
|
||||
private bool ValidateAddress(ulong va)
|
||||
{
|
||||
return va < _addressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
|
||||
private bool ValidateAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
|
||||
private void AssertValidAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
@@ -209,9 +169,19 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Read(ulong va, Span<byte> data)
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
ReadImpl(va, data);
|
||||
try
|
||||
{
|
||||
base.Read(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -340,7 +310,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
ReadImpl(va, data);
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -367,7 +337,7 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
Memory<byte> memory = new byte[size];
|
||||
|
||||
ReadImpl(va, memory.Span);
|
||||
base.Read(va, memory.Span);
|
||||
|
||||
return new WritableRegion(this, va, memory);
|
||||
}
|
||||
@@ -576,48 +546,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
return regions;
|
||||
}
|
||||
|
||||
private void ReadImpl(ulong va, Span<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressChecked(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressChecked(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
|
||||
@@ -936,6 +864,10 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
_addressSpace.Dispose();
|
||||
}
|
||||
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> GetPhysicalAddressChecked(va);
|
||||
}
|
||||
}
|
||||
|
@@ -14,12 +14,8 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager.
|
||||
/// </summary>
|
||||
public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManager : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
public const int PageMask = PageSize - 1;
|
||||
|
||||
private const int PteSize = 8;
|
||||
|
||||
private const int PointerTagBit = 62;
|
||||
@@ -35,8 +31,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// </summary>
|
||||
public int AddressSpaceBits { get; }
|
||||
|
||||
private readonly ulong _addressSpaceSize;
|
||||
|
||||
private readonly MemoryBlock _pageTable;
|
||||
|
||||
/// <summary>
|
||||
@@ -50,6 +44,8 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the memory manager.
|
||||
/// </summary>
|
||||
@@ -71,7 +67,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
AddressSpaceBits = asBits;
|
||||
_addressSpaceSize = asSize;
|
||||
AddressSpaceSize = asSize;
|
||||
_pageTable = new MemoryBlock((asSize / PageSize) * PteSize);
|
||||
|
||||
Tracking = new MemoryTracking(this, PageSize);
|
||||
@@ -153,9 +149,19 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Read(ulong va, Span<byte> data)
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
ReadImpl(va, data);
|
||||
try
|
||||
{
|
||||
base.Read(va, data);
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -290,7 +296,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
Span<byte> data = new byte[size];
|
||||
|
||||
ReadImpl(va, data);
|
||||
base.Read(va, data);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -462,48 +468,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
return regions;
|
||||
}
|
||||
|
||||
private void ReadImpl(ulong va, Span<byte> data)
|
||||
{
|
||||
if (data.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AssertValidAddressAndSize(va, (ulong)data.Length);
|
||||
|
||||
int offset = 0, size;
|
||||
|
||||
if ((va & PageMask) != 0)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va);
|
||||
|
||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data[..size]);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
|
||||
for (; offset < data.Length; offset += size)
|
||||
{
|
||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
||||
|
||||
size = Math.Min(data.Length - offset, PageSize);
|
||||
|
||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
||||
}
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRangeMapped(ulong va, ulong size)
|
||||
{
|
||||
@@ -544,37 +508,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
return _pageTable.Read<ulong>((va / PageSize) * PteSize) != 0;
|
||||
}
|
||||
|
||||
private bool ValidateAddress(ulong va)
|
||||
{
|
||||
return va < _addressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
|
||||
private bool ValidateAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
|
||||
private void AssertValidAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||
@@ -691,5 +624,11 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
protected override void Destroy() => _pageTable.Dispose();
|
||||
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
=> _backingMemory.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> GetPhysicalAddressInternal(va);
|
||||
}
|
||||
}
|
||||
|
@@ -13,12 +13,8 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// <summary>
|
||||
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
|
||||
/// </summary>
|
||||
public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
public sealed class MemoryManagerHostMapped : VirtualMemoryManagerRefCountedBase<ulong, ulong>, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
|
||||
{
|
||||
public const int PageBits = 12;
|
||||
public const int PageSize = 1 << PageBits;
|
||||
public const int PageMask = PageSize - 1;
|
||||
|
||||
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
|
||||
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
|
||||
|
||||
@@ -39,8 +35,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
private readonly AddressSpace _addressSpace;
|
||||
|
||||
public ulong AddressSpaceSize { get; }
|
||||
|
||||
private readonly PageTable<ulong> _pageTable;
|
||||
|
||||
private readonly MemoryEhMeilleure _memoryEh;
|
||||
@@ -60,6 +54,8 @@ namespace Ryujinx.Cpu.Jit
|
||||
|
||||
public event Action<ulong, ulong> UnmapEvent;
|
||||
|
||||
protected override ulong AddressSpaceSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the host mapped memory manager.
|
||||
/// </summary>
|
||||
@@ -91,42 +87,6 @@ namespace Ryujinx.Cpu.Jit
|
||||
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the virtual address is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address</param>
|
||||
/// <returns>True if the virtual address is part of the addressable space</returns>
|
||||
private bool ValidateAddress(ulong va)
|
||||
{
|
||||
return va < AddressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
|
||||
private bool ValidateAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
ulong endVa = va + size;
|
||||
return endVa >= va && endVa >= size && endVa <= AddressSpaceSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
|
||||
private void AssertValidAddressAndSize(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
|
||||
/// </summary>
|
||||
@@ -235,7 +195,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Read(ulong va, Span<byte> data)
|
||||
public override void Read(ulong va, Span<byte> data)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -816,6 +776,10 @@ namespace Ryujinx.Cpu.Jit
|
||||
_memoryEh.Dispose();
|
||||
}
|
||||
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
protected override Span<byte> GetPhysicalAddressSpan(ulong pa, int size)
|
||||
=> _addressSpace.Mirror.GetSpan(pa, size);
|
||||
|
||||
protected override ulong TranslateVirtualAddressForRead(ulong va)
|
||||
=> va;
|
||||
}
|
||||
}
|
||||
|
@@ -1106,6 +1106,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64
|
||||
case InstName.Mrs:
|
||||
case InstName.MsrImm:
|
||||
case InstName.MsrReg:
|
||||
case InstName.Sysl:
|
||||
return true;
|
||||
}
|
||||
|
||||
|
48
src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs
Normal file
48
src/Ryujinx.Cpu/LightningJit/Arm64/SysUtils.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm64
|
||||
{
|
||||
static class SysUtils
|
||||
{
|
||||
public static (uint, uint, uint, uint) UnpackOp1CRnCRmOp2(uint encoding)
|
||||
{
|
||||
uint op1 = (encoding >> 16) & 7;
|
||||
uint crn = (encoding >> 12) & 0xf;
|
||||
uint crm = (encoding >> 8) & 0xf;
|
||||
uint op2 = (encoding >> 5) & 7;
|
||||
|
||||
return (op1, crn, crm, op2);
|
||||
}
|
||||
|
||||
public static bool IsCacheInstEl0(uint encoding)
|
||||
{
|
||||
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
|
||||
|
||||
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
|
||||
{
|
||||
0b011_0111_0100_001 => true, // DC ZVA
|
||||
0b011_0111_1010_001 => true, // DC CVAC
|
||||
0b011_0111_1100_001 => true, // DC CVAP
|
||||
0b011_0111_1011_001 => true, // DC CVAU
|
||||
0b011_0111_1110_001 => true, // DC CIVAC
|
||||
0b011_0111_0101_001 => true, // IC IVAU
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public static bool IsCacheInstUciTrapped(uint encoding)
|
||||
{
|
||||
(uint op1, uint crn, uint crm, uint op2) = UnpackOp1CRnCRmOp2(encoding);
|
||||
|
||||
return ((op1 << 11) | (crn << 7) | (crm << 3) | op2) switch
|
||||
{
|
||||
0b011_0111_1010_001 => true, // DC CVAC
|
||||
0b011_0111_1100_001 => true, // DC CVAP
|
||||
0b011_0111_1011_001 => true, // DC CVAU
|
||||
0b011_0111_1110_001 => true, // DC CIVAC
|
||||
0b011_0111_0101_001 => true, // IC IVAU
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -257,7 +257,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
(name, flags, AddressForm addressForm) = InstTable.GetInstNameAndFlags(encoding, cpuPreset.Version, cpuPreset.Features);
|
||||
|
||||
if (name.IsPrivileged())
|
||||
if (name.IsPrivileged() || (name == InstName.Sys && IsPrivilegedSys(encoding)))
|
||||
{
|
||||
name = InstName.UdfPermUndef;
|
||||
flags = InstFlags.None;
|
||||
@@ -341,6 +341,11 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
return new(startAddress, address, insts, !isTruncated && !name.IsException(), isTruncated, isLoopEnd);
|
||||
}
|
||||
|
||||
private static bool IsPrivilegedSys(uint encoding)
|
||||
{
|
||||
return !SysUtils.IsCacheInstEl0(encoding);
|
||||
}
|
||||
|
||||
private static bool IsMrsNzcv(uint encoding)
|
||||
{
|
||||
return (encoding & ~0x1fu) == 0xd53b4200u;
|
||||
|
@@ -13,6 +13,14 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
|
||||
public static void RewriteSysInstruction(int asBits, MemoryManagerType mmType, CodeWriter writer, RegisterAllocator regAlloc, uint encoding)
|
||||
{
|
||||
// TODO: Handle IC instruction, it should invalidate the JIT cache.
|
||||
|
||||
if (InstEmitSystem.IsCacheInstForbidden(encoding))
|
||||
{
|
||||
// Current OS does not allow cache maintenance instructions from user mode, just do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
int rtIndex = RegisterUtils.ExtractRt(encoding);
|
||||
if (rtIndex == RegisterUtils.ZrIndex)
|
||||
{
|
||||
|
@@ -69,7 +69,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
asm.LdrRiUn(Register((int)rd), Register(regAlloc.FixedContextRegister), NativeContextOffsets.TpidrEl0Offset);
|
||||
}
|
||||
}
|
||||
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0
|
||||
else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
|
||||
{
|
||||
uint rd = encoding & 0x1f;
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if ((encoding & ~0x1f) == 0xd53b0020 && IsAppleOS()) // mrs x0, ctr_el0
|
||||
else if ((encoding & ~0x1f) == 0xd53b0020 && IsCtrEl0AccessForbidden()) // mrs x0, ctr_el0
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -127,9 +127,16 @@ namespace Ryujinx.Cpu.LightningJit.Arm64.Target.Arm64
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsAppleOS()
|
||||
private static bool IsCtrEl0AccessForbidden()
|
||||
{
|
||||
return OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
|
||||
// Only Linux allows accessing CTR_EL0 from user mode.
|
||||
return OperatingSystem.IsWindows() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
|
||||
}
|
||||
|
||||
public static bool IsCacheInstForbidden(uint encoding)
|
||||
{
|
||||
// Windows does not allow the cache maintenance instructions to be used from user mode.
|
||||
return OperatingSystem.IsWindows() && SysUtils.IsCacheInstUciTrapped(encoding);
|
||||
}
|
||||
|
||||
public static bool NeedsContextStoreLoad(InstName name)
|
||||
|
@@ -1,10 +1,13 @@
|
||||
using Ryujinx.Memory;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public abstract class MemoryManagerBase : IRefCounted
|
||||
public abstract class VirtualMemoryManagerRefCountedBase<TVirtual, TPhysical> : VirtualMemoryManagerBase<TVirtual, TPhysical>, IRefCounted
|
||||
where TVirtual : IBinaryInteger<TVirtual>
|
||||
where TPhysical : IBinaryInteger<TPhysical>
|
||||
{
|
||||
private int _referenceCount;
|
||||
|
@@ -674,9 +674,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
1 => new FormatInfo(Format.R8Unorm, 1, 1, 1, 1),
|
||||
2 => new FormatInfo(Format.R16Unorm, 1, 1, 2, 1),
|
||||
4 => new FormatInfo(Format.R32Float, 1, 1, 4, 1),
|
||||
8 => new FormatInfo(Format.R32G32Float, 1, 1, 8, 2),
|
||||
16 => new FormatInfo(Format.R32G32B32A32Float, 1, 1, 16, 4),
|
||||
4 => new FormatInfo(Format.R32Uint, 1, 1, 4, 1),
|
||||
8 => new FormatInfo(Format.R32G32Uint, 1, 1, 8, 2),
|
||||
16 => new FormatInfo(Format.R32G32B32A32Uint, 1, 1, 16, 4),
|
||||
_ => format,
|
||||
};
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 6253;
|
||||
private const uint CodeGenVersion = 6455;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@@ -24,17 +24,21 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
continue;
|
||||
}
|
||||
|
||||
Operand temp = OperandHelper.Local();
|
||||
|
||||
for (int index = 0; index < phi.SourcesCount; index++)
|
||||
{
|
||||
Operand src = phi.GetSource(index);
|
||||
|
||||
BasicBlock srcBlock = phi.GetBlock(index);
|
||||
|
||||
Operation copyOp = new(Instruction.Copy, phi.Dest, src);
|
||||
Operation copyOp = new(Instruction.Copy, temp, src);
|
||||
|
||||
srcBlock.Append(copyOp);
|
||||
}
|
||||
|
||||
Operation copyOp2 = new(Instruction.Copy, phi.Dest, temp);
|
||||
|
||||
nextNode = block.Operations.AddAfter(node, copyOp2).Next;
|
||||
block.Operations.Remove(node);
|
||||
|
||||
node = nextNode;
|
||||
|
@@ -25,7 +25,7 @@ namespace Ryujinx.Modules
|
||||
private readonly string _buildUrl;
|
||||
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"))
|
||||
{
|
||||
@@ -34,7 +34,7 @@ namespace Ryujinx.Modules
|
||||
_mainWindow = mainWindow;
|
||||
_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?";
|
||||
SecondaryText.Text = $"{Program.Version} -> {newVersion}";
|
||||
|
@@ -1,75 +1,93 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Gtk;
|
||||
using ICSharpCode.SharpZipLib.GZip;
|
||||
using ICSharpCode.SharpZipLib.Tar;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Ryujinx.Ava;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.UI.Common.Helper;
|
||||
using Ryujinx.UI;
|
||||
using Ryujinx.UI.Common.Models.Github;
|
||||
using Ryujinx.UI.Widgets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Modules
|
||||
{
|
||||
internal static class Updater
|
||||
public static class Updater
|
||||
{
|
||||
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 _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
|
||||
private static readonly string _updatePublishDir = Path.Combine(_updateDir, "publish");
|
||||
private const int ConnectionCount = 4;
|
||||
|
||||
private static string _buildVer;
|
||||
private static string _platformExt;
|
||||
private static string _buildUrl;
|
||||
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;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
Running = true;
|
||||
mainWindow.UpdateMenuItem.Sensitive = false;
|
||||
|
||||
int artifactIndex = -1;
|
||||
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "macos_universal.app.tar.gz";
|
||||
_platformExt = "osx_x64.zip";
|
||||
artifactIndex = 1;
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_platformExt = "win_x64.zip";
|
||||
artifactIndex = 2;
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
var arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64";
|
||||
_platformExt = $"linux_{arch}.tar.gz";
|
||||
artifactIndex = 0;
|
||||
}
|
||||
|
||||
if (artifactIndex == -1)
|
||||
{
|
||||
GtkDialog.CreateErrorDialog("Your platform is not supported!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Version newVersion;
|
||||
@@ -81,14 +99,9 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
catch
|
||||
{
|
||||
GtkDialog.CreateWarningDialog("Failed to convert the current Ryujinx version.", "Cancelling Update!");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -96,15 +109,16 @@ namespace Ryujinx.Modules
|
||||
try
|
||||
{
|
||||
using HttpClient jsonClient = ConstructHttpClient();
|
||||
|
||||
string buildInfoUrl = $"{GitHubApiUrl}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
|
||||
|
||||
// Fetch latest build information
|
||||
string fetchedJson = await jsonClient.GetStringAsync(buildInfoUrl);
|
||||
var fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse);
|
||||
_buildVer = fetched.Name;
|
||||
|
||||
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;
|
||||
|
||||
@@ -112,13 +126,9 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,29 +136,20 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
}
|
||||
|
||||
// If build not done, assume no new update are available.
|
||||
if (_buildUrl is null)
|
||||
if (_buildUrl == null)
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, exception.Message);
|
||||
|
||||
await ContentDialogHelper.CreateErrorDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
|
||||
|
||||
_running = false;
|
||||
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.");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -159,13 +160,8 @@ namespace Ryujinx.Modules
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
|
||||
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
|
||||
|
||||
_running = false;
|
||||
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!");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -174,12 +170,11 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showVersionUpToDate)
|
||||
{
|
||||
await ContentDialogHelper.CreateUpdaterInfoDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage],
|
||||
"");
|
||||
GtkDialog.CreateUpdaterInfoDialog("You are already using the latest version of Ryujinx!", "");
|
||||
}
|
||||
|
||||
_running = false;
|
||||
Running = false;
|
||||
mainWindow.UpdateMenuItem.Sensitive = true;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -202,39 +197,13 @@ namespace Ryujinx.Modules
|
||||
_buildSize = -1;
|
||||
}
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
// Show a message asking the user if they want to update
|
||||
var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
|
||||
$"{Program.Version} -> {newVersion}");
|
||||
|
||||
if (shouldUpdate)
|
||||
{
|
||||
await UpdateRyujinx(mainWindow, _buildUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
});
|
||||
// Show a message asking the user if they want to update
|
||||
UpdateDialog updateDialog = new(mainWindow, newVersion, _buildUrl);
|
||||
updateDialog.Show();
|
||||
}
|
||||
|
||||
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
|
||||
if (Directory.Exists(_updateDir))
|
||||
{
|
||||
@@ -245,93 +214,22 @@ namespace Ryujinx.Modules
|
||||
|
||||
string updateFile = Path.Combine(_updateDir, "update.bin");
|
||||
|
||||
TaskDialog taskDialog = new()
|
||||
// Download the update .zip
|
||||
updateDialog.MainText.Text = "Downloading Update...";
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
updateDialog.ProgressBar.MaxValue = 100;
|
||||
|
||||
if (_buildSize >= 0)
|
||||
{
|
||||
Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
|
||||
IconSource = new SymbolIconSource { Symbol = Symbol.Download },
|
||||
ShowProgressBar = true,
|
||||
XamlRoot = parent,
|
||||
};
|
||||
|
||||
taskDialog.Opened += (s, e) =>
|
||||
DoUpdateWithMultipleThreads(updateDialog, downloadUrl, updateFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_buildSize >= 0)
|
||||
{
|
||||
DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
DoUpdateWithSingleThread(taskDialog, 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);
|
||||
}
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
private static void DoUpdateWithMultipleThreads(UpdateDialog updateDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
// Multi-Threaded Updater
|
||||
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.
|
||||
using WebClient client = new();
|
||||
#pragma warning restore SYSLIB0014
|
||||
|
||||
webClients.Add(client);
|
||||
|
||||
if (i == ConnectionCount - 1)
|
||||
@@ -375,7 +272,7 @@ namespace Ryujinx.Modules
|
||||
Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
|
||||
Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
|
||||
|
||||
taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
|
||||
updateDialog.ProgressBar.Value = totalProgressPercentage / ConnectionCount;
|
||||
};
|
||||
|
||||
client.DownloadDataCompleted += (_, args) =>
|
||||
@@ -386,8 +283,6 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
webClients[index].Dispose();
|
||||
|
||||
taskDialog.Hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -405,24 +300,18 @@ namespace Ryujinx.Modules
|
||||
|
||||
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
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
InstallUpdate(updateDialog, updateFile);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, e.Message);
|
||||
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();
|
||||
}
|
||||
|
||||
DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
|
||||
DoUpdateWithSingleThread(updateDialog, downloadUrl, updateFile);
|
||||
|
||||
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();
|
||||
// We do not want to timeout while downloading
|
||||
@@ -474,118 +363,101 @@ namespace Ryujinx.Modules
|
||||
|
||||
byteWritten += readSize;
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
|
||||
|
||||
updateDialog.ProgressBar.Value = ((double)byteWritten / totalBytes) * 100;
|
||||
updateFileStream.Write(buffer, 0, readSize);
|
||||
}
|
||||
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
InstallUpdate(updateDialog, updateFile);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static double GetPercentage(double value, double max)
|
||||
private static void DoUpdateWithSingleThread(UpdateDialog updateDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
return max == 0 ? 0 : value / max * 100;
|
||||
}
|
||||
|
||||
private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
|
||||
{
|
||||
Thread worker = new(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile))
|
||||
Thread worker = new(() => DoUpdateWithSingleThreadWorker(updateDialog, downloadUrl, updateFile))
|
||||
{
|
||||
Name = "Updater.SingleThreadWorker",
|
||||
};
|
||||
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
|
||||
zipStream.CopyTo(outStream);
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
private static async void InstallUpdate(UpdateDialog updateDialog, string updateFile)
|
||||
{
|
||||
// Extract Update
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
updateDialog.MainText.Text = "Extracting Update...";
|
||||
updateDialog.ProgressBar.Value = 0;
|
||||
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
ExtractTarGzipFile(taskDialog, updateFile, _updateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
ExtractZipFile(taskDialog, updateFile, _updateDir);
|
||||
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;
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
while ((tarEntry = tarStream.GetNextEntry()) != null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(_updateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
TarEntry entry = tarEntry;
|
||||
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
updateDialog.ProgressBar.Value += entry.Size;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
updateDialog.ProgressBar.Value = inStream.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
updateDialog.ProgressBar.MaxValue = zipFile.Count;
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
if (zipEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(_updateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using Stream zipStream = zipFile.GetInputStream(zipEntry);
|
||||
using FileStream outStream = File.OpenWrite(outPath);
|
||||
zipStream.CopyTo(outStream);
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
updateDialog.ProgressBar.Value++;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Delete downloaded zip
|
||||
@@ -593,46 +465,49 @@ namespace Ryujinx.Modules
|
||||
|
||||
List<string> allFiles = EnumerateFilesToDelete().ToList();
|
||||
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
updateDialog.MainText.Text = "Renaming Old Files...";
|
||||
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
|
||||
await Task.Run(() =>
|
||||
{
|
||||
// Replace old files
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
count++;
|
||||
try
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
Application.Invoke(delegate
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
updateDialog.ProgressBar.Value++;
|
||||
});
|
||||
}
|
||||
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];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
updateDialog.MainText.Text = "Adding New Files...";
|
||||
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)
|
||||
@@ -642,11 +517,7 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage])
|
||||
);
|
||||
GtkDialog.CreateWarningDialog("You are not connected to the Internet!", "Please verify that you have a working Internet connection!");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -656,11 +527,7 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
|
||||
);
|
||||
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.");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -672,19 +539,11 @@ namespace Ryujinx.Modules
|
||||
{
|
||||
if (ReleaseInformation.IsFlatHubBuild)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage])
|
||||
);
|
||||
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please update Ryujinx via FlatHub.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage])
|
||||
);
|
||||
GtkDialog.CreateWarningDialog("Updater Disabled!", "Please download Ryujinx at https://ryujinx.org/ if you are looking for a supported version.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +557,7 @@ namespace Ryujinx.Modules
|
||||
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
|
||||
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.
|
||||
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));
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
string dirName = Path.GetFileName(directory);
|
||||
@@ -736,28 +594,28 @@ namespace Ryujinx.Modules
|
||||
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))
|
||||
{
|
||||
count++;
|
||||
|
||||
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()
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(_homeDir, "*.ryuold", SearchOption.AllDirectories))
|
||||
foreach (string file in EnumerateFilesToDelete())
|
||||
{
|
||||
File.Delete(file);
|
||||
if (Path.GetExtension(file).EndsWith(".ryuold"))
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
378
src/Ryujinx.Gtk3/Program.cs
Normal file
378
src/Ryujinx.Gtk3/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
104
src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
Normal file
104
src/Ryujinx.Gtk3/Ryujinx.Gtk3.csproj
Normal 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>
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
@@ -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)
|
||||
{
|
||||
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;
|
||||
|
@@ -143,7 +143,7 @@ namespace Ryujinx.UI
|
||||
|
||||
#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"))
|
||||
{
|
@@ -3,6 +3,7 @@ using Gtk;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.GAL.Multithreading;
|
||||
using Ryujinx.Graphics.Gpu;
|
||||
@@ -378,8 +379,12 @@ namespace Ryujinx.UI
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
var currentTime = DateTime.Now;
|
||||
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||
string applicationName = Device.Processes.ActiveApplication.Name;
|
||||
string sanitizedApplicationName = FileSystemUtils.SanitizeFileName(applicationName);
|
||||
DateTime currentTime = DateTime.Now;
|
||||
|
||||
string filename = $"{sanitizedApplicationName}_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||
|
||||
string directory = AppDataManager.Mode switch
|
||||
{
|
||||
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => System.IO.Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
@@ -189,7 +189,7 @@ namespace Ryujinx.UI.Widgets
|
||||
_dialog = new MessageDialog(null, DialogFlags.DestroyWithParent, MessageType.Info, ButtonsType.Cancel, null)
|
||||
{
|
||||
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)}...",
|
||||
WindowPosition = WindowPosition.Center,
|
||||
};
|
@@ -15,7 +15,7 @@ namespace Ryujinx.UI.Widgets
|
||||
[GUI] Label _errorMessage;
|
||||
#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"))
|
||||
{
|
@@ -22,7 +22,7 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] Button _saveButton;
|
||||
#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"))
|
||||
{
|
@@ -117,7 +117,7 @@ namespace Ryujinx.UI.Windows
|
||||
|
||||
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"))
|
||||
{
|
@@ -32,7 +32,7 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] TreeSelection _dlcTreeSelection;
|
||||
#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"))
|
||||
{
|
@@ -120,7 +120,7 @@ namespace Ryujinx.UI.Windows
|
||||
|
||||
#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"))
|
||||
{
|
@@ -38,7 +38,7 @@ namespace Ryujinx.UI.Windows
|
||||
[GUI] RadioButton _noUpdateRadioButton;
|
||||
#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"))
|
||||
{
|
@@ -44,10 +44,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
private readonly Color _textSelectedColor;
|
||||
private readonly Color _textOverCursorColor;
|
||||
|
||||
private readonly IBrush _panelBrush;
|
||||
private readonly IBrush _disabledBrush;
|
||||
private readonly IBrush _cursorBrush;
|
||||
private readonly IBrush _selectionBoxBrush;
|
||||
private readonly Brush _panelBrush;
|
||||
private readonly Brush _disabledBrush;
|
||||
private readonly Brush _cursorBrush;
|
||||
private readonly Brush _selectionBoxBrush;
|
||||
|
||||
private readonly Pen _textBoxOutlinePen;
|
||||
private readonly Pen _cursorPen;
|
||||
@@ -97,10 +97,10 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
_cursorBrush = new SolidBrush(_textNormalColor);
|
||||
_selectionBoxBrush = new SolidBrush(selectionBackgroundColor);
|
||||
|
||||
_textBoxOutlinePen = new Pen(borderColor, _textBoxOutlineWidth);
|
||||
_cursorPen = new Pen(_textNormalColor, cursorWidth);
|
||||
_selectionBoxPen = new Pen(selectionBackgroundColor, cursorWidth);
|
||||
_padPressedPen = new Pen(borderColor, _padPressedPenWidth);
|
||||
_textBoxOutlinePen = Pens.Solid(borderColor, _textBoxOutlineWidth);
|
||||
_cursorPen = Pens.Solid(_textNormalColor, cursorWidth);
|
||||
_selectionBoxPen = Pens.Solid(selectionBackgroundColor, cursorWidth);
|
||||
_padPressedPen = Pens.Solid(borderColor, _padPressedPenWidth);
|
||||
|
||||
_inputTextFontSize = 20;
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
private static void SetGraphicsOptions(IImageProcessingContext context)
|
||||
{
|
||||
context.GetGraphicsOptions().Antialias = true;
|
||||
context.GetShapeGraphicsOptions().GraphicsOptions.Antialias = true;
|
||||
context.GetDrawingOptions().GraphicsOptions.Antialias = true;
|
||||
}
|
||||
|
||||
private void DrawImmutableElements()
|
||||
@@ -293,31 +293,31 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
}
|
||||
private static RectangleF MeasureString(string text, Font font)
|
||||
{
|
||||
RendererOptions options = new(font);
|
||||
TextOptions options = new(font);
|
||||
|
||||
if (text == "")
|
||||
{
|
||||
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
|
||||
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
|
||||
|
||||
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
|
||||
}
|
||||
|
||||
FontRectangle rectangle = TextMeasurer.Measure(text, options);
|
||||
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
|
||||
|
||||
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
||||
}
|
||||
|
||||
private static RectangleF MeasureString(ReadOnlySpan<char> text, Font font)
|
||||
{
|
||||
RendererOptions options = new(font);
|
||||
TextOptions options = new(font);
|
||||
|
||||
if (text == "")
|
||||
{
|
||||
FontRectangle emptyRectangle = TextMeasurer.Measure(" ", options);
|
||||
FontRectangle emptyRectangle = TextMeasurer.MeasureSize(" ", options);
|
||||
return new RectangleF(0, emptyRectangle.Y, 0, emptyRectangle.Height);
|
||||
}
|
||||
|
||||
FontRectangle rectangle = TextMeasurer.Measure(text, options);
|
||||
FontRectangle rectangle = TextMeasurer.MeasureSize(text, options);
|
||||
|
||||
return new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height);
|
||||
}
|
||||
@@ -350,7 +350,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
// Draw the cursor on top of the text and redraw the text with a different color if necessary.
|
||||
|
||||
Color cursorTextColor;
|
||||
IBrush cursorBrush;
|
||||
Brush cursorBrush;
|
||||
Pen cursorPen;
|
||||
|
||||
float cursorPositionYTop = inputTextY + 1;
|
||||
@@ -435,7 +435,7 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
new PointF(cursorPositionXLeft, cursorPositionYBottom),
|
||||
};
|
||||
|
||||
context.DrawLines(cursorPen, points);
|
||||
context.DrawLine(cursorPen, points);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -562,12 +562,12 @@ namespace Ryujinx.HLE.HOS.Applets.SoftwareKeyboard
|
||||
|
||||
// Convert the pixel format used in the image to the one used in the Switch surface.
|
||||
|
||||
if (!_surface.TryGetSinglePixelSpan(out Span<Argb32> pixels))
|
||||
if (!_surface.DangerousTryGetSinglePixelMemory(out Memory<Argb32> pixels))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_bufferData = MemoryMarshal.AsBytes(pixels).ToArray();
|
||||
_bufferData = MemoryMarshal.AsBytes(pixels.Span).ToArray();
|
||||
Span<uint> dataConvert = MemoryMarshal.Cast<byte, uint>(_bufferData);
|
||||
|
||||
Debug.Assert(_bufferData.Length == _surfaceInfo.Size);
|
||||
|
@@ -4,12 +4,6 @@ using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.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.HLE.FileSystem;
|
||||
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.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||
using Ryujinx.HLE.HOS.Services.Apm;
|
||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||
using Ryujinx.HLE.HOS.Services.Caps;
|
||||
using Ryujinx.HLE.HOS.Services.Mii;
|
||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||
@@ -61,11 +54,6 @@ namespace Ryujinx.HLE.HOS
|
||||
internal ITickSource TickSource { get; }
|
||||
|
||||
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; }
|
||||
|
||||
@@ -79,8 +67,6 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
internal ServerBase SmServer { 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 HidServer { get; private set; }
|
||||
internal ServerBase NvDrvServer { get; private set; }
|
||||
@@ -248,56 +234,6 @@ namespace Ryujinx.HLE.HOS
|
||||
HostSyncpoint = new NvHostSyncpt(device);
|
||||
|
||||
SurfaceFlinger = new SurfaceFlinger(device);
|
||||
|
||||
InitializeAudioRenderer(TickSource);
|
||||
}
|
||||
|
||||
private void InitializeAudioRenderer(ITickSource tickSource)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
public void InitializeServices()
|
||||
@@ -310,8 +246,6 @@ namespace Ryujinx.HLE.HOS
|
||||
SmServer.InitDone.WaitOne();
|
||||
|
||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
||||
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
||||
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||
@@ -329,7 +263,13 @@ namespace Ryujinx.HLE.HOS
|
||||
HorizonFsClient fsClient = new(this);
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -384,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()
|
||||
{
|
||||
AppletState.SetFocus(true);
|
||||
@@ -458,11 +387,7 @@ namespace Ryujinx.HLE.HOS
|
||||
// "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
|
||||
if (IsPaused)
|
||||
{
|
||||
AudioManager.StopUpdates();
|
||||
|
||||
TogglePauseEmulation(false);
|
||||
|
||||
AudioRendererManager.StopSendingCommands();
|
||||
}
|
||||
|
||||
KProcess terminationProcess = new(KernelContext);
|
||||
@@ -513,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.
|
||||
INvDrvServices.Destroy();
|
||||
|
||||
AudioManager.Dispose();
|
||||
AudioOutputManager.Dispose();
|
||||
AudioInputManager.Dispose();
|
||||
|
||||
AudioRendererManager.Dispose();
|
||||
|
||||
if (LibHacHorizonManager.ApplicationClient != null)
|
||||
{
|
||||
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
|
||||
|
25
src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
Normal file
25
src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
@@ -3142,6 +3143,37 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||
}
|
||||
#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)
|
||||
{
|
||||
return (address + 0x1000000000) < 0xffffff000;
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user