mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-01-12 13:49:12 +00:00
Merge branch 'master' into 4304-missing-updates
This commit is contained in:
commit
dd738df1fc
110 changed files with 975 additions and 610 deletions
|
@ -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
|
||||||
|
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -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
|
||||||
|
|
4
.github/workflows/flatpak.yml
vendored
4
.github/workflows/flatpak.yml
vendored
|
@ -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
|
||||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -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: |
|
||||||
|
|
|
@ -21,16 +21,16 @@
|
||||||
<PackageVersion Include="LibHac" Version="0.19.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="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" />
|
||||||
|
@ -45,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>
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "7.0.200",
|
"version": "8.0.100",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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..];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -716,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;
|
||||||
|
|
|
@ -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...",
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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,9 +87,4 @@
|
||||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
|
||||||
Click="CreateApplicationShortcut_Click"
|
|
||||||
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
|
||||||
IsEnabled="{Binding CreateShortcutEnabled}"
|
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
|
||||||
</MenuFlyout>
|
</MenuFlyout>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal file
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal file
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -930,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.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
|
||||||
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
|
|
||||||
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TimePlayedNum),
|
|
||||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
||||||
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
|
||||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||||
|
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||||
|
ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
|
||||||
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
||||||
|
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize)
|
||||||
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
|
||||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||||
|
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
||||||
|
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
||||||
_ => null,
|
_ => null,
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
};
|
};
|
||||||
|
@ -1549,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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal file
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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'">
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -101,11 +101,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AlwaysFlushOnOverlap { get; private set; }
|
public bool AlwaysFlushOnOverlap { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Indicates that the texture was fully unmapped since the modified flag was set, and flushes should be ignored until it is modified again.
|
|
||||||
/// </summary>
|
|
||||||
public bool FlushStale { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1417,7 +1412,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SignalModified()
|
public void SignalModified()
|
||||||
{
|
{
|
||||||
FlushStale = false;
|
|
||||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||||
|
|
||||||
if (_modifiedStale || Group.HasCopyDependencies)
|
if (_modifiedStale || Group.HasCopyDependencies)
|
||||||
|
@ -1438,7 +1432,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
if (bound)
|
if (bound)
|
||||||
{
|
{
|
||||||
FlushStale = false;
|
|
||||||
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1703,12 +1696,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
/// <param name="unmapRange">The range of memory being unmapped</param>
|
/// <param name="unmapRange">The range of memory being unmapped</param>
|
||||||
public void Unmapped(MultiRange unmapRange)
|
public void Unmapped(MultiRange unmapRange)
|
||||||
{
|
{
|
||||||
if (unmapRange.Contains(Range))
|
|
||||||
{
|
|
||||||
// If this is a full unmap, prevent flushes until the texture is mapped again.
|
|
||||||
FlushStale = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangedMapping = true;
|
ChangedMapping = true;
|
||||||
|
|
||||||
if (Group.Storage == this)
|
if (Group.Storage == this)
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -339,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;
|
||||||
|
@ -718,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>
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -1660,9 +1631,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
// If size is zero, we have nothing to flush.
|
// If size is zero, we have nothing to flush.
|
||||||
// If the flush is stale, we should ignore it because the texture was unmapped since the modified
|
if (size == 0)
|
||||||
// flag was set, and flushing it is not safe anymore as the GPU might no longer own the memory.
|
|
||||||
if (size == 0 || Storage.FlushStale)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -153,8 +156,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
bool changesScale = (hasValue != (_rtColors[index] != null)) || (hasValue && RenderTargetScale != color.ScaleFactor);
|
||||||
|
|
||||||
if (_rtColors[index] != color)
|
if (_rtColors[index] != color)
|
||||||
|
{
|
||||||
|
if (_rtColorsBound[index])
|
||||||
{
|
{
|
||||||
_rtColors[index]?.SignalModifying(false);
|
_rtColors[index]?.SignalModifying(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_rtColorsBound[index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (color != null)
|
if (color != null)
|
||||||
{
|
{
|
||||||
|
@ -179,8 +189,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
bool changesScale = (hasValue != (_rtDepthStencil != null)) || (hasValue && RenderTargetScale != depthStencil.ScaleFactor);
|
||||||
|
|
||||||
if (_rtDepthStencil != depthStencil)
|
if (_rtDepthStencil != depthStencil)
|
||||||
|
{
|
||||||
|
if (_rtDsBound)
|
||||||
{
|
{
|
||||||
_rtDepthStencil?.SignalModifying(false);
|
_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>
|
||||||
|
@ -487,12 +553,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
_samplerPoolCache.Dispose();
|
_samplerPoolCache.Dispose();
|
||||||
|
|
||||||
for (int i = 0; i < _rtColors.Length; i++)
|
for (int i = 0; i < _rtColors.Length; i++)
|
||||||
|
{
|
||||||
|
if (_rtColorsBound[i])
|
||||||
{
|
{
|
||||||
_rtColors[i]?.DecrementReferenceCount();
|
_rtColors[i]?.DecrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
_rtColors[i] = null;
|
_rtColors[i] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_rtDsBound)
|
||||||
|
{
|
||||||
_rtDepthStencil?.DecrementReferenceCount();
|
_rtDepthStencil?.DecrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
_rtDepthStencil = null;
|
_rtDepthStencil = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}).");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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 = 5791;
|
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";
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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'">
|
||||||
|
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -419,11 +419,8 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal file
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -203,15 +203,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||||
{
|
{
|
||||||
// TODO.
|
_cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert());
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
|
protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||||
{
|
{
|
||||||
// TODO.
|
// TODO: Flush JIT cache.
|
||||||
return Result.Success;
|
|
||||||
|
return Reprotect(address, pagesCount, permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
@ -1255,7 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
|
|
||||||
if ((oldPermission & KMemoryPermission.Execute) != 0)
|
if ((oldPermission & KMemoryPermission.Execute) != 0)
|
||||||
{
|
{
|
||||||
result = ReprotectWithAttributes(address, pagesCount, permission);
|
result = ReprotectAndFlush(address, pagesCount, permission);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3036,13 +3036,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
|
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Changes the permissions of a given virtual memory region.
|
/// Changes the permissions of a given virtual memory region, while also flushing the cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address">Virtual address of the region to have the permission changes</param>
|
/// <param name="address">Virtual address of the region to have the permission changes</param>
|
||||||
/// <param name="pagesCount">Number of pages to have their permissions changed</param>
|
/// <param name="pagesCount">Number of pages to have their permissions changed</param>
|
||||||
/// <param name="permission">New permission</param>
|
/// <param name="permission">New permission</param>
|
||||||
/// <returns>Result of the permission change operation</returns>
|
/// <returns>Result of the permission change operation</returns>
|
||||||
protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
|
protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Alerts the memory tracking that a given region has been read from or written to.
|
/// Alerts the memory tracking that a given region has been read from or written to.
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -85,10 +85,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
|
||||||
|
|
||||||
public void SetSyncpointMinEqualSyncpointMax(uint id)
|
public void SetSyncpointMinEqualSyncpointMax(uint id)
|
||||||
{
|
{
|
||||||
if (id >= SynchronizationManager.MaxHardwareSyncpoints)
|
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)SynchronizationManager.MaxHardwareSyncpoints);
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
int value = (int)ReadSyncpointValue(id);
|
int value = (int)ReadSyncpointValue(id);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
|
||||||
int controlLength = message.Control == null ? 0 : message.Control.Length;
|
int controlLength = message.Control == null ? 0 : message.Control.Length;
|
||||||
BsdSocketFlags flags = message.Flags;
|
BsdSocketFlags flags = message.Flags;
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref msgNameLength))
|
if (!MemoryMarshal.TryWrite(rawData, in msgNameLength))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
|
||||||
rawData = rawData[msgNameLength..];
|
rawData = rawData[msgNameLength..];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref iovCount))
|
if (!MemoryMarshal.TryWrite(rawData, in iovCount))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
|
||||||
{
|
{
|
||||||
ulong iovLength = (ulong)message.Iov[index].Length;
|
ulong iovLength = (ulong)message.Iov[index].Length;
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref iovLength))
|
if (!MemoryMarshal.TryWrite(rawData, in iovLength))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref controlLength))
|
if (!MemoryMarshal.TryWrite(rawData, in controlLength))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
@ -96,14 +96,14 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
|
||||||
rawData = rawData[controlLength..];
|
rawData = rawData[controlLength..];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref flags))
|
if (!MemoryMarshal.TryWrite(rawData, in flags))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData = rawData[sizeof(BsdSocketFlags)..];
|
rawData = rawData[sizeof(BsdSocketFlags)..];
|
||||||
|
|
||||||
if (!MemoryMarshal.TryWrite(rawData, ref message.Length))
|
if (!MemoryMarshal.TryWrite(rawData, in message.Length))
|
||||||
{
|
{
|
||||||
return LinuxError.EFAULT;
|
return LinuxError.EFAULT;
|
||||||
}
|
}
|
||||||
|
|
|
@ -654,7 +654,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
|
||||||
}
|
}
|
||||||
|
|
||||||
uint sentinel = 0;
|
uint sentinel = 0;
|
||||||
MemoryMarshal.Write(data, ref sentinel);
|
MemoryMarshal.Write(data, in sentinel);
|
||||||
data = data[sizeof(uint)..];
|
data = data[sizeof(uint)..];
|
||||||
|
|
||||||
return region.Memory.Span.Length - data.Length;
|
return region.Memory.Span.Length - data.Length;
|
||||||
|
|
|
@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
|
||||||
|
|
||||||
Header.ToNetworkOrder();
|
Header.ToNetworkOrder();
|
||||||
|
|
||||||
MemoryMarshal.Write(buffer, ref Header);
|
MemoryMarshal.Write(buffer, in Header);
|
||||||
|
|
||||||
buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..];
|
buffer = buffer[Unsafe.SizeOf<AddrInfoSerializedHeader>()..];
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
|
||||||
AddrInfo4 socketAddress = SocketAddress.Value;
|
AddrInfo4 socketAddress = SocketAddress.Value;
|
||||||
socketAddress.ToNetworkOrder();
|
socketAddress.ToNetworkOrder();
|
||||||
|
|
||||||
MemoryMarshal.Write(buffer, ref socketAddress);
|
MemoryMarshal.Write(buffer, in socketAddress);
|
||||||
|
|
||||||
buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..];
|
buffer = buffer[Unsafe.SizeOf<AddrInfo4>()..];
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
|
||||||
Array4<byte> rawIPv4Address = RawIPv4Address.Value;
|
Array4<byte> rawIPv4Address = RawIPv4Address.Value;
|
||||||
AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address);
|
AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address);
|
||||||
|
|
||||||
MemoryMarshal.Write(buffer, ref rawIPv4Address);
|
MemoryMarshal.Write(buffer, in rawIPv4Address);
|
||||||
|
|
||||||
buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..];
|
buffer = buffer[Unsafe.SizeOf<Array4<byte>>()..];
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw exception;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
@ -206,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw exception;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<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>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" />
|
<PackageReference Include="CommandLineParser" />
|
||||||
<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'" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -455,6 +455,11 @@ namespace Ryujinx.Memory
|
||||||
return _pageTable.Read(va) + (nuint)(va & PageMask);
|
return _pageTable.Read(va) + (nuint)(va & PageMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
|
|
|
@ -104,6 +104,12 @@ namespace Ryujinx.Memory
|
||||||
/// <returns>True if the data was changed, false otherwise</returns>
|
/// <returns>True if the data was changed, false otherwise</returns>
|
||||||
bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
|
bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fills the specified memory region with the value specified in <paramref name="value"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address to fill the value into</param>
|
||||||
|
/// <param name="size">Size of the memory region to fill</param>
|
||||||
|
/// <param name="value">Value to fill with</param>
|
||||||
void Fill(ulong va, ulong size, byte value)
|
void Fill(ulong va, ulong size, byte value)
|
||||||
{
|
{
|
||||||
const int MaxChunkSize = 1 << 24;
|
const int MaxChunkSize = 1 << 24;
|
||||||
|
@ -194,6 +200,14 @@ namespace Ryujinx.Memory
|
||||||
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
|
/// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
|
||||||
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
|
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reprotect a region of virtual memory for guest access.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address base</param>
|
||||||
|
/// <param name="size">Size of the region to protect</param>
|
||||||
|
/// <param name="protection">Memory protection to set</param>
|
||||||
|
void Reprotect(ulong va, ulong size, MemoryPermission protection);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reprotect a region of virtual memory for tracking.
|
/// Reprotect a region of virtual memory for tracking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -52,10 +52,7 @@ namespace Ryujinx.Memory.Range
|
||||||
{
|
{
|
||||||
if (HasSingleRange)
|
if (HasSingleRange)
|
||||||
{
|
{
|
||||||
if (_singleRange.Size - offset < size)
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(size, _singleRange.Size - offset);
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MultiRange(_singleRange.Address + offset, size);
|
return new MultiRange(_singleRange.Address + offset, size);
|
||||||
}
|
}
|
||||||
|
@ -108,10 +105,7 @@ namespace Ryujinx.Memory.Range
|
||||||
{
|
{
|
||||||
if (HasSingleRange)
|
if (HasSingleRange)
|
||||||
{
|
{
|
||||||
if (index != 0)
|
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _singleRange;
|
return _singleRange;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -102,6 +102,11 @@ namespace Ryujinx.Tests.Memory
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
OnProtect?.Invoke(va, size, protection);
|
OnProtect?.Invoke(va, size, protection);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,9 @@ using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ui.App.Common
|
namespace Ryujinx.Ui.App.Common
|
||||||
{
|
{
|
||||||
|
@ -24,29 +23,18 @@ namespace Ryujinx.Ui.App.Common
|
||||||
public string TitleId { get; set; }
|
public string TitleId { get; set; }
|
||||||
public string Developer { get; set; }
|
public string Developer { get; set; }
|
||||||
public string Version { get; set; }
|
public string Version { get; set; }
|
||||||
public string TimePlayed { get; set; }
|
public TimeSpan TimePlayed { get; set; }
|
||||||
public double TimePlayedNum { get; set; }
|
|
||||||
public DateTime? LastPlayed { get; set; }
|
public DateTime? LastPlayed { get; set; }
|
||||||
public string FileExtension { get; set; }
|
public string FileExtension { get; set; }
|
||||||
public string FileSize { get; set; }
|
public long FileSize { get; set; }
|
||||||
public double FileSizeBytes { get; set; }
|
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
|
||||||
public string LastPlayedString
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!LastPlayed.HasValue)
|
|
||||||
{
|
|
||||||
// TODO: maybe put localized string here instead of just "Never"
|
|
||||||
return "Never";
|
|
||||||
}
|
|
||||||
|
|
||||||
return LastPlayed.Value.ToLocalTime().ToString(CultureInfo.CurrentCulture);
|
public string LastPlayedString => ValueFormatUtils.FormatDateTime(LastPlayed);
|
||||||
}
|
|
||||||
}
|
public string FileSizeString => ValueFormatUtils.FormatFileSize(FileSize);
|
||||||
|
|
||||||
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
||||||
{
|
{
|
||||||
|
|
|
@ -155,7 +155,7 @@ namespace Ryujinx.Ui.App.Common
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
|
long fileSize = new FileInfo(applicationPath).Length;
|
||||||
string titleName = "Unknown";
|
string titleName = "Unknown";
|
||||||
string titleId = "0000000000000000";
|
string titleId = "0000000000000000";
|
||||||
string developer = "Unknown";
|
string developer = "Unknown";
|
||||||
|
@ -425,25 +425,25 @@ namespace Ryujinx.Ui.App.Common
|
||||||
{
|
{
|
||||||
appMetadata.Title = titleName;
|
appMetadata.Title = titleName;
|
||||||
|
|
||||||
if (appMetadata.LastPlayedOld == default || appMetadata.LastPlayed.HasValue)
|
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
|
||||||
|
if (appMetadata.TimePlayedOld != default && appMetadata.TimePlayed == TimeSpan.Zero)
|
||||||
{
|
{
|
||||||
// Don't do the migration if last_played doesn't exist or last_played_utc already has a value.
|
appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
|
||||||
return;
|
appMetadata.TimePlayedOld = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only do the migration if last_played has a value and last_played_utc doesn't exist yet.
|
||||||
|
if (appMetadata.LastPlayedOld != default && !appMetadata.LastPlayed.HasValue)
|
||||||
|
{
|
||||||
// Migrate from string-based last_played to DateTime-based last_played_utc.
|
// Migrate from string-based last_played to DateTime-based last_played_utc.
|
||||||
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
|
if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
|
||||||
{
|
{
|
||||||
Logger.Info?.Print(LogClass.Application, $"last_played found: \"{appMetadata.LastPlayedOld}\", migrating to last_played_utc");
|
|
||||||
appMetadata.LastPlayed = lastPlayedOldParsed;
|
appMetadata.LastPlayed = lastPlayedOldParsed;
|
||||||
|
|
||||||
// Migration successful: deleting last_played from the metadata file.
|
// Migration successful: deleting last_played from the metadata file.
|
||||||
appMetadata.LastPlayedOld = default;
|
appMetadata.LastPlayedOld = default;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Migration failed: emitting warning but leaving the unparsable value in the metadata file so the user can fix it.
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Last played string \"{appMetadata.LastPlayedOld}\" is invalid for current system culture, skipping (did current culture change?)");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -455,12 +455,10 @@ namespace Ryujinx.Ui.App.Common
|
||||||
TitleId = titleId,
|
TitleId = titleId,
|
||||||
Developer = developer,
|
Developer = developer,
|
||||||
Version = version,
|
Version = version,
|
||||||
TimePlayed = ConvertSecondsToFormattedString(appMetadata.TimePlayed),
|
TimePlayed = appMetadata.TimePlayed,
|
||||||
TimePlayedNum = appMetadata.TimePlayed,
|
|
||||||
LastPlayed = appMetadata.LastPlayed,
|
LastPlayed = appMetadata.LastPlayed,
|
||||||
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
|
FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(),
|
||||||
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + " MiB" : fileSize.ToString("0.##") + " GiB",
|
FileSize = fileSize,
|
||||||
FileSizeBytes = fileSize,
|
|
||||||
Path = applicationPath,
|
Path = applicationPath,
|
||||||
ControlHolder = controlHolder,
|
ControlHolder = controlHolder,
|
||||||
};
|
};
|
||||||
|
@ -716,31 +714,6 @@ namespace Ryujinx.Ui.App.Common
|
||||||
return applicationIcon ?? _ncaIcon;
|
return applicationIcon ?? _ncaIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ConvertSecondsToFormattedString(double seconds)
|
|
||||||
{
|
|
||||||
TimeSpan time = TimeSpan.FromSeconds(seconds);
|
|
||||||
|
|
||||||
string timeString;
|
|
||||||
if (time.Days != 0)
|
|
||||||
{
|
|
||||||
timeString = $"{time.Days}d {time.Hours:D2}h {time.Minutes:D2}m";
|
|
||||||
}
|
|
||||||
else if (time.Hours != 0)
|
|
||||||
{
|
|
||||||
timeString = $"{time.Hours:D2}h {time.Minutes:D2}m";
|
|
||||||
}
|
|
||||||
else if (time.Minutes != 0)
|
|
||||||
{
|
|
||||||
timeString = $"{time.Minutes:D2}m";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
timeString = "Never";
|
|
||||||
}
|
|
||||||
|
|
||||||
return timeString;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
|
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
|
||||||
{
|
{
|
||||||
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
|
||||||
|
|
|
@ -7,13 +7,45 @@ namespace Ryujinx.Ui.App.Common
|
||||||
{
|
{
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public double TimePlayed { get; set; }
|
|
||||||
|
[JsonPropertyName("timespan_played")]
|
||||||
|
public TimeSpan TimePlayed { get; set; } = TimeSpan.Zero;
|
||||||
|
|
||||||
[JsonPropertyName("last_played_utc")]
|
[JsonPropertyName("last_played_utc")]
|
||||||
public DateTime? LastPlayed { get; set; } = null;
|
public DateTime? LastPlayed { get; set; } = null;
|
||||||
|
|
||||||
|
[JsonPropertyName("time_played")]
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
|
public double TimePlayedOld { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("last_played")]
|
[JsonPropertyName("last_played")]
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||||
public string LastPlayedOld { get; set; }
|
public string LastPlayedOld { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates <see cref="LastPlayed"/>. Call this before launching a game.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePreGame()
|
||||||
|
{
|
||||||
|
LastPlayed = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates <see cref="LastPlayed"/> and <see cref="TimePlayed"/>. Call this after a game ends.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePostGame()
|
||||||
|
{
|
||||||
|
DateTime? prevLastPlayed = LastPlayed;
|
||||||
|
UpdatePreGame();
|
||||||
|
|
||||||
|
if (!prevLastPlayed.HasValue)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan diff = DateTime.UtcNow - prevLastPlayed.Value;
|
||||||
|
double newTotalSeconds = TimePlayed.Add(diff).TotalSeconds;
|
||||||
|
TimePlayed = TimeSpan.FromSeconds(Math.Round(newTotalSeconds, MidpointRounding.AwayFromZero));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Ryujinx.Ui.Common.Configuration
|
|
||||||
{
|
|
||||||
public enum ConfigurationLoadResult
|
|
||||||
{
|
|
||||||
Success = 0,
|
|
||||||
NotLoaded = 1,
|
|
||||||
MigratedFromPreVulkan = 1 << 8,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||||
using Ryujinx.Common.Configuration.Multiplayer;
|
using Ryujinx.Common.Configuration.Multiplayer;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.Vulkan;
|
||||||
using Ryujinx.Ui.Common.Configuration.System;
|
using Ryujinx.Ui.Common.Configuration.System;
|
||||||
using Ryujinx.Ui.Common.Configuration.Ui;
|
using Ryujinx.Ui.Common.Configuration.Ui;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
@ -763,7 +764,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
Graphics.ResScaleCustom.Value = 1.0f;
|
Graphics.ResScaleCustom.Value = 1.0f;
|
||||||
Graphics.MaxAnisotropy.Value = -1.0f;
|
Graphics.MaxAnisotropy.Value = -1.0f;
|
||||||
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
|
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
|
||||||
Graphics.GraphicsBackend.Value = OperatingSystem.IsMacOS() ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl;
|
Graphics.GraphicsBackend.Value = DefaultGraphicsBackend();
|
||||||
Graphics.PreferredGpu.Value = "";
|
Graphics.PreferredGpu.Value = "";
|
||||||
Graphics.ShadersDumpPath.Value = "";
|
Graphics.ShadersDumpPath.Value = "";
|
||||||
Logger.EnableDebug.Value = false;
|
Logger.EnableDebug.Value = false;
|
||||||
|
@ -784,7 +785,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
EnableDiscordIntegration.Value = true;
|
EnableDiscordIntegration.Value = true;
|
||||||
CheckUpdatesOnStart.Value = true;
|
CheckUpdatesOnStart.Value = true;
|
||||||
ShowConfirmExit.Value = true;
|
ShowConfirmExit.Value = true;
|
||||||
HideCursor.Value = HideCursorMode.Never;
|
HideCursor.Value = HideCursorMode.OnIdle;
|
||||||
Graphics.EnableVsync.Value = true;
|
Graphics.EnableVsync.Value = true;
|
||||||
Graphics.EnableShaderCache.Value = true;
|
Graphics.EnableShaderCache.Value = true;
|
||||||
Graphics.EnableTextureRecompression.Value = false;
|
Graphics.EnableTextureRecompression.Value = false;
|
||||||
|
@ -907,7 +908,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationLoadResult Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath)
|
public void Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath)
|
||||||
{
|
{
|
||||||
bool configurationFileUpdated = false;
|
bool configurationFileUpdated = false;
|
||||||
|
|
||||||
|
@ -916,12 +917,8 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default.");
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default.");
|
||||||
|
|
||||||
LoadDefault();
|
LoadDefault();
|
||||||
|
|
||||||
return ConfigurationLoadResult.NotLoaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurationLoadResult result = ConfigurationLoadResult.Success;
|
|
||||||
|
|
||||||
if (configurationFileFormat.Version < 2)
|
if (configurationFileFormat.Version < 2)
|
||||||
{
|
{
|
||||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 2.");
|
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 2.");
|
||||||
|
@ -1336,8 +1333,6 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
|
|
||||||
configurationFileFormat.GraphicsBackend = GraphicsBackend.OpenGl;
|
configurationFileFormat.GraphicsBackend = GraphicsBackend.OpenGl;
|
||||||
|
|
||||||
result |= ConfigurationLoadResult.MigratedFromPreVulkan;
|
|
||||||
|
|
||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1535,8 +1530,18 @@ namespace Ryujinx.Ui.Common.Configuration
|
||||||
|
|
||||||
Ryujinx.Common.Logging.Logger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}");
|
Ryujinx.Common.Logging.Logger.Notice.Print(LogClass.Application, $"Configuration file updated to version {ConfigurationFileFormat.CurrentVersion}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
private static GraphicsBackend DefaultGraphicsBackend()
|
||||||
|
{
|
||||||
|
// Any system running macOS or returning any amount of valid Vulkan devices should default to Vulkan.
|
||||||
|
// Checks for if the Vulkan version and featureset is compatible should be performed within VulkanRenderer.
|
||||||
|
if (OperatingSystem.IsMacOS() || VulkanRenderer.GetPhysicalDevices().Length > 0)
|
||||||
|
{
|
||||||
|
return GraphicsBackend.Vulkan;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GraphicsBackend.OpenGl;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogValueChange<T>(ReactiveEventArgs<T> eventArgs, string valueName)
|
private static void LogValueChange<T>(ReactiveEventArgs<T> eventArgs, string valueName)
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
graphic.DrawImage(image, 0, 0, 128, 128);
|
graphic.DrawImage(image, 0, 0, 128, 128);
|
||||||
SaveBitmapAsIcon(bitmap, iconPath);
|
SaveBitmapAsIcon(bitmap, iconPath);
|
||||||
|
|
||||||
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(basePath, applicationFilePath), iconPath, 0);
|
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath), iconPath, 0);
|
||||||
shortcut.StringData.NameString = cleanedAppName;
|
shortcut.StringData.NameString = cleanedAppName;
|
||||||
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
||||||
}
|
}
|
||||||
|
@ -46,16 +46,16 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
image.SaveAsPng(iconPath);
|
image.SaveAsPng(iconPath);
|
||||||
|
|
||||||
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
||||||
outputFile.Write(desktopFile, cleanedAppName, iconPath, GetArgsString(basePath, applicationFilePath));
|
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
[SupportedOSPlatform("macos")]
|
||||||
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
|
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
|
||||||
{
|
{
|
||||||
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
|
||||||
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.plist");
|
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.plist");
|
||||||
// Macos .App folder
|
// Macos .App folder
|
||||||
string contentFolderPath = Path.Combine(desktopPath, cleanedAppName + ".app", "Contents");
|
string contentFolderPath = Path.Combine("/Applications", cleanedAppName + ".app", "Contents");
|
||||||
string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
|
string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
|
||||||
|
|
||||||
if (!Directory.Exists(scriptFolderPath))
|
if (!Directory.Exists(scriptFolderPath))
|
||||||
|
@ -69,7 +69,7 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
using StreamWriter scriptFile = new(scriptPath);
|
using StreamWriter scriptFile = new(scriptPath);
|
||||||
|
|
||||||
scriptFile.WriteLine("#!/bin/sh");
|
scriptFile.WriteLine("#!/bin/sh");
|
||||||
scriptFile.WriteLine(GetArgsString(basePath, appFilePath));
|
scriptFile.WriteLine($"{basePath} {GetArgsString(appFilePath)}");
|
||||||
|
|
||||||
// Set execute permission
|
// Set execute permission
|
||||||
FileInfo fileInfo = new(scriptPath);
|
FileInfo fileInfo = new(scriptPath);
|
||||||
|
@ -125,13 +125,10 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetArgsString(string basePath, string appFilePath)
|
private static string GetArgsString(string appFilePath)
|
||||||
{
|
{
|
||||||
// args are first defined as a list, for easier adjustments in the future
|
// args are first defined as a list, for easier adjustments in the future
|
||||||
var argsList = new List<string>
|
var argsList = new List<string>();
|
||||||
{
|
|
||||||
basePath,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
|
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
|
||||||
{
|
{
|
||||||
|
@ -141,7 +138,6 @@ namespace Ryujinx.Ui.Common.Helper
|
||||||
|
|
||||||
argsList.Add($"\"{appFilePath}\"");
|
argsList.Add($"\"{appFilePath}\"");
|
||||||
|
|
||||||
|
|
||||||
return String.Join(" ", argsList);
|
return String.Join(" ", argsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
219
src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs
Normal file
219
src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Helper
|
||||||
|
{
|
||||||
|
public static class ValueFormatUtils
|
||||||
|
{
|
||||||
|
private static readonly string[] _fileSizeUnitStrings =
|
||||||
|
{
|
||||||
|
"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", // Base 10 units, used for formatting and parsing
|
||||||
|
"KB", "MB", "GB", "TB", "PB", "EB", // Base 2 units, used for parsing legacy values
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used by <see cref="FormatFileSize"/>.
|
||||||
|
/// </summary>
|
||||||
|
public enum FileSizeUnits
|
||||||
|
{
|
||||||
|
Auto = -1,
|
||||||
|
Bytes = 0,
|
||||||
|
Kibibytes = 1,
|
||||||
|
Mebibytes = 2,
|
||||||
|
Gibibytes = 3,
|
||||||
|
Tebibytes = 4,
|
||||||
|
Pebibytes = 5,
|
||||||
|
Exbibytes = 6,
|
||||||
|
Kilobytes = 7,
|
||||||
|
Megabytes = 8,
|
||||||
|
Gigabytes = 9,
|
||||||
|
Terabytes = 10,
|
||||||
|
Petabytes = 11,
|
||||||
|
Exabytes = 12,
|
||||||
|
}
|
||||||
|
|
||||||
|
private const double SizeBase10 = 1000;
|
||||||
|
private const double SizeBase2 = 1024;
|
||||||
|
private const int UnitEBIndex = 6;
|
||||||
|
|
||||||
|
#region Value formatters
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a human-readable string from a <see cref="TimeSpan"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpan">The <see cref="TimeSpan"/> to be formatted.</param>
|
||||||
|
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
||||||
|
public static string FormatTimeSpan(TimeSpan? timeSpan)
|
||||||
|
{
|
||||||
|
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
|
||||||
|
{
|
||||||
|
// Game was never played
|
||||||
|
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeSpan.Value.TotalDays < 1)
|
||||||
|
{
|
||||||
|
// Game was played for less than a day
|
||||||
|
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game was played for more than a day
|
||||||
|
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days));
|
||||||
|
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
return $"{timeSpan.Value.Days}d, {onlyTimeString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a human-readable string from a <see cref="DateTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="utcDateTime">The <see cref="DateTime"/> to be formatted. This is expected to be UTC-based.</param>
|
||||||
|
/// <param name="culture">The <see cref="CultureInfo"/> that's used in formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.</param>
|
||||||
|
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
||||||
|
public static string FormatDateTime(DateTime? utcDateTime, CultureInfo culture = null)
|
||||||
|
{
|
||||||
|
culture ??= CultureInfo.CurrentCulture;
|
||||||
|
|
||||||
|
if (!utcDateTime.HasValue)
|
||||||
|
{
|
||||||
|
// In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
|
||||||
|
return "Never";
|
||||||
|
}
|
||||||
|
|
||||||
|
return utcDateTime.Value.ToLocalTime().ToString(culture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a human-readable file size string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">The file size in bytes.</param>
|
||||||
|
/// <param name="forceUnit">Formats the passed size value as this unit, bypassing the automatic unit choice.</param>
|
||||||
|
/// <returns>A human-readable file size string.</returns>
|
||||||
|
public static string FormatFileSize(long size, FileSizeUnits forceUnit = FileSizeUnits.Auto)
|
||||||
|
{
|
||||||
|
if (size <= 0)
|
||||||
|
{
|
||||||
|
return $"0 {_fileSizeUnitStrings[0]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
int unitIndex = (int)forceUnit;
|
||||||
|
if (forceUnit == FileSizeUnits.Auto)
|
||||||
|
{
|
||||||
|
unitIndex = Convert.ToInt32(Math.Floor(Math.Log(size, SizeBase10)));
|
||||||
|
|
||||||
|
// Apply an upper bound so that exabytes are the biggest unit used when formatting.
|
||||||
|
if (unitIndex > UnitEBIndex)
|
||||||
|
{
|
||||||
|
unitIndex = UnitEBIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double sizeRounded;
|
||||||
|
|
||||||
|
if (unitIndex > UnitEBIndex)
|
||||||
|
{
|
||||||
|
sizeRounded = Math.Round(size / Math.Pow(SizeBase10, unitIndex - UnitEBIndex), 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sizeRounded = Math.Round(size / Math.Pow(SizeBase2, unitIndex), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
string sizeFormatted = sizeRounded.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
return $"{sizeFormatted} {_fileSizeUnitStrings[unitIndex]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Value parsers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a string generated by <see cref="FormatTimeSpan"/> and returns the original <see cref="TimeSpan"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeSpanString">A string representing a <see cref="TimeSpan"/>.</param>
|
||||||
|
/// <returns>A <see cref="TimeSpan"/> object. If the input string couldn't been parsed, <see cref="TimeSpan.Zero"/> is returned.</returns>
|
||||||
|
public static TimeSpan ParseTimeSpan(string timeSpanString)
|
||||||
|
{
|
||||||
|
TimeSpan returnTimeSpan = TimeSpan.Zero;
|
||||||
|
|
||||||
|
// An input string can either look like "01:23:45" or "1d, 01:23:45" if the timespan represents a duration of more than a day.
|
||||||
|
// Here, we split the input string to check if it's the former or the latter.
|
||||||
|
var valueSplit = timeSpanString.Split(", ");
|
||||||
|
if (valueSplit.Length > 1)
|
||||||
|
{
|
||||||
|
var dayPart = valueSplit[0].Split("d")[0];
|
||||||
|
if (int.TryParse(dayPart, out int days))
|
||||||
|
{
|
||||||
|
returnTimeSpan = returnTimeSpan.Add(TimeSpan.FromDays(days));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TimeSpan.TryParse(valueSplit.Last(), out TimeSpan parsedTimeSpan))
|
||||||
|
{
|
||||||
|
returnTimeSpan = returnTimeSpan.Add(parsedTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnTimeSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a string generated by <see cref="FormatDateTime"/> and returns the original <see cref="DateTime"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateTimeString">The string representing a <see cref="DateTime"/>.</param>
|
||||||
|
/// <returns>A <see cref="DateTime"/> object. If the input string couldn't be parsed, <see cref="DateTime.UnixEpoch"/> is returned.</returns>
|
||||||
|
public static DateTime ParseDateTime(string dateTimeString)
|
||||||
|
{
|
||||||
|
if (!DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, out DateTime parsedDateTime))
|
||||||
|
{
|
||||||
|
// Games that were never played are supposed to appear before the oldest played games in the list,
|
||||||
|
// so returning DateTime.UnixEpoch here makes sense.
|
||||||
|
return DateTime.UnixEpoch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a string generated by <see cref="FormatFileSize"/> and returns a <see cref="long"/> representing a number of bytes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sizeString">A string representing a file size formatted with <see cref="FormatFileSize"/>.</param>
|
||||||
|
/// <returns>A <see cref="long"/> representing a number of bytes.</returns>
|
||||||
|
public static long ParseFileSize(string sizeString)
|
||||||
|
{
|
||||||
|
// Enumerating over the units backwards because otherwise, sizeString.EndsWith("B") would exit the loop in the first iteration.
|
||||||
|
for (int i = _fileSizeUnitStrings.Length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
string unit = _fileSizeUnitStrings[i];
|
||||||
|
if (!sizeString.EndsWith(unit))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string numberString = sizeString.Split(" ")[0];
|
||||||
|
if (!double.TryParse(numberString, CultureInfo.InvariantCulture, out double number))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sizeBase = SizeBase2;
|
||||||
|
|
||||||
|
// If the unit index is one that points to a base 10 unit in the FileSizeUnitStrings array, subtract 6 to arrive at a usable power value.
|
||||||
|
if (i > UnitEBIndex)
|
||||||
|
{
|
||||||
|
i -= UnitEBIndex;
|
||||||
|
sizeBase = SizeBase10;
|
||||||
|
}
|
||||||
|
|
||||||
|
number *= Math.Pow(sizeBase, i);
|
||||||
|
|
||||||
|
return Convert.ToInt64(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue