diff --git a/.editorconfig b/.editorconfig
index 9d695c7fb..db08c67e2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -233,6 +233,29 @@ dotnet_naming_style.IPascalCase.required_suffix =
dotnet_naming_style.IPascalCase.word_separator =
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]
# Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 16058d9f8..7d46adc2c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,7 +30,7 @@ jobs:
- os: windows-latest
OS_NAME: Windows x64
- DOTNET_RUNTIME_IDENTIFIER: win10-x64
+ DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
fail-fast: false
@@ -155,4 +155,4 @@ jobs:
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_headless/*.tar.gz"
- if: github.event_name == 'pull_request'
\ No newline at end of file
+ if: github.event_name == 'pull_request'
diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml
index 4c8ba3e17..c1ae9fe8f 100644
--- a/.github/workflows/flatpak.yml
+++ b/.github/workflows/flatpak.yml
@@ -49,7 +49,9 @@ jobs:
run: python -m pip install PyYAML lxml
- 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
shell: python
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 988264a31..4dc1d091d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,7 +25,7 @@ env:
jobs:
tag:
name: Create tag
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
steps:
- name: Get version info
id: version_info
@@ -59,7 +59,7 @@ jobs:
- os: windows-latest
OS_NAME: Windows x64
- DOTNET_RUNTIME_IDENTIFIER: win10-x64
+ DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
steps:
- uses: actions/checkout@v4
@@ -156,11 +156,11 @@ jobs:
with:
global-json-file: global.json
- - name: Setup LLVM 14
+ - name: Setup LLVM 15
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
- sudo ./llvm.sh 14
+ sudo ./llvm.sh 15
- name: Install rcodesign
run: |
@@ -215,4 +215,4 @@ jobs:
needs: release
with:
ryujinx_version: "1.1.${{ github.run_number }}"
- secrets: inherit
\ No newline at end of file
+ secrets: inherit
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 009430f92..44f9b5d40 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -21,16 +21,16 @@
-
+
-
-
-
-
+
+
+
+
@@ -45,10 +45,10 @@
-
-
-
-
+
+
+
+
diff --git a/README.md b/README.md
index b2a6646f5..b2f95cc1f 100644
--- a/README.md
+++ b/README.md
@@ -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:
### 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
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.
diff --git a/distribution/macos/Info.plist b/distribution/macos/Info.plist
index 6e068ba2d..51c71eaa1 100644
--- a/distribution/macos/Info.plist
+++ b/distribution/macos/Info.plist
@@ -43,7 +43,7 @@
LSApplicationCategoryType
public.app-category.games
LSMinimumSystemVersion
- 11.0
+ 12.0
UTExportedTypeDeclarations
@@ -155,4 +155,4 @@
200000
-
\ No newline at end of file
+
diff --git a/global.json b/global.json
index 39ccef0d0..391ba3c2a 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "7.0.200",
+ "version": "8.0.100",
"rollForward": "latestFeature"
}
-}
\ No newline at end of file
+}
diff --git a/src/ARMeilleure/ARMeilleure.csproj b/src/ARMeilleure/ARMeilleure.csproj
index fa5551154..550e50c26 100644
--- a/src/ARMeilleure/ARMeilleure.csproj
+++ b/src/ARMeilleure/ARMeilleure.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/ARMeilleure/Decoders/Decoder.cs b/src/ARMeilleure/Decoders/Decoder.cs
index 6d07827a2..66d286928 100644
--- a/src/ARMeilleure/Decoders/Decoder.cs
+++ b/src/ARMeilleure/Decoders/Decoder.cs
@@ -38,7 +38,9 @@ namespace ARMeilleure.Decoders
{
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.EndAddress = blkAddress;
diff --git a/src/ARMeilleure/Translation/Cache/JitCache.cs b/src/ARMeilleure/Translation/Cache/JitCache.cs
index 91a054123..e2b5e2d10 100644
--- a/src/ARMeilleure/Translation/Cache/JitCache.cs
+++ b/src/ARMeilleure/Translation/Cache/JitCache.cs
@@ -117,12 +117,11 @@ namespace ARMeilleure.Translation.Cache
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
- bool result = TryFind(funcOffset, out CacheEntry entry);
- Debug.Assert(result);
-
- _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
-
- Remove(funcOffset);
+ if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
+ {
+ _cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
+ _cacheEntries.RemoveAt(entryIndex);
+ }
}
}
@@ -181,22 +180,7 @@ namespace ARMeilleure.Translation.Cache
_cacheEntries.Insert(index, entry);
}
- private static void Remove(int offset)
- {
- 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)
+ public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
{
lock (_lock)
{
@@ -210,11 +194,13 @@ namespace ARMeilleure.Translation.Cache
if (index >= 0)
{
entry = _cacheEntries[index];
+ entryIndex = index;
return true;
}
}
entry = default;
+ entryIndex = 0;
return false;
}
}
diff --git a/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs
index 91fd19c25..3957a7559 100644
--- a/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs
+++ b/src/ARMeilleure/Translation/Cache/JitUnwindWindows.cs
@@ -95,7 +95,7 @@ namespace ARMeilleure.Translation.Cache
{
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.
}
diff --git a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
index 115a37601..b5fd8f9e7 100644
--- a/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
+++ b/src/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj
@@ -1,11 +1,11 @@
- net7.0
+ net8.0
-
+
diff --git a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
index 525f1f5b6..dd18e70a1 100644
--- a/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
+++ b/src/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
index 9f242dbe2..1d92d9d2e 100644
--- a/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
+++ b/src/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj
@@ -1,9 +1,9 @@
- net7.0
+ net8.0
true
- win10-x64;linux-x64;osx-x64
+ win-x64;osx-x64;linux-x64
@@ -15,11 +15,11 @@
PreserveNewest
libsoundio.dll
-
+
PreserveNewest
libsoundio.dylib
-
+
PreserveNewest
libsoundio.so
diff --git a/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
index 4771ae4dd..abbb6ea6c 100644
--- a/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
+++ b/src/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs
@@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Utils
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..];
}
@@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Utils
throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null);
}
- MemoryMarshal.Write(backingMemory[..size], ref data);
+ MemoryMarshal.Write(backingMemory[..size], in data);
backingMemory = backingMemory[size..];
}
diff --git a/src/Ryujinx.Audio/Ryujinx.Audio.csproj b/src/Ryujinx.Audio/Ryujinx.Audio.csproj
index 4a159eb5c..fc20f4ec4 100644
--- a/src/Ryujinx.Audio/Ryujinx.Audio.csproj
+++ b/src/Ryujinx.Audio/Ryujinx.Audio.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs
index cd066efba..4d751e2a9 100644
--- a/src/Ryujinx.Ava/AppHost.cs
+++ b/src/Ryujinx.Ava/AppHost.cs
@@ -716,7 +716,7 @@ namespace Ryujinx.Ava
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
- appMetadata.LastPlayed = DateTime.UtcNow;
+ appMetadata.UpdatePreGame();
});
return true;
diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json
index 62aac1227..bc2bbfe82 100644
--- a/src/Ryujinx.Ava/Assets/Locales/en_US.json
+++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json
@@ -14,7 +14,7 @@
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
"MenuBarFileExit": "_Exit",
- "MenuBarOptions": "Options",
+ "MenuBarOptions": "_Options",
"MenuBarOptionsToggleFullscreen": "Toggle Fullscreen",
"MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode",
"MenuBarOptionsStopEmulation": "Stop Emulation",
@@ -30,7 +30,7 @@
"MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
- "MenuBarHelp": "Help",
+ "MenuBarHelp": "_Help",
"MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About",
"MenuSearch": "Search...",
diff --git a/src/Ryujinx.Ava/Program.cs b/src/Ryujinx.Ava/Program.cs
index 168e9216d..cc062a256 100644
--- a/src/Ryujinx.Ava/Program.cs
+++ b/src/Ryujinx.Ava/Program.cs
@@ -6,13 +6,13 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
-using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
+using Ryujinx.Ui.Common.SystemInfo;
using System;
using System.IO;
using System.Runtime.InteropServices;
diff --git a/src/Ryujinx.Ava/Ryujinx.Ava.csproj b/src/Ryujinx.Ava/Ryujinx.Ava.csproj
index f0e99f427..6812e57c4 100644
--- a/src/Ryujinx.Ava/Ryujinx.Ava.csproj
+++ b/src/Ryujinx.Ava/Ryujinx.Ava.csproj
@@ -1,7 +1,7 @@
- net7.0
- win10-x64;osx-x64;linux-x64
+ net8.0
+ win-x64;osx-x64;linux-x64
Exe
true
1.0.0-dirty
@@ -25,6 +25,16 @@
partial
+
+
+ true
+
+
@@ -40,7 +50,7 @@
-
+
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml
index d81050f83..b8fe7e76f 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml
@@ -12,6 +12,11 @@
Click="ToggleFavorite_Click"
Header="{locale:Locale GameListContextMenuToggleFavorite}"
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
+
-
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
index 09011005b..9004f7518 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml
@@ -126,17 +126,17 @@
Spacing="5">
diff --git a/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
new file mode 100644
index 000000000..737896986
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
@@ -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
+{
+ ///
+ /// This makes sure that the string "Never" that's returned by is properly localized in the Avalonia UI.
+ /// After the Avalonia UI has been made the default and the GTK UI is removed, should be updated to directly return a localized string.
+ ///
+ 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;
+ }
+ }
+}
diff --git a/src/Ryujinx.Ava/UI/Helpers/NullableDateTimeConverter.cs b/src/Ryujinx.Ava/UI/Helpers/NullableDateTimeConverter.cs
deleted file mode 100644
index e91937612..000000000
--- a/src/Ryujinx.Ava/UI/Helpers/NullableDateTimeConverter.cs
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs b/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs
index ca55d0399..35d16b9e0 100644
--- a/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs
+++ b/src/Ryujinx.Ava/UI/Helpers/Win32NativeInterop.cs
@@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Helpers
public static partial IntPtr SetCursor(IntPtr handle);
[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")]
public static partial ushort RegisterClassEx(ref WndClassEx param);
diff --git a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
index 8a4346556..8340d39df 100644
--- a/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
+++ b/src/Ryujinx.Ava/UI/Models/Generic/LastPlayedSortComparer.cs
@@ -13,20 +13,19 @@ namespace Ryujinx.Ava.UI.Models.Generic
public int Compare(ApplicationData x, ApplicationData y)
{
- var aValue = x.LastPlayed;
- var bValue = y.LastPlayed;
+ DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch;
- 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);
}
}
}
diff --git a/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
new file mode 100644
index 000000000..d53ff566f
--- /dev/null
+++ b/src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
@@ -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
+ {
+ 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);
+ }
+ }
+}
diff --git a/src/Ryujinx.Ava/UI/Models/SaveModel.cs b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
index f15befbb3..7b476932b 100644
--- a/src/Ryujinx.Ava/UI/Models/SaveModel.cs
+++ b/src/Ryujinx.Ava/UI/Models/SaveModel.cs
@@ -4,7 +4,7 @@ using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.HLE.FileSystem;
using Ryujinx.Ui.App.Common;
-using System;
+using Ryujinx.Ui.Common.Helper;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -38,26 +38,7 @@ namespace Ryujinx.Ava.UI.Models
public bool SizeAvailable { get; set; }
- public string SizeString => GetSizeString();
-
- 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 string SizeString => ValueFormatUtils.FormatFileSize(Size);
public SaveModel(SaveDataInfo info)
{
diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index b14905204..80df5d398 100644
--- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -930,21 +930,20 @@ namespace Ryujinx.Ava.UI.ViewModels
return SortMode switch
{
#pragma warning disable IDE0055 // Disable formatting
+ ApplicationSort.Title => IsAscending ? SortExpressionComparer.Ascending(app => app.TitleName)
+ : SortExpressionComparer.Descending(app => app.TitleName),
+ ApplicationSort.Developer => IsAscending ? SortExpressionComparer.Ascending(app => app.Developer)
+ : SortExpressionComparer.Descending(app => app.Developer),
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
- ApplicationSort.FileSize => IsAscending ? SortExpressionComparer.Ascending(app => app.FileSizeBytes)
- : SortExpressionComparer.Descending(app => app.FileSizeBytes),
- ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer.Ascending(app => app.TimePlayedNum)
- : SortExpressionComparer.Descending(app => app.TimePlayedNum),
- ApplicationSort.Title => IsAscending ? SortExpressionComparer.Ascending(app => app.TitleName)
- : SortExpressionComparer.Descending(app => app.TitleName),
+ ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
+ ApplicationSort.FileType => IsAscending ? SortExpressionComparer.Ascending(app => app.FileExtension)
+ : SortExpressionComparer.Descending(app => app.FileExtension),
+ ApplicationSort.FileSize => IsAscending ? SortExpressionComparer.Ascending(app => app.FileSize)
+ : SortExpressionComparer.Descending(app => app.FileSize),
+ ApplicationSort.Path => IsAscending ? SortExpressionComparer.Ascending(app => app.Path)
+ : SortExpressionComparer.Descending(app => app.Path),
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer.Ascending(app => app.Favorite)
: SortExpressionComparer.Descending(app => app.Favorite),
- ApplicationSort.Developer => IsAscending ? SortExpressionComparer.Ascending(app => app.Developer)
- : SortExpressionComparer.Descending(app => app.Developer),
- ApplicationSort.FileType => IsAscending ? SortExpressionComparer.Ascending(app => app.FileExtension)
- : SortExpressionComparer.Descending(app => app.FileExtension),
- ApplicationSort.Path => IsAscending ? SortExpressionComparer.Ascending(app => app.Path)
- : SortExpressionComparer.Descending(app => app.Path),
_ => null,
#pragma warning restore IDE0055
};
@@ -1549,13 +1548,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
- if (appMetadata.LastPlayed.HasValue)
- {
- double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
- appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
- }
-
- appMetadata.LastPlayed = DateTime.UtcNow;
+ appMetadata.UpdatePostGame();
});
}
diff --git a/src/Ryujinx.Common/Configuration/AppDataManager.cs b/src/Ryujinx.Common/Configuration/AppDataManager.cs
index 1dbc1f0ce..2b4a594d3 100644
--- a/src/Ryujinx.Common/Configuration/AppDataManager.cs
+++ b/src/Ryujinx.Common/Configuration/AppDataManager.cs
@@ -48,7 +48,7 @@ namespace Ryujinx.Common.Configuration
string appDataPath;
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
{
diff --git a/src/Ryujinx.Common/Ryujinx.Common.csproj b/src/Ryujinx.Common/Ryujinx.Common.csproj
index c02b11e0c..da2f13a21 100644
--- a/src/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/src/Ryujinx.Common/Ryujinx.Common.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
$(DefineConstants);$(ExtraDefineConstants)
diff --git a/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs b/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
new file mode 100644
index 000000000..876597b78
--- /dev/null
+++ b/src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
@@ -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 code)
+ {
+ Span codeUint = MemoryMarshal.Cast(code);
+ Span> codeVector = MemoryMarshal.Cast>(code);
+
+ Vector128 mask = Vector128.Create(XMask);
+ Vector128 value = Vector128.Create(XValue);
+
+ for (int index = 0; index < codeVector.Length; index++)
+ {
+ Vector128 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;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
index d5ce817a4..947c37100 100644
--- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
+++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
@@ -128,21 +128,6 @@ namespace Ryujinx.Cpu.AppleHv
}
}
-#pragma warning disable IDE0051 // Remove unused private member
- ///
- /// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
- ///
- /// Virtual address of the range
- /// Size of the range in bytes
- 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
-
///
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{
@@ -736,6 +721,24 @@ namespace Ryujinx.Cpu.AppleHv
return (int)(vaSpan / PageSize);
}
+ ///
+ 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
+ }
+
///
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
diff --git a/src/Ryujinx.Cpu/Jit/MemoryManager.cs b/src/Ryujinx.Cpu/Jit/MemoryManager.cs
index 1c27e97fa..912e3f7e3 100644
--- a/src/Ryujinx.Cpu/Jit/MemoryManager.cs
+++ b/src/Ryujinx.Cpu/Jit/MemoryManager.cs
@@ -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)
{
return PteToPa(_pageTable.Read((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ // TODO
+ }
+
///
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.
///
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
}
}
diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
index 010a0bc25..6d32787ac 100644
--- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
+++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
@@ -615,6 +615,12 @@ namespace Ryujinx.Cpu.Jit
return (int)(vaSpan / PageSize);
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ // TODO
+ }
+
///
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
diff --git a/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
index 7da8da25a..5a6bf5c3d 100644
--- a/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
+++ b/src/Ryujinx.Cpu/Ryujinx.Cpu.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
index 082dac9c2..ae2821edb 100644
--- a/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
+++ b/src/Ryujinx.Graphics.Device/Ryujinx.Graphics.Device.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
index 189108a39..d88b641a3 100644
--- a/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
+++ b/src/Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
index e6557780b..b1921bd51 100644
--- a/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
+++ b/src/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
@@ -211,6 +211,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
int xCount = (int)_state.State.LineLengthIn;
int yCount = (int)_state.State.LineCount;
+ _channel.TextureManager.RefreshModifiedTextures();
_3dEngine.CreatePendingSyncs();
_3dEngine.FlushUboDirty();
@@ -279,7 +280,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, 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(
memoryManager,
diff --git a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
index 1b517e63f..0af0725a2 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/FormatTable.cs
@@ -651,9 +651,35 @@ namespace Ryujinx.Graphics.Gpu.Image
/// True if the format is valid, false otherwise
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;
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
index dca6263aa..f1615b388 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -101,11 +101,6 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public bool AlwaysFlushOnOverlap { get; private set; }
- ///
- /// Indicates that the texture was fully unmapped since the modified flag was set, and flushes should be ignored until it is modified again.
- ///
- public bool FlushStale { get; private set; }
-
///
/// Increments when the host texture is swapped, or when the texture is removed from all pools.
///
@@ -1417,7 +1412,6 @@ namespace Ryujinx.Graphics.Gpu.Image
///
public void SignalModified()
{
- FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
if (_modifiedStale || Group.HasCopyDependencies)
@@ -1438,7 +1432,6 @@ namespace Ryujinx.Graphics.Gpu.Image
{
if (bound)
{
- FlushStale = false;
_scaledSetScore = Math.Max(0, _scaledSetScore - 1);
}
@@ -1703,12 +1696,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// The range of memory being unmapped
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;
if (Group.Storage == this)
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index 5af0471c0..eef38948d 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -2,6 +2,8 @@ using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Texture;
using System;
+using System.Diagnostics;
+using System.Numerics;
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))
{
- 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;
@@ -718,7 +733,8 @@ namespace Ryujinx.Graphics.Gpu.Image
(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);
}
///
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 21d7939ad..b93f3832d 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -992,26 +992,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
}
- ///
- /// The action to perform when a memory tracking handle is flipped to dirty.
- /// This notifies overlapping textures that the memory needs to be synchronized.
- ///
- /// The handle that a dirty flag was set on
- 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();
- }
- }
- }
-
///
/// Generate a CpuRegionHandle for a given address and size range in CPU VA.
///
@@ -1083,11 +1063,6 @@ namespace Ryujinx.Graphics.Gpu.Image
views,
result.ToArray());
- foreach (RegionHandle handle in result)
- {
- handle.RegisterDirtyEvent(() => DirtyAction(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);
- foreach (RegionHandle handle in cpuRegionHandles)
- {
- handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
- }
-
handles = new TextureGroupHandle[] { groupHandle };
}
else
@@ -1619,6 +1589,7 @@ namespace Ryujinx.Graphics.Gpu.Image
if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
{
handle.Modified = false;
+ handle.DeferredCopy = null;
Storage.SignalModifiedDirty();
lock (handle.Overlaps)
@@ -1660,9 +1631,7 @@ namespace Ryujinx.Graphics.Gpu.Image
}
// 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
- // flag was set, and flushing it is not safe anymore as the GPU might no longer own the memory.
- if (size == 0 || Storage.FlushStale)
+ if (size == 0)
{
return;
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
index 84171c7a9..72b743906 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureGroupHandle.cs
@@ -152,6 +152,32 @@ namespace Ryujinx.Graphics.Gpu.Image
// Linear textures are presumed to be used for readback initially.
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
}
+
+ foreach (RegionHandle handle in handles)
+ {
+ handle.RegisterDirtyEvent(DirtyAction);
+ }
+ }
+
+ ///
+ /// The action to perform when a memory tracking handle is flipped to dirty.
+ /// This notifies overlapping textures that the memory needs to be synchronized.
+ ///
+ 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;
}
///
@@ -449,7 +475,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public void DeferCopy(TextureGroupHandle copyFrom)
{
Modified = false;
-
DeferredCopy = copyFrom;
_group.Storage.SignalGroupDirty();
@@ -506,7 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Image
{
existing.Other.Handle.CreateCopyDependency(this);
- if (copyToOther)
+ if (copyToOther && Modified)
{
existing.Other.Handle.DeferCopy(this);
}
@@ -550,10 +575,10 @@ namespace Ryujinx.Graphics.Gpu.Image
if (fromHandle != null)
{
// 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.
- shouldCopy = fromHandle.Modified;
+ shouldCopy = true;
if (fromHandle._bindCount == 0)
{
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index ed181640a..fa278bbd9 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -20,8 +20,10 @@ namespace Ryujinx.Graphics.Gpu.Image
private readonly Texture[] _rtColors;
private readonly ITexture[] _rtHostColors;
+ private readonly bool[] _rtColorsBound;
private Texture _rtDepthStencil;
private ITexture _rtHostDs;
+ private bool _rtDsBound;
public int ClipRegionWidth { get; private set; }
public int ClipRegionHeight { get; private set; }
@@ -51,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.Image
_rtColors = new Texture[Constants.TotalRenderTargets];
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
+ _rtColorsBound = new bool[Constants.TotalRenderTargets];
}
///
@@ -154,7 +157,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_rtColors[index] != color)
{
- _rtColors[index]?.SignalModifying(false);
+ if (_rtColorsBound[index])
+ {
+ _rtColors[index]?.SignalModifying(false);
+ }
+ else
+ {
+ _rtColorsBound[index] = true;
+ }
if (color != null)
{
@@ -180,7 +190,14 @@ namespace Ryujinx.Graphics.Gpu.Image
if (_rtDepthStencil != depthStencil)
{
- _rtDepthStencil?.SignalModifying(false);
+ if (_rtDsBound)
+ {
+ _rtDepthStencil?.SignalModifying(false);
+ }
+ else
+ {
+ _rtDsBound = true;
+ }
if (depthStencil != null)
{
@@ -413,21 +430,45 @@ namespace Ryujinx.Graphics.Gpu.Image
{
bool anyChanged = false;
- if (_rtHostDs != _rtDepthStencil?.HostTexture)
- {
- _rtHostDs = _rtDepthStencil?.HostTexture;
+ Texture dsTexture = _rtDepthStencil;
+ ITexture hostDsTexture = null;
+ if (dsTexture != null)
+ {
+ hostDsTexture = dsTexture.HostTexture;
+
+ if (!_rtDsBound)
+ {
+ dsTexture.SignalModifying(true);
+ _rtDsBound = true;
+ }
+ }
+
+ if (_rtHostDs != hostDsTexture)
+ {
+ _rtHostDs = hostDsTexture;
anyChanged = true;
}
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)
{
_rtHostColors[index] = hostTexture;
-
anyChanged = true;
}
}
@@ -452,6 +493,31 @@ namespace Ryujinx.Graphics.Gpu.Image
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
}
+ ///
+ /// Marks all currently bound render target textures as modified, and also makes them be set as modified again on next use.
+ ///
+ 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;
+ }
+ }
+ }
+
///
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
///
@@ -488,11 +554,19 @@ namespace Ryujinx.Graphics.Gpu.Image
for (int i = 0; i < _rtColors.Length; i++)
{
- _rtColors[i]?.DecrementReferenceCount();
+ if (_rtColorsBound[i])
+ {
+ _rtColors[i]?.DecrementReferenceCount();
+ }
+
_rtColors[i] = null;
}
- _rtDepthStencil?.DecrementReferenceCount();
+ if (_rtDsBound)
+ {
+ _rtDepthStencil?.DecrementReferenceCount();
+ }
+
_rtDepthStencil = null;
}
}
diff --git a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 0fdb6cd64..a4035577d 100644
--- a/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/src/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -430,7 +430,7 @@ namespace Ryujinx.Graphics.Gpu.Image
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}).");
}
diff --git a/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
index 5255a6e00..6f1cce6ac 100644
--- a/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
+++ b/src/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
index 0dc4b1a72..403e039a4 100644
--- a/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
+++ b/src/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
private const ushort FileFormatVersionMajor = 1;
private const ushort FileFormatVersionMinor = 2;
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
- private const uint CodeGenVersion = 5791;
+ private const uint CodeGenVersion = 5957;
private const string SharedTocFileName = "shared.toc";
private const string SharedDataFileName = "shared.data";
diff --git a/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs
index ccec763e3..2d5eede58 100644
--- a/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs
+++ b/src/Ryujinx.Graphics.Gpu/Synchronization/SynchronizationManager.cs
@@ -37,10 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// The incremented value of the syncpoint
public uint IncrementSyncpoint(uint id)
{
- if (id >= MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
return _syncpoints[id].Increment();
}
@@ -53,10 +50,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// The value of the syncpoint
public uint GetSyncpointValue(uint id)
{
- if (id >= MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
return _syncpoints[id].Value;
}
@@ -72,10 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// The created SyncpointWaiterHandle object or null if already past threshold
public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action callback)
{
- if (id >= MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
return _syncpoints[id].RegisterCallback(threshold, callback);
}
@@ -88,10 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// Thrown when id >= MaxHardwareSyncpoints
public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation)
{
- if (id >= MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
_syncpoints[id].UnregisterCallback(waiterInformation);
}
@@ -107,10 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
/// True if timed out
public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout)
{
- if (id >= MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
// TODO: Remove this when GPU channel scheduling will be implemented.
if (timeout == Timeout.InfiniteTimeSpan)
diff --git a/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
index 3cff4061e..22959fad8 100644
--- a/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
+++ b/src/Ryujinx.Graphics.Host1x/Ryujinx.Graphics.Host1x.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
index bff1e803b..d1a6358c2 100644
--- a/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
+++ b/src/Ryujinx.Graphics.Nvdec.FFmpeg/Ryujinx.Graphics.Nvdec.FFmpeg.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
index bff1e803b..d1a6358c2 100644
--- a/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
+++ b/src/Ryujinx.Graphics.Nvdec.Vp9/Ryujinx.Graphics.Nvdec.Vp9.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
index bfba98a73..fd49a7c80 100644
--- a/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
+++ b/src/Ryujinx.Graphics.Nvdec/Ryujinx.Graphics.Nvdec.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index 2313cc68f..3d64da99b 100644
--- a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs
index 6c95c7bdd..f4c4fef42 100644
--- a/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs
+++ b/src/Ryujinx.Graphics.Shader/IntermediateRepresentation/PhiNode.cs
@@ -69,10 +69,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
public Operand GetDest(int index)
{
- if (index != 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
+ ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
return _dest;
}
diff --git a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
index ea9a7821b..8ccf5348f 100644
--- a/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
+++ b/src/Ryujinx.Graphics.Shader/Ryujinx.Graphics.Shader.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
index 19b7999a7..c955f5b5f 100644
--- a/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
+++ b/src/Ryujinx.Graphics.Shader/Translation/Optimizations/BindlessElimination.cs
@@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
continue;
}
- if (bindlessHandle.AsgOp is not Operation handleCombineOp)
+ if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
{
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)
{
// Assume it was already checked that the operation is bitwise AND.
+
Operand src0 = asgOp.GetSource(0);
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.
// Be optimistic and assume it matches. Avoid constant buffer 1 as official drivers
// uses this one to store compiler constants.
+
return src0.GetCbufSlot() == 1 ? src1 : src0;
}
else if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.Constant)
diff --git a/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
index 70e3453c3..51721490e 100644
--- a/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
+++ b/src/Ryujinx.Graphics.Texture/Ryujinx.Graphics.Texture.csproj
@@ -1,6 +1,6 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs
index de7c9262d..8ca3f89bc 100644
--- a/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs
+++ b/src/Ryujinx.Graphics.Texture/Utils/RgbaColor32.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
@@ -102,11 +103,11 @@ namespace Ryujinx.Graphics.Texture.Utils
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static RgbaColor32 operator <<(RgbaColor32 x, int shift)
+ public static RgbaColor32 operator <<(RgbaColor32 x, [ConstantExpected] byte shift)
{
if (Sse2.IsSupported)
{
- return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, (byte)shift));
+ return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, shift));
}
else
{
@@ -115,11 +116,11 @@ namespace Ryujinx.Graphics.Texture.Utils
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static RgbaColor32 operator >>(RgbaColor32 x, int shift)
+ public static RgbaColor32 operator >>(RgbaColor32 x, [ConstantExpected] byte shift)
{
if (Sse2.IsSupported)
{
- return new RgbaColor32(Sse2.ShiftRightLogical(x._color, (byte)shift));
+ return new RgbaColor32(Sse2.ShiftRightLogical(x._color, shift));
}
else
{
diff --git a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
index 2a7cdd985..cfebcfa2a 100644
--- a/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
+++ b/src/Ryujinx.Graphics.Vic/Ryujinx.Graphics.Vic.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
index 9cf37670e..abff58a53 100644
--- a/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
+++ b/src/Ryujinx.Graphics.Video/Ryujinx.Graphics.Video.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 156b3db16..7346d7891 100644
--- a/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/src/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -189,7 +189,7 @@ namespace Ryujinx.Graphics.Vulkan
PipelineStageFlags.AllCommandsBit,
0,
1,
- new ReadOnlySpan(memoryBarrier),
+ new ReadOnlySpan(in memoryBarrier),
0,
ReadOnlySpan.Empty,
0,
diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
index 8d30457e2..f6a7be91e 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
index 983f03d4e..2d9dbc348 100644
--- a/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
+++ b/src/Ryujinx.Graphics.Vulkan/VulkanException.cs
@@ -33,9 +33,5 @@ namespace Ryujinx.Graphics.Vulkan
public VulkanException(string message, Exception innerException) : base(message, innerException)
{
}
-
- protected VulkanException(SerializationInfo info, StreamingContext context) : base(info, context)
- {
- }
}
}
diff --git a/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs b/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs
index e43c838a2..9cb1cf2c7 100644
--- a/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs
+++ b/src/Ryujinx.HLE/Exceptions/ServiceNotImplementedException.cs
@@ -35,8 +35,6 @@ namespace Ryujinx.HLE.Exceptions
Request = context.Request;
}
- protected ServiceNotImplementedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
-
public override string Message
{
get
diff --git a/src/Ryujinx.HLE/FileSystem/ContentManager.cs b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
index 724cb675c..b27eb5ead 100644
--- a/src/Ryujinx.HLE/FileSystem/ContentManager.cs
+++ b/src/Ryujinx.HLE/FileSystem/ContentManager.cs
@@ -420,10 +420,7 @@ namespace Ryujinx.HLE.FileSystem
if (locationList != null)
{
- if (locationList.Contains(entry))
- {
- locationList.Remove(entry);
- }
+ locationList.Remove(entry);
locationList.AddLast(entry);
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
new file mode 100644
index 000000000..32734574e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
@@ -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;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs
index dcfc8f4ff..4cd3e6fdd 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTable.cs
@@ -203,15 +203,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
///
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
{
- // TODO.
+ _cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert());
+
return Result.Success;
}
///
- protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
+ protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission)
{
- // TODO.
- return Result.Success;
+ // TODO: Flush JIT cache.
+
+ return Reprotect(address, pagesCount, permission);
}
///
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
index 2b00f802a..2b6d4e4e9 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Memory/KPageTableBase.cs
@@ -1255,7 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
if ((oldPermission & KMemoryPermission.Execute) != 0)
{
- result = ReprotectWithAttributes(address, pagesCount, permission);
+ result = ReprotectAndFlush(address, pagesCount, permission);
}
else
{
@@ -3036,13 +3036,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
///
- /// Changes the permissions of a given virtual memory region.
+ /// Changes the permissions of a given virtual memory region, while also flushing the cache.
///
/// Virtual address of the region to have the permission changes
/// Number of pages to have their permissions changed
/// New permission
/// Result of the permission change operation
- protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
+ protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission);
///
/// Alerts the memory tracking that a given region has been read from or written to.
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs b/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs
deleted file mode 100644
index 068cdbb88..000000000
--- a/src/Ryujinx.HLE/HOS/Kernel/Memory/MemoryPermission.cs
+++ /dev/null
@@ -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,
- }
-}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs
index 9c6d025eb..b83c642e5 100644
--- a/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Nv/NvDrvServices/NvHostCtrl/Types/NvHostSyncPt.cs
@@ -85,10 +85,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl
public void SetSyncpointMinEqualSyncpointMax(uint id)
{
- if (id >= SynchronizationManager.MaxHardwareSyncpoints)
- {
- throw new ArgumentOutOfRangeException(nameof(id));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)SynchronizationManager.MaxHardwareSyncpoints);
int value = (int)ReadSyncpointValue(id);
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs
index 07c97182c..62a7ccb59 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Bsd/Types/BsdMsgHdr.cs
@@ -27,7 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
int controlLength = message.Control == null ? 0 : message.Control.Length;
BsdSocketFlags flags = message.Flags;
- if (!MemoryMarshal.TryWrite(rawData, ref msgNameLength))
+ if (!MemoryMarshal.TryWrite(rawData, in msgNameLength))
{
return LinuxError.EFAULT;
}
@@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
rawData = rawData[msgNameLength..];
}
- if (!MemoryMarshal.TryWrite(rawData, ref iovCount))
+ if (!MemoryMarshal.TryWrite(rawData, in iovCount))
{
return LinuxError.EFAULT;
}
@@ -58,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
{
ulong iovLength = (ulong)message.Iov[index].Length;
- if (!MemoryMarshal.TryWrite(rawData, ref iovLength))
+ if (!MemoryMarshal.TryWrite(rawData, in iovLength))
{
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;
}
@@ -96,14 +96,14 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd.Types
rawData = rawData[controlLength..];
}
- if (!MemoryMarshal.TryWrite(rawData, ref flags))
+ if (!MemoryMarshal.TryWrite(rawData, in flags))
{
return LinuxError.EFAULT;
}
rawData = rawData[sizeof(BsdSocketFlags)..];
- if (!MemoryMarshal.TryWrite(rawData, ref message.Length))
+ if (!MemoryMarshal.TryWrite(rawData, in message.Length))
{
return LinuxError.EFAULT;
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs
index d0fb6675a..39af90383 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/IResolver.cs
@@ -654,7 +654,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres
}
uint sentinel = 0;
- MemoryMarshal.Write(data, ref sentinel);
+ MemoryMarshal.Write(data, in sentinel);
data = data[sizeof(uint)..];
return region.Memory.Span.Length - data.Length;
diff --git a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs
index a0613d7bc..b57b0d5ca 100644
--- a/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Sockets/Sfdnsres/Types/AddrInfoSerialized.cs
@@ -94,7 +94,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
Header.ToNetworkOrder();
- MemoryMarshal.Write(buffer, ref Header);
+ MemoryMarshal.Write(buffer, in Header);
buffer = buffer[Unsafe.SizeOf()..];
@@ -103,7 +103,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
AddrInfo4 socketAddress = SocketAddress.Value;
socketAddress.ToNetworkOrder();
- MemoryMarshal.Write(buffer, ref socketAddress);
+ MemoryMarshal.Write(buffer, in socketAddress);
buffer = buffer[Unsafe.SizeOf()..];
}
@@ -117,7 +117,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Sfdnsres.Types
Array4 rawIPv4Address = RawIPv4Address.Value;
AddrInfo4.RawIpv4AddressNetworkEndianSwap(ref rawIPv4Address);
- MemoryMarshal.Write(buffer, ref rawIPv4Address);
+ MemoryMarshal.Write(buffer, in rawIPv4Address);
buffer = buffer[Unsafe.SizeOf>()..];
}
diff --git a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs
index dab099aab..e3c05df51 100644
--- a/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs
+++ b/src/Ryujinx.HLE/HOS/Services/Ssl/SslService/SslManagedSocketConnection.cs
@@ -161,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
}
else
{
- throw exception;
+ throw;
}
}
finally
@@ -206,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Ssl.SslService
}
else
{
- throw exception;
+ throw;
}
}
finally
diff --git a/src/Ryujinx.HLE/Ryujinx.HLE.csproj b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
index f3439cc8f..370933ccf 100644
--- a/src/Ryujinx.HLE/Ryujinx.HLE.csproj
+++ b/src/Ryujinx.HLE/Ryujinx.HLE.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
index d2585c563..7b13df736 100644
--- a/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
+++ b/src/Ryujinx.Headless.SDL2/Ryujinx.Headless.SDL2.csproj
@@ -1,8 +1,8 @@
- net7.0
- win10-x64;osx-x64;linux-x64
+ net8.0
+ win-x64;osx-x64;linux-x64
Exe
true
1.0.0-dirty
@@ -34,7 +34,7 @@
-
+
@@ -69,4 +69,4 @@
true
partial
-
\ No newline at end of file
+
diff --git a/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
index d04c5a9b6..fa1544c4f 100644
--- a/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
+++ b/src/Ryujinx.Horizon.Common/Ryujinx.Horizon.Common.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
index 0139c367f..ae40f7b5e 100644
--- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
+++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
index 817a96e2e..1ab79d08a 100644
--- a/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
+++ b/src/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Input/Ryujinx.Input.csproj b/src/Ryujinx.Input/Ryujinx.Input.csproj
index df462734f..59a9eeb61 100644
--- a/src/Ryujinx.Input/Ryujinx.Input.csproj
+++ b/src/Ryujinx.Input/Ryujinx.Input.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.Memory/AddressSpaceManager.cs b/src/Ryujinx.Memory/AddressSpaceManager.cs
index 65b4d48f2..b8d48365c 100644
--- a/src/Ryujinx.Memory/AddressSpaceManager.cs
+++ b/src/Ryujinx.Memory/AddressSpaceManager.cs
@@ -455,6 +455,11 @@ namespace Ryujinx.Memory
return _pageTable.Read(va) + (nuint)(va & PageMask);
}
+ ///
+ public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ }
+
///
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
diff --git a/src/Ryujinx.Memory/IVirtualMemoryManager.cs b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
index edbfc8855..8c9ca1684 100644
--- a/src/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/src/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -104,6 +104,12 @@ namespace Ryujinx.Memory
/// True if the data was changed, false otherwise
bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan data);
+ ///
+ /// Fills the specified memory region with the value specified in .
+ ///
+ /// Virtual address to fill the value into
+ /// Size of the memory region to fill
+ /// Value to fill with
void Fill(ulong va, ulong size, byte value)
{
const int MaxChunkSize = 1 << 24;
@@ -194,6 +200,14 @@ namespace Ryujinx.Memory
/// Optional ID of the handles that should not be signalled
void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
+ ///
+ /// Reprotect a region of virtual memory for guest access.
+ ///
+ /// Virtual address base
+ /// Size of the region to protect
+ /// Memory protection to set
+ void Reprotect(ulong va, ulong size, MemoryPermission protection);
+
///
/// Reprotect a region of virtual memory for tracking.
///
diff --git a/src/Ryujinx.Memory/Range/MultiRange.cs b/src/Ryujinx.Memory/Range/MultiRange.cs
index 7011e528e..5a0b4178a 100644
--- a/src/Ryujinx.Memory/Range/MultiRange.cs
+++ b/src/Ryujinx.Memory/Range/MultiRange.cs
@@ -52,10 +52,7 @@ namespace Ryujinx.Memory.Range
{
if (HasSingleRange)
{
- if (_singleRange.Size - offset < size)
- {
- throw new ArgumentOutOfRangeException(nameof(size));
- }
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(size, _singleRange.Size - offset);
return new MultiRange(_singleRange.Address + offset, size);
}
@@ -108,10 +105,7 @@ namespace Ryujinx.Memory.Range
{
if (HasSingleRange)
{
- if (index != 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index));
- }
+ ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
return _singleRange;
}
diff --git a/src/Ryujinx.Memory/Ryujinx.Memory.csproj b/src/Ryujinx.Memory/Ryujinx.Memory.csproj
index 91e46e48e..8310a3e5c 100644
--- a/src/Ryujinx.Memory/Ryujinx.Memory.csproj
+++ b/src/Ryujinx.Memory/Ryujinx.Memory.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
diff --git a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
index 955e6d3f1..8e7953045 100644
--- a/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
+++ b/src/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
diff --git a/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
index 74b4ec2f7..ab89fb5c7 100644
--- a/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
+++ b/src/Ryujinx.ShaderTools/Ryujinx.ShaderTools.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
Exe
Debug;Release
diff --git a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs
index 59dc1a525..435bb35ae 100644
--- a/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs
+++ b/src/Ryujinx.Tests.Memory/MockVirtualMemoryManager.cs
@@ -102,6 +102,11 @@ namespace Ryujinx.Tests.Memory
throw new NotImplementedException();
}
+ public void Reprotect(ulong va, ulong size, MemoryPermission protection)
+ {
+ throw new NotImplementedException();
+ }
+
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
OnProtect?.Invoke(va, size, protection);
diff --git a/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj b/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj
index 4dcb69623..f05060838 100644
--- a/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj
+++ b/src/Ryujinx.Tests.Memory/Ryujinx.Tests.Memory.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
false
diff --git a/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj b/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
index d925546fe..befacfb22 100644
--- a/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
+++ b/src/Ryujinx.Tests.Unicorn/Ryujinx.Tests.Unicorn.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
Debug;Release
diff --git a/src/Ryujinx.Tests/Ryujinx.Tests.csproj b/src/Ryujinx.Tests/Ryujinx.Tests.csproj
index ab331ce58..3be9787a3 100644
--- a/src/Ryujinx.Tests/Ryujinx.Tests.csproj
+++ b/src/Ryujinx.Tests/Ryujinx.Tests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
Exe
false
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationData.cs b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
index 1be883ee1..65ab01eeb 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationData.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
@@ -9,10 +9,9 @@ using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
+using Ryujinx.Ui.Common.Helper;
using System;
-using System.Globalization;
using System.IO;
-using System.Text.Json.Serialization;
namespace Ryujinx.Ui.App.Common
{
@@ -24,29 +23,18 @@ namespace Ryujinx.Ui.App.Common
public string TitleId { get; set; }
public string Developer { get; set; }
public string Version { get; set; }
- public string TimePlayed { get; set; }
- public double TimePlayedNum { get; set; }
+ public TimeSpan TimePlayed { get; set; }
public DateTime? LastPlayed { get; set; }
public string FileExtension { get; set; }
- public string FileSize { get; set; }
- public double FileSizeBytes { get; set; }
+ public long FileSize { get; set; }
public string Path { get; set; }
public BlitStruct ControlHolder { get; set; }
- [JsonIgnore]
- public string LastPlayedString
- {
- get
- {
- if (!LastPlayed.HasValue)
- {
- // TODO: maybe put localized string here instead of just "Never"
- return "Never";
- }
+ public string TimePlayedString => ValueFormatUtils.FormatTimeSpan(TimePlayed);
- 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)
{
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
index 1ae96b9fc..077a78946 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationLibrary.cs
@@ -155,7 +155,7 @@ namespace Ryujinx.Ui.App.Common
return;
}
- double fileSize = new FileInfo(applicationPath).Length * 0.000000000931;
+ long fileSize = new FileInfo(applicationPath).Length;
string titleName = "Unknown";
string titleId = "0000000000000000";
string developer = "Unknown";
@@ -425,25 +425,25 @@ namespace Ryujinx.Ui.App.Common
{
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.
- return;
+ appMetadata.TimePlayed = TimeSpan.FromSeconds(appMetadata.TimePlayedOld);
+ appMetadata.TimePlayedOld = default;
}
- // Migrate from string-based last_played to DateTime-based last_played_utc.
- if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
+ // 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)
{
- Logger.Info?.Print(LogClass.Application, $"last_played found: \"{appMetadata.LastPlayedOld}\", migrating to last_played_utc");
- appMetadata.LastPlayed = lastPlayedOldParsed;
+ // Migrate from string-based last_played to DateTime-based last_played_utc.
+ if (DateTime.TryParse(appMetadata.LastPlayedOld, out DateTime lastPlayedOldParsed))
+ {
+ appMetadata.LastPlayed = lastPlayedOldParsed;
+
+ // Migration successful: deleting last_played from the metadata file.
+ appMetadata.LastPlayedOld = default;
+ }
- // Migration successful: deleting last_played from the metadata file.
- 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,
Developer = developer,
Version = version,
- TimePlayed = ConvertSecondsToFormattedString(appMetadata.TimePlayed),
- TimePlayedNum = appMetadata.TimePlayed,
+ TimePlayed = appMetadata.TimePlayed,
LastPlayed = appMetadata.LastPlayed,
- FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
- FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + " MiB" : fileSize.ToString("0.##") + " GiB",
- FileSizeBytes = fileSize,
+ FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper(),
+ FileSize = fileSize,
Path = applicationPath,
ControlHolder = controlHolder,
};
@@ -716,31 +714,6 @@ namespace Ryujinx.Ui.App.Common
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)
{
_ = Enum.TryParse(_desiredTitleLanguage.ToString(), out TitleLanguage desiredTitleLanguage);
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs b/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs
index 01b857a62..9e2ca6870 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationMetadata.cs
@@ -7,13 +7,45 @@ namespace Ryujinx.Ui.App.Common
{
public string Title { 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")]
public DateTime? LastPlayed { get; set; } = null;
+ [JsonPropertyName("time_played")]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
+ public double TimePlayedOld { get; set; }
+
[JsonPropertyName("last_played")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string LastPlayedOld { get; set; }
+
+ ///
+ /// Updates . Call this before launching a game.
+ ///
+ public void UpdatePreGame()
+ {
+ LastPlayed = DateTime.UtcNow;
+ }
+
+ ///
+ /// Updates and . Call this after a game ends.
+ ///
+ 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));
+ }
}
}
diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationLoadResult.cs b/src/Ryujinx.Ui.Common/Configuration/ConfigurationLoadResult.cs
deleted file mode 100644
index 71366ba7c..000000000
--- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationLoadResult.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Ryujinx.Ui.Common.Configuration
-{
- public enum ConfigurationLoadResult
- {
- Success = 0,
- NotLoaded = 1,
- MigratedFromPreVulkan = 1 << 8,
- }
-}
diff --git a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs b/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
index 9ed8fd8cc..b017d384c 100644
--- a/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
+++ b/src/Ryujinx.Ui.Common/Configuration/ConfigurationState.cs
@@ -5,6 +5,7 @@ using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Configuration.Multiplayer;
using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Vulkan;
using Ryujinx.Ui.Common.Configuration.System;
using Ryujinx.Ui.Common.Configuration.Ui;
using Ryujinx.Ui.Common.Helper;
@@ -763,7 +764,7 @@ namespace Ryujinx.Ui.Common.Configuration
Graphics.ResScaleCustom.Value = 1.0f;
Graphics.MaxAnisotropy.Value = -1.0f;
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
- Graphics.GraphicsBackend.Value = OperatingSystem.IsMacOS() ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl;
+ Graphics.GraphicsBackend.Value = DefaultGraphicsBackend();
Graphics.PreferredGpu.Value = "";
Graphics.ShadersDumpPath.Value = "";
Logger.EnableDebug.Value = false;
@@ -784,7 +785,7 @@ namespace Ryujinx.Ui.Common.Configuration
EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
ShowConfirmExit.Value = true;
- HideCursor.Value = HideCursorMode.Never;
+ HideCursor.Value = HideCursorMode.OnIdle;
Graphics.EnableVsync.Value = true;
Graphics.EnableShaderCache.Value = true;
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;
@@ -916,12 +917,8 @@ namespace Ryujinx.Ui.Common.Configuration
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Unsupported configuration version {configurationFileFormat.Version}, loading default.");
LoadDefault();
-
- return ConfigurationLoadResult.NotLoaded;
}
- ConfigurationLoadResult result = ConfigurationLoadResult.Success;
-
if (configurationFileFormat.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;
- result |= ConfigurationLoadResult.MigratedFromPreVulkan;
-
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}");
}
+ }
- 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(ReactiveEventArgs eventArgs, string valueName)
diff --git a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs b/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
index dab473fa3..103b78c24 100644
--- a/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
+++ b/src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
@@ -30,7 +30,7 @@ namespace Ryujinx.Ui.Common.Helper
graphic.DrawImage(image, 0, 0, 128, 128);
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.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
}
@@ -46,16 +46,16 @@ namespace Ryujinx.Ui.Common.Helper
image.SaveAsPng(iconPath);
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")]
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");
// 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");
if (!Directory.Exists(scriptFolderPath))
@@ -69,7 +69,7 @@ namespace Ryujinx.Ui.Common.Helper
using StreamWriter scriptFile = new(scriptPath);
scriptFile.WriteLine("#!/bin/sh");
- scriptFile.WriteLine(GetArgsString(basePath, appFilePath));
+ scriptFile.WriteLine($"{basePath} {GetArgsString(appFilePath)}");
// Set execute permission
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.");
}
- 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
- var argsList = new List
- {
- basePath,
- };
+ var argsList = new List();
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
{
@@ -141,7 +138,6 @@ namespace Ryujinx.Ui.Common.Helper
argsList.Add($"\"{appFilePath}\"");
-
return String.Join(" ", argsList);
}
diff --git a/src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs b/src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs
new file mode 100644
index 000000000..951cd089e
--- /dev/null
+++ b/src/Ryujinx.Ui.Common/Helper/ValueFormatUtils.cs
@@ -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
+ };
+
+ ///
+ /// Used by .
+ ///
+ 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
+
+ ///
+ /// Creates a human-readable string from a .
+ ///
+ /// The to be formatted.
+ /// A formatted string that can be displayed in the UI.
+ 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}";
+ }
+
+ ///
+ /// Creates a human-readable string from a .
+ ///
+ /// The to be formatted. This is expected to be UTC-based.
+ /// The that's used in formatting. Defaults to .
+ /// A formatted string that can be displayed in the UI.
+ 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);
+ }
+
+ ///
+ /// Creates a human-readable file size string.
+ ///
+ /// The file size in bytes.
+ /// Formats the passed size value as this unit, bypassing the automatic unit choice.
+ /// A human-readable file size string.
+ 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
+
+ ///
+ /// Parses a string generated by and returns the original .
+ ///
+ /// A string representing a .
+ /// A object. If the input string couldn't been parsed, is returned.
+ 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;
+ }
+
+ ///
+ /// Parses a string generated by and returns the original .
+ ///
+ /// The string representing a .
+ /// A object. If the input string couldn't be parsed, is returned.
+ 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;
+ }
+
+ ///
+ /// Parses a string generated by and returns a representing a number of bytes.
+ ///
+ /// A string representing a file size formatted with .
+ /// A representing a number of bytes.
+ 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
+ }
+}
diff --git a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj
index 3da47431f..74331fdef 100644
--- a/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj
+++ b/src/Ryujinx.Ui.Common/Ryujinx.Ui.Common.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0
true
@@ -62,6 +62,7 @@
+
diff --git a/src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs b/src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs
similarity index 98%
rename from src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs
rename to src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs
index 08aa452eb..5f1ab5416 100644
--- a/src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs
+++ b/src/Ryujinx.Ui.Common/SystemInfo/LinuxSystemInfo.cs
@@ -5,7 +5,7 @@ using System.Globalization;
using System.IO;
using System.Runtime.Versioning;
-namespace Ryujinx.Common.SystemInfo
+namespace Ryujinx.Ui.Common.SystemInfo
{
[SupportedOSPlatform("linux")]
class LinuxSystemInfo : SystemInfo
diff --git a/src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs b/src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs
similarity index 99%
rename from src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs
rename to src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs
index a968ad17b..3508ae3a4 100644
--- a/src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs
+++ b/src/Ryujinx.Ui.Common/SystemInfo/MacOSSystemInfo.cs
@@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
-namespace Ryujinx.Common.SystemInfo
+namespace Ryujinx.Ui.Common.SystemInfo
{
[SupportedOSPlatform("macos")]
partial class MacOSSystemInfo : SystemInfo
diff --git a/src/Ryujinx.Common/SystemInfo/SystemInfo.cs b/src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs
similarity index 87%
rename from src/Ryujinx.Common/SystemInfo/SystemInfo.cs
rename to src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs
index 55ec0127c..6a4fe6803 100644
--- a/src/Ryujinx.Common/SystemInfo/SystemInfo.cs
+++ b/src/Ryujinx.Ui.Common/SystemInfo/SystemInfo.cs
@@ -1,10 +1,11 @@
using Ryujinx.Common.Logging;
+using Ryujinx.Ui.Common.Helper;
using System;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
using System.Text;
-namespace Ryujinx.Common.SystemInfo
+namespace Ryujinx.Ui.Common.SystemInfo
{
public class SystemInfo
{
@@ -20,13 +21,13 @@ namespace Ryujinx.Common.SystemInfo
CpuName = "Unknown";
}
- private static string ToMiBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : $"{bytesValue / 1024 / 1024} MiB";
+ private static string ToGBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : ValueFormatUtils.FormatFileSize((long)bytesValue, ValueFormatUtils.FileSizeUnits.Gibibytes);
public void Print()
{
Logger.Notice.Print(LogClass.Application, $"Operating System: {OsDescription}");
Logger.Notice.Print(LogClass.Application, $"CPU: {CpuName}");
- Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToMiBString(RamTotal)} ; Available {ToMiBString(RamAvailable)}");
+ Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToGBString(RamTotal)} ; Available {ToGBString(RamAvailable)}");
}
public static SystemInfo Gather()
diff --git a/src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs b/src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs
similarity index 98%
rename from src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs
rename to src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs
index 3b36d6e2e..9bb0fbf74 100644
--- a/src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs
+++ b/src/Ryujinx.Ui.Common/SystemInfo/WindowsSystemInfo.cs
@@ -4,7 +4,7 @@ using System.Management;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
-namespace Ryujinx.Common.SystemInfo
+namespace Ryujinx.Ui.Common.SystemInfo
{
[SupportedOSPlatform("windows")]
partial class WindowsSystemInfo : SystemInfo
diff --git a/src/Ryujinx/Program.cs b/src/Ryujinx/Program.cs
index 50151d733..597d00f30 100644
--- a/src/Ryujinx/Program.cs
+++ b/src/Ryujinx/Program.cs
@@ -3,7 +3,6 @@ using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.GraphicsDriver;
using Ryujinx.Common.Logging;
-using Ryujinx.Common.SystemInfo;
using Ryujinx.Common.SystemInterop;
using Ryujinx.Modules;
using Ryujinx.SDL2.Common;
@@ -11,6 +10,7 @@ using Ryujinx.Ui;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
+using Ryujinx.Ui.Common.SystemInfo;
using Ryujinx.Ui.Widgets;
using SixLabors.ImageSharp.Formats.Jpeg;
using System;
@@ -177,8 +177,6 @@ namespace Ryujinx
? appDataConfigurationPath
: null;
- bool showVulkanPrompt = false;
-
if (ConfigurationPath == null)
{
// No configuration, we load the default values and save it to disk
@@ -186,26 +184,17 @@ namespace Ryujinx
ConfigurationState.Instance.LoadDefault();
ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
-
- showVulkanPrompt = true;
}
else
{
if (ConfigurationFileFormat.TryLoad(ConfigurationPath, out ConfigurationFileFormat configurationFileFormat))
{
- ConfigurationLoadResult result = ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
-
- if ((result & ConfigurationLoadResult.MigratedFromPreVulkan) != 0)
- {
- showVulkanPrompt = true;
- }
+ ConfigurationState.Instance.Load(configurationFileFormat, ConfigurationPath);
}
else
{
ConfigurationState.Instance.LoadDefault();
- showVulkanPrompt = true;
-
Logger.Warning?.PrintMsg(LogClass.Application, $"Failed to load config! Loading the default config instead.\nFailed config location {ConfigurationPath}");
}
}
@@ -216,12 +205,10 @@ namespace Ryujinx
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
- showVulkanPrompt = false;
}
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
{
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
- showVulkanPrompt = false;
}
}
@@ -343,35 +330,6 @@ namespace Ryujinx
}, TaskContinuationOptions.OnlyOnFaulted);
}
- if (showVulkanPrompt)
- {
- var buttonTexts = new Dictionary()
- {
- { 0, "Yes (Vulkan)" },
- { 1, "No (OpenGL)" },
- };
-
- ResponseType response = GtkDialog.CreateCustomDialog(
- "Ryujinx - Default graphics backend",
- "Use Vulkan as default graphics backend?",
- "Ryujinx now supports the Vulkan API. " +
- "Vulkan greatly improves shader compilation performance, " +
- "and fixes some graphical glitches; however, since it is a new feature, " +
- "you may experience some issues that did not occur with OpenGL.\n\n" +
- "Note that you will also lose any existing shader cache the first time you start a game " +
- "on version 1.1.200 onwards, because Vulkan required changes to the shader cache that makes it incompatible with previous versions.\n\n" +
- "Would you like to set Vulkan as the default graphics backend? " +
- "You can change this at any time on the settings window.",
- buttonTexts,
- MessageType.Question);
-
- ConfigurationState.Instance.Graphics.GraphicsBackend.Value = response == 0
- ? GraphicsBackend.Vulkan
- : GraphicsBackend.OpenGl;
-
- ConfigurationState.Instance.ToFileFormat().SaveConfig(ConfigurationPath);
- }
-
Application.Run();
}
diff --git a/src/Ryujinx/Ryujinx.csproj b/src/Ryujinx/Ryujinx.csproj
index 5b5ed4637..9890b761b 100644
--- a/src/Ryujinx/Ryujinx.csproj
+++ b/src/Ryujinx/Ryujinx.csproj
@@ -1,8 +1,8 @@
- net7.0
- win10-x64;osx-x64;linux-x64
+ net8.0
+ win-x64;osx-x64;linux-x64
Exe
true
1.0.0-dirty
@@ -25,7 +25,7 @@
-
+
diff --git a/src/Ryujinx/Ui/Helper/SortHelper.cs b/src/Ryujinx/Ui/Helper/SortHelper.cs
index 0c0eefd2c..c7a72ab9b 100644
--- a/src/Ryujinx/Ui/Helper/SortHelper.cs
+++ b/src/Ryujinx/Ui/Helper/SortHelper.cs
@@ -1,4 +1,5 @@
using Gtk;
+using Ryujinx.Ui.Common.Helper;
using System;
namespace Ryujinx.Ui.Helper
@@ -7,88 +8,26 @@ namespace Ryujinx.Ui.Helper
{
public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
{
- static string ReverseFormat(string time)
- {
- if (time == "Never")
- {
- return "00";
- }
+ TimeSpan aTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(a, 5).ToString());
+ TimeSpan bTimeSpan = ValueFormatUtils.ParseTimeSpan(model.GetValue(b, 5).ToString());
- var numbers = time.Split(new char[] { 'd', 'h', 'm' });
-
- time = time.Replace(" ", "").Replace("d", ".").Replace("h", ":").Replace("m", "");
-
- if (numbers.Length == 2)
- {
- return $"00.00:{time}";
- }
- else if (numbers.Length == 3)
- {
- return $"00.{time}";
- }
-
- return time;
- }
-
- string aValue = ReverseFormat(model.GetValue(a, 5).ToString());
- string bValue = ReverseFormat(model.GetValue(b, 5).ToString());
-
- return TimeSpan.Compare(TimeSpan.Parse(aValue), TimeSpan.Parse(bValue));
+ return TimeSpan.Compare(aTimeSpan, bTimeSpan);
}
public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
{
- string aValue = model.GetValue(a, 6).ToString();
- string bValue = model.GetValue(b, 6).ToString();
+ DateTime aDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(a, 6).ToString());
+ DateTime bDateTime = ValueFormatUtils.ParseDateTime(model.GetValue(b, 6).ToString());
- if (aValue == "Never")
- {
- aValue = DateTime.UnixEpoch.ToString();
- }
-
- if (bValue == "Never")
- {
- bValue = DateTime.UnixEpoch.ToString();
- }
-
- return DateTime.Compare(DateTime.Parse(bValue), DateTime.Parse(aValue));
+ return DateTime.Compare(aDateTime, bDateTime);
}
public static int FileSizeSort(ITreeModel model, TreeIter a, TreeIter b)
{
- string aValue = model.GetValue(a, 8).ToString();
- string bValue = model.GetValue(b, 8).ToString();
+ long aSize = ValueFormatUtils.ParseFileSize(model.GetValue(a, 8).ToString());
+ long bSize = ValueFormatUtils.ParseFileSize(model.GetValue(b, 8).ToString());
- if (aValue[^3..] == "GiB")
- {
- aValue = (float.Parse(aValue[0..^3]) * 1024).ToString();
- }
- else
- {
- aValue = aValue[0..^3];
- }
-
- if (bValue[^3..] == "GiB")
- {
- bValue = (float.Parse(bValue[0..^3]) * 1024).ToString();
- }
- else
- {
- bValue = bValue[0..^3];
- }
-
- if (float.Parse(aValue) > float.Parse(bValue))
- {
- return -1;
- }
- else if (float.Parse(bValue) > float.Parse(aValue))
- {
- return 1;
- }
- else
- {
- return 0;
- }
+ return aSize.CompareTo(bSize);
}
}
}
diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs
index 7fe2bfe5b..efd72baaf 100644
--- a/src/Ryujinx/Ui/MainWindow.cs
+++ b/src/Ryujinx/Ui/MainWindow.cs
@@ -959,7 +959,7 @@ namespace Ryujinx.Ui
ApplicationLibrary.LoadAndSaveMetaData(_emulationContext.Processes.ActiveApplication.ProgramIdText, appMetadata =>
{
- appMetadata.LastPlayed = DateTime.UtcNow;
+ appMetadata.UpdatePreGame();
});
}
}
@@ -1132,13 +1132,7 @@ namespace Ryujinx.Ui
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
- if (appMetadata.LastPlayed.HasValue)
- {
- double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
- appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
- }
-
- appMetadata.LastPlayed = DateTime.UtcNow;
+ appMetadata.UpdatePostGame();
});
}
}
@@ -1212,10 +1206,10 @@ namespace Ryujinx.Ui
$"{args.AppData.TitleName}\n{args.AppData.TitleId.ToUpper()}",
args.AppData.Developer,
args.AppData.Version,
- args.AppData.TimePlayed,
+ args.AppData.TimePlayedString,
args.AppData.LastPlayedString,
args.AppData.FileExtension,
- args.AppData.FileSize,
+ args.AppData.FileSizeString,
args.AppData.Path,
args.AppData.ControlHolder);
});
diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
index 75b166136..734437eea 100644
--- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
+++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
@@ -211,6 +211,8 @@ namespace Ryujinx.Ui.Widgets
_manageSubMenu.Append(_openPtcDirMenuItem);
_manageSubMenu.Append(_openShaderCacheDirMenuItem);
+ Add(_createShortcutMenuItem);
+ Add(new SeparatorMenuItem());
Add(_openSaveUserDirMenuItem);
Add(_openSaveDeviceDirMenuItem);
Add(_openSaveBcatDirMenuItem);
@@ -223,7 +225,6 @@ namespace Ryujinx.Ui.Widgets
Add(new SeparatorMenuItem());
Add(_manageCacheMenuItem);
Add(_extractMenuItem);
- Add(_createShortcutMenuItem);
ShowAll();
}
diff --git a/src/Spv.Generator/Spv.Generator.csproj b/src/Spv.Generator/Spv.Generator.csproj
index 082dac9c2..ae2821edb 100644
--- a/src/Spv.Generator/Spv.Generator.csproj
+++ b/src/Spv.Generator/Spv.Generator.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net8.0