Merge branch '4304-missing-updates' of https://github.com/tvoracek/Ryujinx into 4304-missing-updates

This commit is contained in:
Tomas Voracek 2023-12-21 19:50:50 +01:00
commit 3b77ecb8a6
185 changed files with 3232 additions and 765 deletions

View file

@ -233,6 +233,29 @@ dotnet_naming_style.IPascalCase.required_suffix =
dotnet_naming_style.IPascalCase.word_separator = dotnet_naming_style.IPascalCase.word_separator =
dotnet_naming_style.IPascalCase.capitalization = pascal_case dotnet_naming_style.IPascalCase.capitalization = pascal_case
# TODO:
# .NET 8 migration (new warnings are caused by the NET 8 C# compiler and analyzer)
# The following info messages might need to be fixed in the source code instead of hiding the actual message
# Without the following lines, dotnet format would fail
# Disable "Collection initialization can be simplified"
dotnet_diagnostic.IDE0028.severity = none
dotnet_diagnostic.IDE0300.severity = none
dotnet_diagnostic.IDE0301.severity = none
dotnet_diagnostic.IDE0302.severity = none
dotnet_diagnostic.IDE0305.severity = none
# Disable "'new' expression can be simplified"
dotnet_diagnostic.IDE0090.severity = none
# Disable "Use primary constructor"
dotnet_diagnostic.IDE0290.severity = none
# Disable "Member '' does not access instance data and can be marked as static"
dotnet_diagnostic.CA1822.severity = none
# Disable "Change type of field '' from '' to '' for improved performance"
dotnet_diagnostic.CA1859.severity = none
# Disable "Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array"
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] [src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for services # Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none dotnet_diagnostic.CA1822.severity = none

View file

@ -30,7 +30,7 @@ jobs:
- os: windows-latest - os: windows-latest
OS_NAME: Windows x64 OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win10-x64 DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64 RELEASE_ZIP_OS_NAME: win_x64
fail-fast: false fail-fast: false
@ -155,4 +155,4 @@ jobs:
with: with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz" path: "publish_headless/*.tar.gz"
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'

View file

@ -49,7 +49,9 @@ jobs:
run: python -m pip install PyYAML lxml run: python -m pip install PyYAML lxml
- name: Restore Nuget packages - name: Restore Nuget packages
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} # With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
# So we just publish to grab the dependencies
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
- name: Generate nuget_sources.json - name: Generate nuget_sources.json
shell: python shell: python

View file

@ -25,7 +25,7 @@ env:
jobs: jobs:
tag: tag:
name: Create tag name: Create tag
runs-on: ubuntu-latest runs-on: ubuntu-20.04
steps: steps:
- name: Get version info - name: Get version info
id: version_info id: version_info
@ -59,7 +59,7 @@ jobs:
- os: windows-latest - os: windows-latest
OS_NAME: Windows x64 OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win10-x64 DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64 RELEASE_ZIP_OS_NAME: win_x64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -156,11 +156,11 @@ jobs:
with: with:
global-json-file: global.json global-json-file: global.json
- name: Setup LLVM 14 - name: Setup LLVM 15
run: | run: |
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh chmod +x llvm.sh
sudo ./llvm.sh 14 sudo ./llvm.sh 15
- name: Install rcodesign - name: Install rcodesign
run: | run: |
@ -215,4 +215,4 @@ jobs:
needs: release needs: release
with: with:
ryujinx_version: "1.1.${{ github.run_number }}" ryujinx_version: "1.1.${{ github.run_number }}"
secrets: inherit secrets: inherit

View file

@ -3,13 +3,13 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.4" /> <PackageVersion Include="Avalonia" Version="11.0.5" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.4" /> <PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.4" /> <PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.4" /> <PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.4" /> <PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.2" /> <PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.2" /> <PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" /> <PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" /> <PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@ -18,23 +18,25 @@
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" /> <PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" /> <PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" /> <PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.18.0" /> <PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" /> <PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" /> <PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
<PackageVersion Include="NUnit" Version="3.13.3" /> <PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" /> <PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.7.7" /> <PackageVersion Include="OpenTK.Core" Version="4.8.1" />
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" /> <PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" /> <PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" /> <PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" /> <PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" /> <PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" /> <PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" /> <PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" /> <PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" /> <PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" /> <PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" /> <PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
@ -43,10 +45,10 @@
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" /> <PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" /> <PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" /> <PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" /> <PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="7.0.2" /> <PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" /> <PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -68,7 +68,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
If you wish to build the emulator yourself, follow these steps: If you wish to build the emulator yourself, follow these steps:
### Step 1 ### Step 1
Install the X64 version of [.NET 7.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/7.0). Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
### Step 2 ### Step 2
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files. Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
@ -141,3 +141,5 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system. - [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation. - [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View file

@ -681,4 +681,33 @@
END OF TERMS AND CONDITIONS END OF TERMS AND CONDITIONS
``` ```
</details> </details>
# ShellLink (MIT)
<details>
<summary>See License</summary>
```
MIT License
Copyright (c) 2017 Yorick Koster, Securify B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
</details>

View file

@ -3,8 +3,8 @@ Version=1.0
Name=Ryujinx Name=Ryujinx
Type=Application Type=Application
Icon=Ryujinx Icon=Ryujinx
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f Exec=Ryujinx.sh %f
Comment=A Nintendo Switch Emulator Comment=Plays Nintendo Switch applications
GenericName=Nintendo Switch Emulator GenericName=Nintendo Switch Emulator
Terminal=false Terminal=false
Categories=Game;Emulator; Categories=Game;Emulator;

View file

@ -0,0 +1,13 @@
[Desktop Entry]
Version=1.0
Name={0}
Type=Application
Icon={1}
Exec={2} %f
Comment=Nintendo Switch application
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;
Keywords=Switch;Nintendo;Emulator;
StartupWMClass=Ryujinx
PrefersNonDefaultGPU=true

View file

@ -43,7 +43,7 @@
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>
<string>public.app-category.games</string> <string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>11.0</string> <string>12.0</string>
<key>UTExportedTypeDeclarations</key> <key>UTExportedTypeDeclarations</key>
<array> <array>
<dict> <dict>
@ -155,4 +155,4 @@
<string>200000</string> <string>200000</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>{0}</string>
<key>CFBundleGetInfoString</key>
<string>{1}</string>
<key>CFBundleIconFile</key>
<string>{2}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 - 2023 Ryujinx Team and Contributors.</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>UIPrerenderedIcon</key>
<true/>
<key>LSEnvironment</key>
<dict>
<key>DOTNET_DefaultStackSize</key>
<string>200000</string>
</dict>
</dict>
</plist>

View file

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "7.0.200", "version": "8.0.100",
"rollForward": "latestFeature" "rollForward": "latestFeature"
} }
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -38,7 +38,9 @@ namespace ARMeilleure.Decoders
{ {
block = new Block(blkAddress); block = new Block(blkAddress);
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress)) if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) ||
opsCount > instructionLimit ||
(visited.Count > 0 && !memory.IsMapped(blkAddress)))
{ {
block.Exit = true; block.Exit = true;
block.EndAddress = blkAddress; block.EndAddress = blkAddress;

View file

@ -117,12 +117,11 @@ namespace ARMeilleure.Translation.Cache
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64()); int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
bool result = TryFind(funcOffset, out CacheEntry entry); if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
Debug.Assert(result); {
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size)); _cacheEntries.RemoveAt(entryIndex);
}
Remove(funcOffset);
} }
} }
@ -181,22 +180,7 @@ namespace ARMeilleure.Translation.Cache
_cacheEntries.Insert(index, entry); _cacheEntries.Insert(index, entry);
} }
private static void Remove(int offset) public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
if (index < 0)
{
index = ~index - 1;
}
if (index >= 0)
{
_cacheEntries.RemoveAt(index);
}
}
public static bool TryFind(int offset, out CacheEntry entry)
{ {
lock (_lock) lock (_lock)
{ {
@ -210,11 +194,13 @@ namespace ARMeilleure.Translation.Cache
if (index >= 0) if (index >= 0)
{ {
entry = _cacheEntries[index]; entry = _cacheEntries[index];
entryIndex = index;
return true; return true;
} }
} }
entry = default; entry = default;
entryIndex = 0;
return false; return false;
} }
} }

View file

@ -95,7 +95,7 @@ namespace ARMeilleure.Translation.Cache
{ {
int offset = (int)((long)controlPc - context.ToInt64()); int offset = (int)((long)controlPc - context.ToInt64());
if (!JitCache.TryFind(offset, out CacheEntry funcEntry)) if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _))
{ {
return null; // Not found. return null; // Not found.
} }

View file

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.OpenAL" /> <PackageReference Include="OpenTK.Audio.OpenAL" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -15,11 +15,11 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dll</TargetPath> <TargetPath>libsoundio.dll</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.dylib</TargetPath> <TargetPath>libsoundio.dylib</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'"> <ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>libsoundio.so</TargetPath> <TargetPath>libsoundio.so</TargetPath>
</ContentWithTargetPath> </ContentWithTargetPath>

View file

@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Utils
throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null);
} }
MemoryMarshal.Write(backingMemory.Span[..size], ref data); MemoryMarshal.Write(backingMemory.Span[..size], in data);
backingMemory = backingMemory[size..]; backingMemory = backingMemory[size..];
} }
@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Utils
throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null); throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null);
} }
MemoryMarshal.Write(backingMemory[..size], ref data); MemoryMarshal.Write(backingMemory[..size], in data);
backingMemory = backingMemory[size..]; backingMemory = backingMemory[size..];
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -190,6 +190,7 @@ namespace Ryujinx.Ava
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel; ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
@ -408,6 +409,11 @@ namespace Ryujinx.Ava
}); });
} }
private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e)
{
Device.Configuration.EnableInternetAccess = e.NewValue;
}
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e) private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
{ {
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
@ -710,7 +716,7 @@ namespace Ryujinx.Ava
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{ {
appMetadata.LastPlayed = DateTime.UtcNow; appMetadata.UpdatePreGame();
}); });
return true; return true;

View file

@ -14,7 +14,7 @@
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder", "MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
"MenuBarFileOpenLogsFolder": "Open Logs Folder", "MenuBarFileOpenLogsFolder": "Open Logs Folder",
"MenuBarFileExit": "_Exit", "MenuBarFileExit": "_Exit",
"MenuBarOptions": "Options", "MenuBarOptions": "_Options",
"MenuBarOptionsToggleFullscreen": "Toggle Fullscreen", "MenuBarOptionsToggleFullscreen": "Toggle Fullscreen",
"MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode", "MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode",
"MenuBarOptionsStopEmulation": "Stop Emulation", "MenuBarOptionsStopEmulation": "Stop Emulation",
@ -30,7 +30,7 @@
"MenuBarToolsManageFileTypes": "Manage file types", "MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types", "MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types", "MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarHelp": "Help", "MenuBarHelp": "_Help",
"MenuBarHelpCheckForUpdates": "Check for Updates", "MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About", "MenuBarHelpAbout": "About",
"MenuSearch": "Search...", "MenuSearch": "Search...",
@ -72,6 +72,8 @@
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)", "GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
"GameListContextMenuExtractDataLogo": "Logo", "GameListContextMenuExtractDataLogo": "Logo",
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)", "GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded", "StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}", "StatusBarSystemVersion": "System Version: {0}",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected", "LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
@ -648,7 +650,7 @@
"UserEditorTitle": "Edit User", "UserEditorTitle": "Edit User",
"UserEditorTitleCreate": "Create User", "UserEditorTitleCreate": "Create User",
"SettingsTabNetworkInterface": "Network Interface:", "SettingsTabNetworkInterface": "Network Interface:",
"NetworkInterfaceTooltip": "The network interface used for LAN features", "NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
"NetworkInterfaceDefault": "Default", "NetworkInterfaceDefault": "Default",
"PackagingShaders": "Packaging Shaders", "PackagingShaders": "Packaging Shaders",
"AboutChangelogButton": "View Changelog on GitHub", "AboutChangelogButton": "View Changelog on GitHub",

View file

@ -173,7 +173,7 @@ namespace Ryujinx.Ava.Common
string extension = Path.GetExtension(titleFilePath).ToLower(); string extension = Path.GetExtension(titleFilePath).ToLower();
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci") if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
{ {
PartitionFileSystem pfs; IFileSystem pfs;
if (extension == ".xci") if (extension == ".xci")
{ {
@ -181,7 +181,9 @@ namespace Ryujinx.Ava.Common
} }
else else
{ {
pfs = new PartitionFileSystem(file.AsStorage()); var pfsTemp = new PartitionFileSystem();
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
pfs = pfsTemp;
} }
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))

View file

@ -6,13 +6,13 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop; using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.SDL2.Common; using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration; using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper; using Ryujinx.Ui.Common.Helper;
using Ryujinx.Ui.Common.SystemInfo;
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Version>1.0.0-dirty</Version> <Version>1.0.0-dirty</Version>
@ -25,6 +25,16 @@
<TrimMode>partial</TrimMode> <TrimMode>partial</TrimMode>
</PropertyGroup> </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> <ItemGroup>
<PackageReference Include="Avalonia" /> <PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Desktop" /> <PackageReference Include="Avalonia.Desktop" />
@ -40,7 +50,7 @@
<PackageReference Include="OpenTK.Core" /> <PackageReference Include="OpenTK.Core" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" /> <PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" /> <PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
<PackageReference Include="Silk.NET.Vulkan" /> <PackageReference Include="Silk.NET.Vulkan" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" /> <PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
@ -145,4 +155,4 @@
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="Assets\Locales\en_US.json" /> <AdditionalFiles Include="Assets\Locales\en_US.json" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -12,6 +12,11 @@
Click="ToggleFavorite_Click" Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}" Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
<MenuItem
Click="CreateApplicationShortcut_Click"
Header="{locale:Locale GameListContextMenuCreateShortcut}"
IsEnabled="{Binding CreateShortcutEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
<Separator /> <Separator />
<MenuItem <MenuItem
Click="OpenUserSaveDirectory_Click" Click="OpenUserSaveDirectory_Click"
@ -82,4 +87,4 @@
Header="{locale:Locale GameListContextMenuExtractDataLogo}" Header="{locale:Locale GameListContextMenuExtractDataLogo}"
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" /> ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
</MenuItem> </MenuItem>
</MenuFlyout> </MenuFlyout>

View file

@ -337,6 +337,17 @@ namespace Ryujinx.Ava.UI.Controls
} }
} }
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
ApplicationData selectedApplication = viewModel.SelectedApplication;
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
}
}
public async void RunApplication_Click(object sender, RoutedEventArgs args) public async void RunApplication_Click(object sender, RoutedEventArgs args)
{ {
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;

View file

@ -126,17 +126,17 @@
Spacing="5"> Spacing="5">
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding TimePlayed}" Text="{Binding TimePlayedString}"
TextAlignment="Right" TextAlignment="Right"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding LastPlayed, Converter={helpers:NullableDateTimeConverter}}" Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}"
TextAlignment="Right" TextAlignment="Right"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<TextBlock <TextBlock
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Text="{Binding FileSize}" Text="{Binding FileSizeString}"
TextAlignment="Right" TextAlignment="Right"
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </StackPanel>

View file

@ -0,0 +1,31 @@
using Avalonia.Controls;
using Avalonia.Input;
using System;
namespace Ryujinx.Ava.UI.Controls
{
public class SliderScroll : Slider
{
protected override Type StyleKeyOverride => typeof(Slider);
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
var newValue = Value + e.Delta.Y * TickFrequency;
if (newValue < Minimum)
{
Value = Minimum;
}
else if (newValue > Maximum)
{
Value = Maximum;
}
else
{
Value = newValue;
}
e.Handled = true;
}
}
}

View file

@ -0,0 +1,43 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
/// <summary>
/// This <see cref="IValueConverter"/> makes sure that the string "Never" that's returned by <see cref="ValueFormatUtils.FormatDateTime"/> is properly localized in the Avalonia UI.
/// After the Avalonia UI has been made the default and the GTK UI is removed, <see cref="ValueFormatUtils"/> should be updated to directly return a localized string.
/// </summary>
internal class LocalizedNeverConverter : MarkupExtension, IValueConverter
{
private static readonly LocalizedNeverConverter _instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not string valStr)
{
return "";
}
if (valStr == "Never")
{
return LocaleManager.Instance[LocaleKeys.Never];
}
return valStr;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _instance;
}
}
}

View file

@ -1,38 +0,0 @@
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Ryujinx.Ava.Common.Locale;
using System;
using System.Globalization;
namespace Ryujinx.Ava.UI.Helpers
{
internal class NullableDateTimeConverter : MarkupExtension, IValueConverter
{
private static readonly NullableDateTimeConverter _instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return LocaleManager.Instance[LocaleKeys.Never];
}
if (value is DateTime dateTime)
{
return dateTime.ToLocalTime().ToString(culture);
}
throw new NotSupportedException();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _instance;
}
}
}

View file

@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Helpers
public static partial IntPtr SetCursor(IntPtr handle); public static partial IntPtr SetCursor(IntPtr handle);
[LibraryImport("user32.dll")] [LibraryImport("user32.dll")]
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvAndPlane, byte[] pvXorPlane); public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, [In] byte[] pvAndPlane, [In] byte[] pvXorPlane);
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")] [LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
public static partial ushort RegisterClassEx(ref WndClassEx param); public static partial ushort RegisterClassEx(ref WndClassEx param);

View file

@ -13,20 +13,19 @@ namespace Ryujinx.Ava.UI.Models.Generic
public int Compare(ApplicationData x, ApplicationData y) public int Compare(ApplicationData x, ApplicationData y)
{ {
var aValue = x.LastPlayed; DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch;
var bValue = y.LastPlayed;
if (!aValue.HasValue) if (x?.LastPlayed != null)
{ {
aValue = DateTime.UnixEpoch; aValue = x.LastPlayed.Value;
} }
if (!bValue.HasValue) if (y?.LastPlayed != null)
{ {
bValue = DateTime.UnixEpoch; bValue = y.LastPlayed.Value;
} }
return (IsAscending ? 1 : -1) * DateTime.Compare(bValue.Value, aValue.Value); return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue);
} }
} }
} }

View file

@ -0,0 +1,31 @@
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Ava.UI.Models.Generic
{
internal class TimePlayedSortComparer : IComparer<ApplicationData>
{
public TimePlayedSortComparer() { }
public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; }
public bool IsAscending { get; }
public int Compare(ApplicationData x, ApplicationData y)
{
TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero;
if (x?.TimePlayed != null)
{
aValue = x.TimePlayed;
}
if (y?.TimePlayed != null)
{
bValue = y.TimePlayed;
}
return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue);
}
}
}

View file

@ -4,7 +4,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
using System; using Ryujinx.Ui.Common.Helper;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -38,26 +38,7 @@ namespace Ryujinx.Ava.UI.Models
public bool SizeAvailable { get; set; } public bool SizeAvailable { get; set; }
public string SizeString => GetSizeString(); public string SizeString => ValueFormatUtils.FormatFileSize(Size);
private string GetSizeString()
{
const int Scale = 1024;
string[] orders = { "GiB", "MiB", "KiB" };
long max = (long)Math.Pow(Scale, orders.Length);
foreach (string order in orders)
{
if (Size > max)
{
return $"{decimal.Divide(Size, max):##.##} {order}";
}
max /= Scale;
}
return "0 KiB";
}
public SaveModel(SaveDataInfo info) public SaveModel(SaveDataInfo info)
{ {

View file

@ -126,7 +126,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage()); PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem); _virtualFileSystem.ImportTickets(partitionFileSystem);
@ -232,7 +233,8 @@ namespace Ryujinx.Ava.UI.ViewModels
using FileStream containerFile = File.OpenRead(path); using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage()); PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
bool containsDownloadableContent = false; bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem); _virtualFileSystem.ImportTickets(partitionFileSystem);

View file

@ -356,6 +356,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
public string LoadHeading public string LoadHeading
{ {
get => _loadHeading; get => _loadHeading;
@ -928,21 +930,20 @@ namespace Ryujinx.Ava.UI.ViewModels
return SortMode switch return SortMode switch
{ {
#pragma warning disable IDE0055 // Disable formatting #pragma warning disable IDE0055 // Disable formatting
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes) ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes), ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
: SortExpressionComparer<ApplicationData>.Descending(app => app.TimePlayedNum), ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize)
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName), ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite), : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
_ => null, _ => null,
#pragma warning restore IDE0055 #pragma warning restore IDE0055
}; };
@ -1488,7 +1489,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime(); Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path); SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
PrepareLoadScreen(); PrepareLoadScreen();
@ -1547,13 +1548,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{ {
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{ {
if (appMetadata.LastPlayed.HasValue) appMetadata.UpdatePostGame();
{
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
}
appMetadata.LastPlayed = DateTime.UtcNow;
}); });
} }
@ -1696,7 +1691,6 @@ namespace Ryujinx.Ava.UI.ViewModels
} }
} }
} }
#endregion #endregion
} }
} }

View file

@ -170,7 +170,9 @@ namespace Ryujinx.Ava.UI.ViewModels
try try
{ {
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0); var pfs = new PartitionFileSystem();
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null) if (controlNca != null && patchNca != null)
{ {

View file

@ -5,6 +5,7 @@
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models" xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
@ -460,7 +461,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Slider <controls:SliderScroll
Width="130" Width="130"
Maximum="1" Maximum="1"
TickFrequency="0.01" TickFrequency="0.01"
@ -480,7 +481,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Slider <controls:SliderScroll
Width="130" Width="130"
Maximum="2" Maximum="2"
TickFrequency="0.01" TickFrequency="0.01"
@ -604,7 +605,7 @@
<StackPanel <StackPanel
HorizontalAlignment="Center" HorizontalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Slider <controls:SliderScroll
Width="130" Width="130"
Maximum="1" Maximum="1"
TickFrequency="0.01" TickFrequency="0.01"
@ -1083,7 +1084,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Slider <controls:SliderScroll
Width="130" Width="130"
Maximum="1" Maximum="1"
TickFrequency="0.01" TickFrequency="0.01"
@ -1105,7 +1106,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<Slider <controls:SliderScroll
Width="130" Width="130"
Maximum="2" Maximum="2"
TickFrequency="0.01" TickFrequency="0.01"
@ -1125,4 +1126,4 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -23,11 +24,11 @@
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" /> Text="{locale:Locale ControllerSettingsMotionGyroSensitivity}" />
<Slider <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="150" Width="150"
MaxWidth="150" MaxWidth="150"
TickFrequency="0.01" TickFrequency="1"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Maximum="100" Maximum="100"
@ -45,11 +46,11 @@
Margin="0" Margin="0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" /> Text="{locale:Locale ControllerSettingsMotionGyroDeadzone}" />
<Slider <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="150" Width="150"
MaxWidth="150" MaxWidth="150"
TickFrequency="0.01" TickFrequency="1"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
SmallChange="0.01" SmallChange="0.01"
Maximum="100" Maximum="100"
@ -167,4 +168,4 @@
</Grid> </Grid>
</Border> </Border>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -1,6 +1,7 @@
<UserControl <UserControl
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
@ -21,7 +22,7 @@
TextWrapping="WrapWithOverflow" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" /> Text="{locale:Locale ControllerSettingsRumbleStrongMultiplier}" />
<Slider <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="200" Width="200"
TickFrequency="0.01" TickFrequency="0.01"
@ -41,7 +42,7 @@
TextWrapping="WrapWithOverflow" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" /> Text="{locale:Locale ControllerSettingsRumbleWeakMultiplier}" />
<Slider <controls:SliderScroll
Margin="0,-5,0,-5" Margin="0,-5,0,-5"
Width="200" Width="200"
MaxWidth="200" MaxWidth="200"
@ -58,4 +59,4 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
</UserControl> </UserControl>

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -176,6 +177,7 @@
Content="{Binding VolumeStatusText}" Content="{Binding VolumeStatusText}"
IsChecked="{Binding VolumeMuted}" IsChecked="{Binding VolumeMuted}"
IsVisible="{Binding !ShowLoadProgress}" IsVisible="{Binding !ShowLoadProgress}"
PointerWheelChanged="VolumeStatus_OnPointerWheelChanged"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
CornerRadius="0"> CornerRadius="0">
@ -192,7 +194,7 @@
<ToggleSplitButton.Flyout> <ToggleSplitButton.Flyout>
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway"> <Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
<Grid Margin="0"> <Grid Margin="0">
<Slider <controls:SliderScroll
MaxHeight="40" MaxHeight="40"
Width="150" Width="150"
Margin="0" Margin="0"

View file

@ -53,5 +53,20 @@ namespace Ryujinx.Ava.UI.Views.Main
{ {
Window.LoadApplications(); Window.LoadApplications();
} }
private void VolumeStatus_OnPointerWheelChanged(object sender, PointerWheelEventArgs e)
{
// Change the volume by 5% at a time
float newValue = Window.ViewModel.Volume + (float)e.Delta.Y * 0.05f;
Window.ViewModel.Volume = newValue switch
{
< 0 => 0,
> 1 => 1,
_ => newValue,
};
e.Handled = true;
}
} }
} }

View file

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers" xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
@ -50,7 +51,7 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{locale:Locale IconSize}" Text="{locale:Locale IconSize}"
ToolTip.Tip="{locale:Locale IconSizeTooltip}" /> ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
<Slider <controls:SliderScroll
Width="150" Width="150"
Height="35" Height="35"
Margin="5,-10,5,0" Margin="5,-10,5,0"
@ -173,4 +174,4 @@
DockPanel.Dock="Right" DockPanel.Dock="Right"
Text="{locale:Locale CommonSort}" /> Text="{locale:Locale CommonSort}" />
</DockPanel> </DockPanel>
</UserControl> </UserControl>

View file

@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -63,13 +64,13 @@
Maximum="100" /> Maximum="100" />
</StackPanel> </StackPanel>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal"> <StackPanel Margin="10,0,0,0" Orientation="Horizontal">
<Slider Value="{Binding Volume}" <controls:SliderScroll Value="{Binding Volume}"
Margin="250,0,0,0" Margin="250,0,0,0"
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}" ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
Minimum="0" Minimum="0"
Maximum="100" Maximum="100"
SmallChange="5" SmallChange="1"
TickFrequency="5" TickFrequency="1"
IsSnapToTickEnabled="True" IsSnapToTickEnabled="True"
LargeChange="10" LargeChange="10"
Width="350" /> Width="350" />
@ -77,4 +78,4 @@
</StackPanel> </StackPanel>
</Border> </Border>
</ScrollViewer> </ScrollViewer>
</UserControl> </UserControl>

View file

@ -4,6 +4,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
@ -173,7 +174,7 @@
<TextBlock Text="FSR" /> <TextBlock Text="FSR" />
</ComboBoxItem> </ComboBoxItem>
</ComboBox> </ComboBox>
<Slider Value="{Binding ScalingFilterLevel}" <controls:SliderScroll Value="{Binding ScalingFilterLevel}"
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}" ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
MinWidth="150" MinWidth="150"
Margin="10,-3,0,0" Margin="10,-3,0,0"

View file

@ -48,7 +48,7 @@ namespace Ryujinx.Common.Configuration
string appDataPath; string appDataPath;
if (OperatingSystem.IsMacOS()) if (OperatingSystem.IsMacOS())
{ {
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support"); appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
} }
else else
{ {

View file

@ -3,5 +3,6 @@
public enum MultiplayerMode public enum MultiplayerMode
{ {
Disabled, Disabled,
LdnMitm,
} }
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants> <DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup> </PropertyGroup>

View file

@ -74,5 +74,10 @@ namespace Ryujinx.Common.Utilities
{ {
return ConvertIpv4Address(IPAddress.Parse(ipAddress)); return ConvertIpv4Address(IPAddress.Parse(ipAddress));
} }
public static IPAddress ConvertUint(uint ipAddress)
{
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) });
}
} }
} }

View file

@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace Ryujinx.Cpu.AppleHv
{
static class HvCodePatcher
{
private const uint XMask = 0x3f808000u;
private const uint XValue = 0x8000000u;
private const uint ZrIndex = 31u;
public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
{
Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
Vector128<uint> mask = Vector128.Create(XMask);
Vector128<uint> value = Vector128.Create(XValue);
for (int index = 0; index < codeVector.Length; index++)
{
Vector128<uint> v = codeVector[index];
if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
{
int baseIndex = index * 4;
for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
{
ref uint inst = ref codeUint[instIndex];
if ((inst & XMask) != XValue)
{
continue;
}
bool isPair = (inst & (1u << 21)) != 0;
bool isLoad = (inst & (1u << 22)) != 0;
uint rt2 = (inst >> 10) & 0x1fu;
uint rs = (inst >> 16) & 0x1fu;
if (isLoad && rs != ZrIndex)
{
continue;
}
if (!isPair && rt2 != ZrIndex)
{
continue;
}
// Set the ordered flag.
inst |= 1u << 15;
}
}
}
}
}
}

View file

@ -128,21 +128,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
#pragma warning disable IDE0051 // Remove unused private member
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
private void AssertMapped(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
{
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
#pragma warning restore IDE0051
/// <inheritdoc/> /// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags) public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{ {
@ -736,6 +721,24 @@ namespace Ryujinx.Cpu.AppleHv
return (int)(vaSpan / PageSize); return (int)(vaSpan / PageSize);
} }
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{
if (protection.HasFlag(MemoryPermission.Execute))
{
// Some applications use unordered exclusive memory access instructions
// where it is not valid to do so, leading to memory re-ordering that
// makes the code behave incorrectly on some CPUs.
// To work around this, we force all such accesses to be ordered.
using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
}
// TODO
}
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{ {

View file

@ -575,24 +575,17 @@ namespace Ryujinx.Cpu.Jit
} }
} }
#pragma warning disable IDE0051 // Remove unused private member
private ulong GetPhysicalAddress(ulong va)
{
// We return -1L if the virtual address is invalid or unmapped.
if (!ValidateAddress(va) || !IsMapped(va))
{
return ulong.MaxValue;
}
return GetPhysicalAddressInternal(va);
}
#pragma warning restore IDE0051
private ulong GetPhysicalAddressInternal(ulong va) private ulong GetPhysicalAddressInternal(ulong va)
{ {
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask); return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
} }
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{
// TODO
}
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{ {
@ -698,9 +691,5 @@ namespace Ryujinx.Cpu.Jit
/// Disposes of resources used by the memory manager. /// Disposes of resources used by the memory manager.
/// </summary> /// </summary>
protected override void Destroy() => _pageTable.Dispose(); protected override void Destroy() => _pageTable.Dispose();
#pragma warning disable IDE0051 // Remove unused private member
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
#pragma warning restore IDE0051
} }
} }

View file

@ -615,6 +615,12 @@ namespace Ryujinx.Cpu.Jit
return (int)(vaSpan / PageSize); return (int)(vaSpan / PageSize);
} }
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
{
// TODO
}
/// <inheritdoc/> /// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{ {

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -38,6 +38,7 @@ namespace Ryujinx.Graphics.GAL
public readonly bool SupportsShaderBallot; public readonly bool SupportsShaderBallot;
public readonly bool SupportsShaderBarrierDivergence; public readonly bool SupportsShaderBarrierDivergence;
public readonly bool SupportsShaderFloat64; public readonly bool SupportsShaderFloat64;
public readonly bool SupportsTextureGatherOffsets;
public readonly bool SupportsTextureShadowLod; public readonly bool SupportsTextureShadowLod;
public readonly bool SupportsVertexStoreAndAtomics; public readonly bool SupportsVertexStoreAndAtomics;
public readonly bool SupportsViewportIndexVertexTessellation; public readonly bool SupportsViewportIndexVertexTessellation;
@ -92,6 +93,7 @@ namespace Ryujinx.Graphics.GAL
bool supportsShaderBallot, bool supportsShaderBallot,
bool supportsShaderBarrierDivergence, bool supportsShaderBarrierDivergence,
bool supportsShaderFloat64, bool supportsShaderFloat64,
bool supportsTextureGatherOffsets,
bool supportsTextureShadowLod, bool supportsTextureShadowLod,
bool supportsVertexStoreAndAtomics, bool supportsVertexStoreAndAtomics,
bool supportsViewportIndexVertexTessellation, bool supportsViewportIndexVertexTessellation,
@ -142,6 +144,7 @@ namespace Ryujinx.Graphics.GAL
SupportsShaderBallot = supportsShaderBallot; SupportsShaderBallot = supportsShaderBallot;
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence; SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
SupportsShaderFloat64 = supportsShaderFloat64; SupportsShaderFloat64 = supportsShaderFloat64;
SupportsTextureGatherOffsets = supportsTextureGatherOffsets;
SupportsTextureShadowLod = supportsTextureShadowLod; SupportsTextureShadowLod = supportsTextureShadowLod;
SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics; SupportsVertexStoreAndAtomics = supportsVertexStoreAndAtomics;
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation; SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View file

@ -211,6 +211,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
int xCount = (int)_state.State.LineLengthIn; int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount; int yCount = (int)_state.State.LineCount;
_channel.TextureManager.RefreshModifiedTextures();
_3dEngine.CreatePendingSyncs(); _3dEngine.CreatePendingSyncs();
_3dEngine.FlushUboDirty(); _3dEngine.FlushUboDirty();
@ -279,7 +280,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount); bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount); bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
if (completeSource && completeDest) // Try to set the texture data directly,
// but only if we are doing a complete copy,
// and not for block linear to linear copies, since those are typically accessed from the CPU.
if (completeSource && completeDest && !(dstLinear && !srcLinear))
{ {
var target = memoryManager.Physical.TextureCache.FindTexture( var target = memoryManager.Physical.TextureCache.FindTexture(
memoryManager, memoryManager,

View file

@ -651,9 +651,35 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <returns>True if the format is valid, false otherwise</returns> /// <returns>True if the format is valid, false otherwise</returns>
public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format) public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
{ {
encoded |= (isSrgb ? 1u << 19 : 0u); bool isPacked = (encoded & 0x80000000u) != 0;
if (isPacked)
{
encoded &= ~0x80000000u;
}
return _textureFormats.TryGetValue((TextureFormat)encoded, out format); encoded |= isSrgb ? 1u << 19 : 0u;
bool found = _textureFormats.TryGetValue((TextureFormat)encoded, out format);
if (found && isPacked && !format.Format.IsDepthOrStencil())
{
// If the packed flag is set, then the components of the pixel are tightly packed into the
// GPU registers on the shader.
// We can get the same behaviour by aliasing the texture as a format with the same amount of
// bytes per pixel, but only a single or the lowest possible number of components.
format = format.BytesPerPixel switch
{
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),
_ => format,
};
}
return found;
} }
/// <summary> /// <summary>

View file

@ -149,6 +149,7 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
public bool HadPoolOwner { get; private set; } public bool HadPoolOwner { get; private set; }
/// <summary>
/// Physical memory ranges where the texture data is located. /// Physical memory ranges where the texture data is located.
/// </summary> /// </summary>
public MultiRange Range { get; private set; } public MultiRange Range { get; private set; }
@ -1700,7 +1701,6 @@ namespace Ryujinx.Graphics.Gpu.Image
if (Group.Storage == this) if (Group.Storage == this)
{ {
Group.Unmapped(); Group.Unmapped();
Group.ClearModified(unmapRange); Group.ClearModified(unmapRange);
} }
} }

View file

@ -107,8 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
// Any texture that has been unmapped at any point or is partially unmapped // Any texture that has been unmapped at any point or is partially unmapped
// should update their pool references after the remap completes. // should update their pool references after the remap completes.
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
foreach (var texture in _partiallyMappedTextures) foreach (var texture in _partiallyMappedTextures)
{ {
texture.UpdatePoolMappings(); texture.UpdatePoolMappings();
@ -735,9 +733,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
if (overlap.IsView) if (overlap.IsView)
{ {
overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ? overlapCompatibility = TextureViewCompatibility.CopyOnly;
TextureViewCompatibility.Incompatible :
TextureViewCompatibility.CopyOnly;
} }
else else
{ {
@ -815,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Texture overlap = _textureOverlaps[index]; Texture overlap = _textureOverlaps[index];
OverlapInfo oInfo = _overlapInfo[index]; OverlapInfo oInfo = _overlapInfo[index];
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias) if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
{ {
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility)) if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
{ {

View file

@ -2,6 +2,8 @@ using Ryujinx.Common;
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture; using Ryujinx.Graphics.Texture;
using System; using System;
using System.Diagnostics;
using System.Numerics;
namespace Ryujinx.Graphics.Gpu.Image namespace Ryujinx.Graphics.Gpu.Image
{ {
@ -226,7 +228,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
// D32F and R32F texture have the same representation internally, // D32F and R32F texture have the same representation internally,
// however the R32F format is used to sample from depth textures. // however the R32F format is used to sample from depth textures.
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias)) if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias))
{ {
return TextureMatchQuality.FormatAlias; return TextureMatchQuality.FormatAlias;
} }
@ -239,14 +241,8 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
return TextureMatchQuality.FormatAlias; return TextureMatchQuality.FormatAlias;
} }
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm) lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{
return TextureMatchQuality.FormatAlias;
}
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
{ {
return TextureMatchQuality.FormatAlias; return TextureMatchQuality.FormatAlias;
} }
@ -345,7 +341,20 @@ namespace Ryujinx.Graphics.Gpu.Image
if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo)) if (lhs.FormatInfo.BytesPerPixel != rhs.FormatInfo.BytesPerPixel && IsIncompatibleFormatAliasingAllowed(lhs.FormatInfo, rhs.FormatInfo))
{ {
alignedWidthMatches = lhsSize.Width * lhs.FormatInfo.BytesPerPixel == rhsSize.Width * rhs.FormatInfo.BytesPerPixel; // If the formats are incompatible, but the texture strides match,
// we might allow them to be copy compatible depending on the format.
// The strides are aligned because the format with higher bytes per pixel
// might need a bit of padding at the end due to one width not being a multiple of the other.
Debug.Assert((1 << BitOperations.Log2((uint)lhs.FormatInfo.BytesPerPixel)) == lhs.FormatInfo.BytesPerPixel);
Debug.Assert((1 << BitOperations.Log2((uint)rhs.FormatInfo.BytesPerPixel)) == rhs.FormatInfo.BytesPerPixel);
int alignment = Math.Max(lhs.FormatInfo.BytesPerPixel, rhs.FormatInfo.BytesPerPixel);
int lhsStride = BitUtils.AlignUp(lhsSize.Width * lhs.FormatInfo.BytesPerPixel, alignment);
int rhsStride = BitUtils.AlignUp(rhsSize.Width * rhs.FormatInfo.BytesPerPixel, alignment);
alignedWidthMatches = lhsStride == rhsStride;
} }
TextureViewCompatibility result = TextureViewCompatibility.Full; TextureViewCompatibility result = TextureViewCompatibility.Full;
@ -374,6 +383,13 @@ namespace Ryujinx.Graphics.Gpu.Image
return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible; return stride == rhs.Stride ? TextureViewCompatibility.CopyOnly : TextureViewCompatibility.LayoutIncompatible;
} }
else if (lhs.Target.IsMultisample() != rhs.Target.IsMultisample() && alignedWidthMatches && lhsAlignedSize.Height == rhsAlignedSize.Height)
{
// Copy between multisample and non-multisample textures with mismatching size is allowed,
// as long aligned size matches.
return TextureViewCompatibility.CopyOnly;
}
else else
{ {
return TextureViewCompatibility.LayoutIncompatible; return TextureViewCompatibility.LayoutIncompatible;
@ -625,12 +641,27 @@ namespace Ryujinx.Graphics.Gpu.Image
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil()) if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
{ {
return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias);
if (matchQuality == TextureMatchQuality.Perfect)
{ {
TextureMatchQuality.Perfect => TextureViewCompatibility.Full, return TextureViewCompatibility.Full;
TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias, }
_ => TextureViewCompatibility.Incompatible, else if (matchQuality == TextureMatchQuality.FormatAlias)
}; {
return TextureViewCompatibility.FormatAlias;
}
else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format))
{
return TextureViewCompatibility.CopyOnly;
}
else
{
return TextureViewCompatibility.Incompatible;
}
} }
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps)) if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
@ -659,6 +690,30 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureViewCompatibility.Incompatible; return TextureViewCompatibility.Incompatible;
} }
/// <summary>
/// Checks if it's valid to alias a color format as a depth format.
/// </summary>
/// <param name="lhsFormat">Source format to be checked</param>
/// <param name="rhsFormat">Target format to be checked</param>
/// <returns>True if it's valid to alias the formats</returns>
private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat)
{
return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) ||
(lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm);
}
/// <summary>
/// Checks if it's valid to alias a depth format as a color format.
/// </summary>
/// <param name="lhsFormat">Source format to be checked</param>
/// <param name="rhsFormat">Target format to be checked</param>
/// <returns>True if it's valid to alias the formats</returns>
private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat)
{
return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) ||
(lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm);
}
/// <summary> /// <summary>
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed, /// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
/// using copy dependencies. /// using copy dependencies.
@ -678,7 +733,8 @@ namespace Ryujinx.Graphics.Gpu.Image
(lhsFormat, rhsFormat) = (rhsFormat, lhsFormat); (lhsFormat, rhsFormat) = (rhsFormat, lhsFormat);
} }
return lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm; return (lhsFormat.Format == Format.R8G8B8A8Unorm && rhsFormat.Format == Format.R32G32B32A32Float) ||
(lhsFormat.Format == Format.R8Unorm && rhsFormat.Format == Format.R8G8B8A8Unorm);
} }
/// <summary> /// <summary>

View file

@ -992,26 +992,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// The action to perform when a memory tracking handle is flipped to dirty.
/// This notifies overlapping textures that the memory needs to be synchronized.
/// </summary>
/// <param name="groupHandle">The handle that a dirty flag was set on</param>
private void DirtyAction(TextureGroupHandle groupHandle)
{
// Notify all textures that belong to this handle.
Storage.SignalGroupDirty();
lock (groupHandle.Overlaps)
{
foreach (Texture overlap in groupHandle.Overlaps)
{
overlap.SignalGroupDirty();
}
}
}
/// <summary> /// <summary>
/// Generate a CpuRegionHandle for a given address and size range in CPU VA. /// Generate a CpuRegionHandle for a given address and size range in CPU VA.
/// </summary> /// </summary>
@ -1083,11 +1063,6 @@ namespace Ryujinx.Graphics.Gpu.Image
views, views,
result.ToArray()); result.ToArray());
foreach (RegionHandle handle in result)
{
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
}
return groupHandle; return groupHandle;
} }
@ -1359,11 +1334,6 @@ namespace Ryujinx.Graphics.Gpu.Image
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles); var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
foreach (RegionHandle handle in cpuRegionHandles)
{
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
}
handles = new TextureGroupHandle[] { groupHandle }; handles = new TextureGroupHandle[] { groupHandle };
} }
else else
@ -1619,6 +1589,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified) if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
{ {
handle.Modified = false; handle.Modified = false;
handle.DeferredCopy = null;
Storage.SignalModifiedDirty(); Storage.SignalModifiedDirty();
lock (handle.Overlaps) lock (handle.Overlaps)
@ -1659,6 +1630,12 @@ namespace Ryujinx.Graphics.Gpu.Image
return; return;
} }
// If size is zero, we have nothing to flush.
if (size == 0)
{
return;
}
// There is a small gap here where the action is removed but _actionRegistered is still 1. // There is a small gap here where the action is removed but _actionRegistered is still 1.
// In this case it will skip registering the action, but here we are already handling it, // In this case it will skip registering the action, but here we are already handling it,
// so there shouldn't be any issue as it's the same handler for all actions. // so there shouldn't be any issue as it's the same handler for all actions.

View file

@ -152,6 +152,32 @@ namespace Ryujinx.Graphics.Gpu.Image
// Linear textures are presumed to be used for readback initially. // Linear textures are presumed to be used for readback initially.
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement; _flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
} }
foreach (RegionHandle handle in handles)
{
handle.RegisterDirtyEvent(DirtyAction);
}
}
/// <summary>
/// The action to perform when a memory tracking handle is flipped to dirty.
/// This notifies overlapping textures that the memory needs to be synchronized.
/// </summary>
private void DirtyAction()
{
// Notify all textures that belong to this handle.
_group.Storage.SignalGroupDirty();
lock (Overlaps)
{
foreach (Texture overlap in Overlaps)
{
overlap.SignalGroupDirty();
}
}
DeferredCopy = null;
} }
/// <summary> /// <summary>
@ -449,7 +475,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public void DeferCopy(TextureGroupHandle copyFrom) public void DeferCopy(TextureGroupHandle copyFrom)
{ {
Modified = false; Modified = false;
DeferredCopy = copyFrom; DeferredCopy = copyFrom;
_group.Storage.SignalGroupDirty(); _group.Storage.SignalGroupDirty();
@ -506,7 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
existing.Other.Handle.CreateCopyDependency(this); existing.Other.Handle.CreateCopyDependency(this);
if (copyToOther) if (copyToOther && Modified)
{ {
existing.Other.Handle.DeferCopy(this); existing.Other.Handle.DeferCopy(this);
} }
@ -550,10 +575,10 @@ namespace Ryujinx.Graphics.Gpu.Image
if (fromHandle != null) if (fromHandle != null)
{ {
// Only copy if the copy texture is still modified. // Only copy if the copy texture is still modified.
// It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush. // DeferredCopy will be set to null if new data is written from CPU (see the DirtyAction method).
// It will also set as unmodified if a copy is deferred to it. // It will also set as unmodified if a copy is deferred to it.
shouldCopy = fromHandle.Modified; shouldCopy = true;
if (fromHandle._bindCount == 0) if (fromHandle._bindCount == 0)
{ {

View file

@ -20,8 +20,10 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly Texture[] _rtColors; private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors; private readonly ITexture[] _rtHostColors;
private readonly bool[] _rtColorsBound;
private Texture _rtDepthStencil; private Texture _rtDepthStencil;
private ITexture _rtHostDs; private ITexture _rtHostDs;
private bool _rtDsBound;
public int ClipRegionWidth { get; private set; } public int ClipRegionWidth { get; private set; }
public int ClipRegionHeight { get; private set; } public int ClipRegionHeight { get; private set; }
@ -51,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_rtColors = new Texture[Constants.TotalRenderTargets]; _rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets]; _rtHostColors = new ITexture[Constants.TotalRenderTargets];
_rtColorsBound = new bool[Constants.TotalRenderTargets];
} }
/// <summary> /// <summary>
@ -154,7 +157,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_rtColors[index] != color) if (_rtColors[index] != color)
{ {
_rtColors[index]?.SignalModifying(false); if (_rtColorsBound[index])
{
_rtColors[index]?.SignalModifying(false);
}
else
{
_rtColorsBound[index] = true;
}
if (color != null) if (color != null)
{ {
@ -180,7 +190,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_rtDepthStencil != depthStencil) if (_rtDepthStencil != depthStencil)
{ {
_rtDepthStencil?.SignalModifying(false); if (_rtDsBound)
{
_rtDepthStencil?.SignalModifying(false);
}
else
{
_rtDsBound = true;
}
if (depthStencil != null) if (depthStencil != null)
{ {
@ -413,21 +430,45 @@ namespace Ryujinx.Graphics.Gpu.Image
{ {
bool anyChanged = false; bool anyChanged = false;
if (_rtHostDs != _rtDepthStencil?.HostTexture) Texture dsTexture = _rtDepthStencil;
{ ITexture hostDsTexture = null;
_rtHostDs = _rtDepthStencil?.HostTexture;
if (dsTexture != null)
{
hostDsTexture = dsTexture.HostTexture;
if (!_rtDsBound)
{
dsTexture.SignalModifying(true);
_rtDsBound = true;
}
}
if (_rtHostDs != hostDsTexture)
{
_rtHostDs = hostDsTexture;
anyChanged = true; anyChanged = true;
} }
for (int index = 0; index < _rtColors.Length; index++) for (int index = 0; index < _rtColors.Length; index++)
{ {
ITexture hostTexture = _rtColors[index]?.HostTexture; Texture texture = _rtColors[index];
ITexture hostTexture = null;
if (texture != null)
{
hostTexture = texture.HostTexture;
if (!_rtColorsBound[index])
{
texture.SignalModifying(true);
_rtColorsBound[index] = true;
}
}
if (_rtHostColors[index] != hostTexture) if (_rtHostColors[index] != hostTexture)
{ {
_rtHostColors[index] = hostTexture; _rtHostColors[index] = hostTexture;
anyChanged = true; anyChanged = true;
} }
} }
@ -452,6 +493,31 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs); _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
} }
/// <summary>
/// Marks all currently bound render target textures as modified, and also makes them be set as modified again on next use.
/// </summary>
public void RefreshModifiedTextures()
{
Texture dsTexture = _rtDepthStencil;
if (dsTexture != null && _rtDsBound)
{
dsTexture.SignalModifying(false);
_rtDsBound = false;
}
for (int index = 0; index < _rtColors.Length; index++)
{
Texture texture = _rtColors[index];
if (texture != null && _rtColorsBound[index])
{
texture.SignalModifying(false);
_rtColorsBound[index] = false;
}
}
}
/// <summary> /// <summary>
/// Forces the texture and sampler pools to be re-loaded from the cache on next use. /// Forces the texture and sampler pools to be re-loaded from the cache on next use.
/// </summary> /// </summary>
@ -488,11 +554,19 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int i = 0; i < _rtColors.Length; i++) for (int i = 0; i < _rtColors.Length; i++)
{ {
_rtColors[i]?.DecrementReferenceCount(); if (_rtColorsBound[i])
{
_rtColors[i]?.DecrementReferenceCount();
}
_rtColors[i] = null; _rtColors[i] = null;
} }
_rtDepthStencil?.DecrementReferenceCount(); if (_rtDsBound)
{
_rtDepthStencil?.DecrementReferenceCount();
}
_rtDepthStencil = null; _rtDepthStencil = null;
} }
} }

View file

@ -430,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo)) if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
{ {
if (gpuVa != 0 && (int)format > 0) if (gpuVa != 0 && format != 0)
{ {
Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb})."); Logger.Error?.Print(LogClass.Gpu, $"Invalid texture format 0x{format:X} (sRGB: {srgb}).");
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

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

View file

@ -186,6 +186,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat; public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
public bool QueryHostSupportsTextureGatherOffsets() => _context.Capabilities.SupportsTextureGatherOffsets;
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod; public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback; public bool QueryHostSupportsTransformFeedback() => _context.Capabilities.SupportsTransformFeedback;

View file

@ -37,10 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// <returns>The incremented value of the syncpoint</returns> /// <returns>The incremented value of the syncpoint</returns>
public uint IncrementSyncpoint(uint id) public uint IncrementSyncpoint(uint id)
{ {
if (id >= MaxHardwareSyncpoints) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].Increment(); return _syncpoints[id].Increment();
} }
@ -53,10 +50,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// <returns>The value of the syncpoint</returns> /// <returns>The value of the syncpoint</returns>
public uint GetSyncpointValue(uint id) public uint GetSyncpointValue(uint id)
{ {
if (id >= MaxHardwareSyncpoints) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].Value; return _syncpoints[id].Value;
} }
@ -72,10 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns> /// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns>
public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action<SyncpointWaiterHandle> callback) public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action<SyncpointWaiterHandle> callback)
{ {
if (id >= MaxHardwareSyncpoints) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
{
throw new ArgumentOutOfRangeException(nameof(id));
}
return _syncpoints[id].RegisterCallback(threshold, callback); return _syncpoints[id].RegisterCallback(threshold, callback);
} }
@ -88,10 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception> /// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation) public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation)
{ {
if (id >= MaxHardwareSyncpoints) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_syncpoints[id].UnregisterCallback(waiterInformation); _syncpoints[id].UnregisterCallback(waiterInformation);
} }
@ -107,10 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// <returns>True if timed out</returns> /// <returns>True if timed out</returns>
public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout) public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout)
{ {
if (id >= MaxHardwareSyncpoints) ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
{
throw new ArgumentOutOfRangeException(nameof(id));
}
// TODO: Remove this when GPU channel scheduling will be implemented. // TODO: Remove this when GPU channel scheduling will be implemented.
if (timeout == Timeout.InfiniteTimeSpan) if (timeout == Timeout.InfiniteTimeSpan)

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
return to; return to;
} }
private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height) public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
{ {
int dstWidth = width; int dstWidth = width;
int dstHeight = height; int dstHeight = height;
@ -445,8 +445,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
} }
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
return to;
} }
private void EnsurePbo(TextureView view) private void EnsurePbo(TextureView view)

View file

@ -140,6 +140,28 @@ namespace Ryujinx.Graphics.OpenGL.Image
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel); int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels); _renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
} }
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
for (int level = 0; level < levels; level++)
{
int srcWidth = Math.Max(1, Width >> level);
int srcHeight = Math.Max(1, Height >> level);
int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level));
int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level));
int minWidth = Math.Min(srcWidth, dstWidth);
int minHeight = Math.Min(srcHeight, dstHeight);
for (int layer = 0; layer < layers; layer++)
{
_renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight);
}
}
}
else else
{ {
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel); _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
@ -169,6 +191,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
{ {
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); _renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
} }
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
{
int minWidth = Math.Min(Width, destinationView.Width);
int minHeight = Math.Min(Height, destinationView.Height);
_renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight);
}
else else
{ {
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); _renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);

View file

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

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -339,6 +339,15 @@ namespace Ryujinx.Graphics.Shader
return true; return true;
} }
/// <summary>
/// Queries host GPU texture gather with multiple offsets support.
/// </summary>
/// <returns>True if the GPU and driver supports texture gather offsets, false otherwise</returns>
bool QueryHostSupportsTextureGatherOffsets()
{
return true;
}
/// <summary> /// <summary>
/// Queries host GPU texture shadow LOD support. /// Queries host GPU texture shadow LOD support.
/// </summary> /// </summary>

View file

@ -766,7 +766,10 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset; flags |= offset == TexOffset.Ptp ? TextureFlags.Offsets : TextureFlags.Offset;
} }
sourcesList.Add(Const((int)component)); if (!hasDepthCompare)
{
sourcesList.Add(Const((int)component));
}
Operand[] sources = sourcesList.ToArray(); Operand[] sources = sourcesList.ToArray();
Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)]; Operand[] dests = new Operand[BitOperations.PopCount((uint)componentMask)];

View file

@ -69,10 +69,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public Operand GetDest(int index) public Operand GetDest(int index)
{ {
if (index != 0) ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _dest; return _dest;
} }

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue; continue;
} }
if (bindlessHandle.AsgOp is not Operation handleCombineOp) if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{ {
continue; continue;
} }
@ -199,9 +199,64 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
} }
} }
private static bool TryGetOperation(INode asgOp, out Operation outOperation)
{
if (asgOp is PhiNode phi)
{
// If we have a phi, let's check if all inputs are effectively the same value.
// If so, we can "see through" the phi and pick any of the inputs (since they are all the same).
Operand firstSrc = phi.GetSource(0);
for (int index = 1; index < phi.SourcesCount; index++)
{
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
{
outOperation = null;
return false;
}
}
asgOp = firstSrc.AsgOp;
}
if (asgOp is Operation operation)
{
outOperation = operation;
return true;
}
outOperation = null;
return false;
}
private static bool IsSameOperand(Operand x, Operand y)
{
if (x.Type == y.Type && x.Type == OperandType.LocalVariable)
{
return x.AsgOp is Operation xOp &&
y.AsgOp is Operation yOp &&
xOp.Inst == Instruction.BitwiseOr &&
yOp.Inst == Instruction.BitwiseOr &&
AreBothEqualConstantBuffers(xOp.GetSource(0), yOp.GetSource(0)) &&
AreBothEqualConstantBuffers(xOp.GetSource(1), yOp.GetSource(1));
}
return false;
}
private static bool AreBothEqualConstantBuffers(Operand x, Operand y)
{
return x.Type == y.Type && x.Value == y.Value && x.Type == OperandType.ConstantBuffer;
}
private static Operand GetSourceForMaskedHandle(Operation asgOp, uint mask) private static Operand GetSourceForMaskedHandle(Operation asgOp, uint mask)
{ {
// Assume it was already checked that the operation is bitwise AND. // Assume it was already checked that the operation is bitwise AND.
Operand src0 = asgOp.GetSource(0); Operand src0 = asgOp.GetSource(0);
Operand src1 = asgOp.GetSource(1); Operand src1 = asgOp.GetSource(1);
@ -210,6 +265,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
// We can't check if the mask matches here as both operands are from a constant buffer. // We can't check if the mask matches here as both operands are from a constant buffer.
// Be optimistic and assume it matches. Avoid constant buffer 1 as official drivers // Be optimistic and assume it matches. Avoid constant buffer 1 as official drivers
// uses this one to store compiler constants. // uses this one to store compiler constants.
return src0.GetCbufSlot() == 1 ? src1 : src0; return src0.GetCbufSlot() == 1 ? src1 : src0;
} }
else if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.Constant) else if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.Constant)

View file

@ -303,7 +303,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0; bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0; bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
bool hasInvalidOffset = (hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset(); bool needsOffsetsEmulation = hasOffsets && !gpuAccessor.QueryHostSupportsTextureGatherOffsets();
bool hasInvalidOffset = needsOffsetsEmulation || ((hasOffset || hasOffsets) && !gpuAccessor.QueryHostSupportsNonConstantTextureOffset());
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0; bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
@ -402,11 +404,14 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
offsets[index] = offset; offsets[index] = offset;
} }
hasInvalidOffset &= !areAllOffsetsConstant; if (!needsOffsetsEmulation)
if (!hasInvalidOffset)
{ {
return node; hasInvalidOffset &= !areAllOffsetsConstant;
if (!hasInvalidOffset)
{
return node;
}
} }
if (hasLodBias) if (hasLodBias)
@ -434,13 +439,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
LinkedListNode<INode> oldNode = node; LinkedListNode<INode> oldNode = node;
if (isGather && !isShadow) if (isGather && !isShadow && hasOffsets)
{ {
Operand[] newSources = new Operand[sources.Length]; Operand[] newSources = new Operand[sources.Length];
sources.CopyTo(newSources, 0); sources.CopyTo(newSources, 0);
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage); Operand[] texSizes = InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount);
int destIndex = 0; int destIndex = 0;
@ -455,7 +460,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
{ {
Operand offset = Local(); Operand offset = Local();
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)]; Operand intOffset = offsets[index + compIndex * coordsCount];
node.List.AddBefore(node, new Operation( node.List.AddBefore(node, new Operation(
Instruction.FP32 | Instruction.Divide, Instruction.FP32 | Instruction.Divide,
@ -478,7 +483,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
texOp.Format, texOp.Format,
texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets), texOp.Flags & ~(TextureFlags.Offset | TextureFlags.Offsets),
texOp.Binding, texOp.Binding,
1, 1 << 3, // W component: i=0, j=0
new[] { dests[destIndex++] }, new[] { dests[destIndex++] },
newSources); newSources);
@ -502,7 +507,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
} }
else else
{ {
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage); Operand[] texSizes = isGather
? InsertTextureBaseSize(node, texOp, bindlessHandle, coordsCount)
: InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount, stage);
for (int index = 0; index < coordsCount; index++) for (int index = 0; index < coordsCount; index++)
{ {
@ -549,6 +556,43 @@ namespace Ryujinx.Graphics.Shader.Translation.Transforms
return node; return node;
} }
private static Operand[] InsertTextureBaseSize(
LinkedListNode<INode> node,
TextureOperation texOp,
Operand bindlessHandle,
int coordsCount)
{
Operand[] texSizes = new Operand[coordsCount];
for (int index = 0; index < coordsCount; index++)
{
texSizes[index] = Local();
Operand[] texSizeSources;
if (bindlessHandle != null)
{
texSizeSources = new Operand[] { bindlessHandle, Const(0) };
}
else
{
texSizeSources = new Operand[] { Const(0) };
}
node.List.AddBefore(node, new TextureOperation(
Instruction.TextureQuerySize,
texOp.Type,
texOp.Format,
texOp.Flags,
texOp.Binding,
index,
new[] { texSizes[index] },
texSizeSources));
}
return texSizes;
}
private static Operand[] InsertTextureLod( private static Operand[] InsertTextureLod(
LinkedListNode<INode> node, LinkedListNode<INode> node,
TextureOperation texOp, TextureOperation texOp,

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
@ -102,11 +103,11 @@ namespace Ryujinx.Graphics.Texture.Utils
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RgbaColor32 operator <<(RgbaColor32 x, int shift) public static RgbaColor32 operator <<(RgbaColor32 x, [ConstantExpected] byte shift)
{ {
if (Sse2.IsSupported) if (Sse2.IsSupported)
{ {
return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, (byte)shift)); return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, shift));
} }
else else
{ {
@ -115,11 +116,11 @@ namespace Ryujinx.Graphics.Texture.Utils
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RgbaColor32 operator >>(RgbaColor32 x, int shift) public static RgbaColor32 operator >>(RgbaColor32 x, [ConstantExpected] byte shift)
{ {
if (Sse2.IsSupported) if (Sse2.IsSupported)
{ {
return new RgbaColor32(Sse2.ShiftRightLogical(x._color, (byte)shift)); return new RgbaColor32(Sse2.ShiftRightLogical(x._color, shift));
} }
else else
{ {

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -189,7 +189,7 @@ namespace Ryujinx.Graphics.Vulkan
PipelineStageFlags.AllCommandsBit, PipelineStageFlags.AllCommandsBit,
0, 0,
1, 1,
new ReadOnlySpan<MemoryBarrier>(memoryBarrier), new ReadOnlySpan<MemoryBarrier>(in memoryBarrier),
0, 0,
ReadOnlySpan<BufferMemoryBarrier>.Empty, ReadOnlySpan<BufferMemoryBarrier>.Empty,
0, 0,

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View file

@ -211,6 +211,13 @@ namespace Ryujinx.Graphics.Vulkan
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel); int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels); _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
} }
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
}
else else
{ {
TextureCopy.Copy( TextureCopy.Copy(
@ -260,6 +267,10 @@ namespace Ryujinx.Graphics.Vulkan
{ {
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1); _gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
} }
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
{
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
}
else else
{ {
TextureCopy.Copy( TextureCopy.Copy(

View file

@ -33,9 +33,5 @@ namespace Ryujinx.Graphics.Vulkan
public VulkanException(string message, Exception innerException) : base(message, innerException) public VulkanException(string message, Exception innerException) : base(message, innerException)
{ {
} }
protected VulkanException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
} }
} }

View file

@ -605,6 +605,7 @@ namespace Ryujinx.Graphics.Vulkan
supportsShaderBallot: false, supportsShaderBallot: false,
supportsShaderBarrierDivergence: Vendor != Vendor.Intel, supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
supportsShaderFloat64: Capabilities.SupportsShaderFloat64, supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
supportsTextureGatherOffsets: features2.Features.ShaderImageGatherExtended && !IsMoltenVk,
supportsTextureShadowLod: false, supportsTextureShadowLod: false,
supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics, supportsVertexStoreAndAtomics: features2.Features.VertexPipelineStoresAndAtomics,
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex, supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,

View file

@ -35,8 +35,6 @@ namespace Ryujinx.HLE.Exceptions
Request = context.Request; Request = context.Request;
} }
protected ServiceNotImplementedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
public override string Message public override string Message
{ {
get get

View file

@ -198,7 +198,7 @@ namespace Ryujinx.HLE.FileSystem
{ {
using var ncaFile = new UniqueRef<IFile>(); using var ncaFile = new UniqueRef<IFile>();
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read); fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage()); var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
if (nca.Header.ContentType != NcaContentType.Meta) if (nca.Header.ContentType != NcaContentType.Meta)
{ {
@ -210,7 +210,7 @@ namespace Ryujinx.HLE.FileSystem
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel); using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
using var cnmtFile = new UniqueRef<IFile>(); using var cnmtFile = new UniqueRef<IFile>();
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read); pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
var cnmt = new Cnmt(cnmtFile.Get.AsStream()); var cnmt = new Cnmt(cnmtFile.Get.AsStream());
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId) if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
@ -220,7 +220,7 @@ namespace Ryujinx.HLE.FileSystem
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower(); string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
AddAocItem(cnmt.TitleId, containerPath, $"{ncaId}.nca", true); AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
} }
} }
@ -238,7 +238,8 @@ namespace Ryujinx.HLE.FileSystem
if (!mergedToContainer) if (!mergedToContainer)
{ {
using FileStream fileStream = File.OpenRead(containerPath); using FileStream fileStream = File.OpenRead(containerPath);
using PartitionFileSystem partitionFileSystem = new(fileStream.AsStorage()); using PartitionFileSystem partitionFileSystem = new();
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
_virtualFileSystem.ImportTickets(partitionFileSystem); _virtualFileSystem.ImportTickets(partitionFileSystem);
} }
@ -259,17 +260,17 @@ namespace Ryujinx.HLE.FileSystem
{ {
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read); var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
using var ncaFile = new UniqueRef<IFile>(); using var ncaFile = new UniqueRef<IFile>();
PartitionFileSystem pfs;
switch (Path.GetExtension(aoc.ContainerPath)) switch (Path.GetExtension(aoc.ContainerPath))
{ {
case ".xci": case ".xci":
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure); var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read); xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
break; break;
case ".nsp": case ".nsp":
pfs = new PartitionFileSystem(file.AsStorage()); var pfs = new PartitionFileSystem();
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read); pfs.Initialize(file.AsStorage());
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
break; break;
default: default:
return false; // Print error? return false; // Print error?
@ -419,10 +420,7 @@ namespace Ryujinx.HLE.FileSystem
if (locationList != null) if (locationList != null)
{ {
if (locationList.Contains(entry)) locationList.Remove(entry);
{
locationList.Remove(entry);
}
locationList.AddLast(entry); locationList.AddLast(entry);
} }
@ -606,11 +604,11 @@ namespace Ryujinx.HLE.FileSystem
if (filesystem.FileExists($"{path}/00")) if (filesystem.FileExists($"{path}/00"))
{ {
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode); filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure();
} }
else else
{ {
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode); filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure();
} }
return file.Release(); return file.Release();

View file

@ -7,6 +7,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSrv; using LibHac.FsSrv;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Ncm; using LibHac.Ncm;
using LibHac.Sdmmc;
using LibHac.Spl; using LibHac.Spl;
using LibHac.Tools.Es; using LibHac.Tools.Es;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.FileSystem
public KeySet KeySet { get; private set; } public KeySet KeySet { get; private set; }
public EmulatedGameCard GameCard { get; private set; } public EmulatedGameCard GameCard { get; private set; }
public EmulatedSdCard SdCard { get; private set; } public SdmmcApi SdCard { get; private set; }
public ModLoader ModLoader { get; private set; } public ModLoader ModLoader { get; private set; }
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid; private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
@ -198,15 +199,15 @@ namespace Ryujinx.HLE.FileSystem
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(); fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
GameCard = fsServerObjects.GameCard; GameCard = fsServerObjects.GameCard;
SdCard = fsServerObjects.SdCard; SdCard = fsServerObjects.Sdmmc;
SdCard.SetSdCardInsertionStatus(true); SdCard.SetSdCardInserted(true);
var fsServerConfig = new FileSystemServerConfig var fsServerConfig = new FileSystemServerConfig
{ {
DeviceOperator = fsServerObjects.DeviceOperator,
ExternalKeySet = KeySet.ExternalKeySet, ExternalKeySet = KeySet.ExternalKeySet,
FsCreators = fsServerObjects.FsCreators, FsCreators = fsServerObjects.FsCreators,
StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory,
RandomGenerator = randomGenerator, RandomGenerator = randomGenerator,
}; };
@ -263,7 +264,16 @@ namespace Ryujinx.HLE.FileSystem
if (result.IsSuccess()) if (result.IsSuccess())
{ {
Ticket ticket = new(ticketFile.Get.AsStream()); // When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle
// of the hashed portion (usually the first 0x200 bytes) of the file and end the read after
// the end of the hashed portion, so we read the ticket file using a single read.
byte[] ticketData = new byte[0x2C0];
result = ticketFile.Get.Read(out long bytesRead, 0, ticketData);
if (result.IsFailure() || bytesRead != ticketData.Length)
continue;
Ticket ticket = new(new MemoryStream(ticketData));
var titleKey = ticket.GetTitleKey(KeySet); var titleKey = ticket.GetTitleKey(KeySet);
if (titleKey != null) if (titleKey != null)

View file

@ -101,7 +101,7 @@ namespace Ryujinx.HLE
/// <summary> /// <summary>
/// Control if the guest application should be told that there is a Internet connection available. /// Control if the guest application should be told that there is a Internet connection available.
/// </summary> /// </summary>
internal readonly bool EnableInternetAccess; public bool EnableInternetAccess { internal get; set; }
/// <summary> /// <summary>
/// Control LibHac's integrity check level. /// Control LibHac's integrity check level.

View file

@ -0,0 +1,46 @@
using Ryujinx.Memory;
using System;
namespace Ryujinx.HLE.HOS.Kernel.Memory
{
[Flags]
enum KMemoryPermission : uint
{
None = 0,
UserMask = Read | Write | Execute,
Mask = uint.MaxValue,
Read = 1 << 0,
Write = 1 << 1,
Execute = 1 << 2,
DontCare = 1 << 28,
ReadAndWrite = Read | Write,
ReadAndExecute = Read | Execute,
}
static class KMemoryPermissionExtensions
{
public static MemoryPermission Convert(this KMemoryPermission permission)
{
MemoryPermission output = MemoryPermission.None;
if (permission.HasFlag(KMemoryPermission.Read))
{
output = MemoryPermission.Read;
}
if (permission.HasFlag(KMemoryPermission.Write))
{
output |= MemoryPermission.Write;
}
if (permission.HasFlag(KMemoryPermission.Execute))
{
output |= MemoryPermission.Execute;
}
return output;
}
}
}

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