Compare commits

..

9 Commits

Author SHA1 Message Date
Mary-nyan
1865ea87e5 bsd: Fix eventfd broken logic (#3647)
* bsd: Fix eventfd broken logic

This commit fix eventfd logic being broken.

The following changes were made:
- EventFd IPC definition had argument inverted
- EventFd events weren't fired correctly
- Poll logic was wrong and unfinished for eventfd
- Reintroduce workaround from #3385 but in a safer way, and spawn 4
  threads.

* ipc: Rework a bit for multithreads

* Clean up debug logs

* Make server thread yield when managed lock isn't availaible

* Fix replyTargetHandle not being added in the proper locking scope

* Simplify some scopes

* Address gdkchan's comments

* Revert IPC workaround for now

* Reintroduce the EventFileDescriptor workaround
2022-11-27 20:18:05 +00:00
&Olga
18b61aff59 Unbreak bug_report.md (#3915)
* Unbreak bug_report.md

* Update bug_report.md
2022-11-27 20:11:51 +00:00
Luminoso-256
cb22629ac1 HLE: fix small issue in IPsmSession (#3909) 2022-11-27 01:10:42 +00:00
TSRBerry
6f0f99ee2b Avalonia: Fix OpenGL crashing on Linux (#3902)
* ava: Fix OpenGL crashing on Linux

Fixes a regression from #3901

* Fix formatting
2022-11-26 12:06:53 +01:00
TSRBerry
70f2da8fdf ava: Fix invisible vulkan window on Linux (#3901)
Co-authored-by: emmauss <emmausssss@gmail.com>

Co-authored-by: emmauss <emmausssss@gmail.com>
2022-11-25 17:40:44 +00:00
Ac_K
5d3ef7761b ava: Refactor Title Update Manager window (#3898)
* ava: Refactor TitleUpdate Manager window

* Update locale
2022-11-25 17:55:08 +01:00
riperiperi
476b4683cf Fix CB0 alignment with addresses used for 8/16-bit LDG/STG (#3897)
This replacement is meant to be done with the original identified byteOffset, not the one assigned later on by the below conditionals (that already has the constant offset added, for instance).

This fixes videos being pixelated in Xenoblade 3, and other regressions that might have happened since #3847.
2022-11-25 14:39:03 +00:00
Mary-nyan
5fb5079730 chore: Update Avalonia related dependencies (#3891) 2022-11-25 13:27:41 +00:00
Ac_K
3fbacd0f49 ava: Rework DLC Manager, Add various fixes and cleanup (#3896)
* Fixes Everything Part.2

* Change sorting, fix remove and heading
2022-11-25 12:41:34 +01:00
21 changed files with 594 additions and 451 deletions

View File

@@ -1,6 +1,6 @@
--- ---
name: Bug Report name: Bug Report
about: Something doesn't work correctly in Ryujinx. Note that game-specific issues should be instead posted on the Game Compatibility List at https://github.com/Ryujinx/Ryujinx-Games-List, unless it is a provable regression. about: Something doesn't work correctly in Ryujinx. Game-specific issues should be posted at https://github.com/Ryujinx/Ryujinx-Games-List instead, unless it is a provable regression.
#assignees: #assignees:
--- ---

View File

@@ -410,6 +410,8 @@
"DlcManagerTableHeadingContainerPathLabel": "Container Path", "DlcManagerTableHeadingContainerPathLabel": "Container Path",
"DlcManagerTableHeadingFullPathLabel": "Full Path", "DlcManagerTableHeadingFullPathLabel": "Full Path",
"DlcManagerRemoveAllButton": "Remove All", "DlcManagerRemoveAllButton": "Remove All",
"DlcManagerEnableAllButton": "Enable All",
"DlcManagerDisableAllButton": "Disable All",
"MenuBarOptionsChangeLanguage": "Change Language", "MenuBarOptionsChangeLanguage": "Change Language",
"CommonSort": "Sort", "CommonSort": "Sort",
"CommonShowNames": "Show Names", "CommonShowNames": "Show Names",
@@ -562,12 +564,12 @@
"Writable": "Writable", "Writable": "Writable",
"SelectDlcDialogTitle": "Select DLC files", "SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files", "SelectUpdateDialogTitle": "Select update files",
"UserProfileWindowTitle": "Manage User Profiles", "UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Manage Game Cheats", "CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Game DLC", "DlcWindowTitle": "Downloadable Content Manager",
"UpdateWindowTitle": "Manage Game Updates", "UpdateWindowTitle": "Title Update Manager",
"CheatWindowHeading": "Cheats Available for {0} [{1}]", "CheatWindowHeading": "Cheats Available for {0} [{1}]",
"DlcWindowHeading": "DLC Available for {0} [{1}]", "DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel", "Cancel": "Cancel",
"Save": "Save", "Save": "Save",
@@ -575,7 +577,7 @@
"UserProfilesSetProfileImage": "Set Profile Image", "UserProfilesSetProfileImage": "Set Profile Image",
"UserProfileEmptyNameError": "Name is required", "UserProfileEmptyNameError": "Name is required",
"UserProfileNoImageError": "Profile image must be set", "UserProfileNoImageError": "Profile image must be set",
"GameUpdateWindowHeading": "Updates Available for {0} [{1}]", "GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:", "SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:", "SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
"UserProfilesName": "Name:", "UserProfilesName": "Name:",

View File

@@ -23,19 +23,18 @@ namespace Ryujinx.Ava
{ {
internal class Program internal class Program
{ {
public static double WindowScaleFactor { get; set; } public static double WindowScaleFactor { get; set; }
public static double ActualScaleFactor { get; set; } public static double ActualScaleFactor { get; set; }
public static string Version { get; private set; } public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; } public static string ConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; } public static bool PreviewerDetached { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
public static RenderTimer RenderTimer { get; private set; }
[DllImport("user32.dll", SetLastError = true)] [DllImport("user32.dll", SetLastError = true)]
public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type); public static extern int MessageBoxA(IntPtr hWnd, string text, string caption, uint type);
private const uint MB_ICONWARNING = 0x30; private const uint MB_ICONWARNING = 0x30;
private const int BaseDpi = 96; private const int BaseDpi = 96;
public static void Main(string[] args) public static void Main(string[] args)
{ {
@@ -43,7 +42,7 @@ namespace Ryujinx.Ava
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{ {
MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING); _ = MessageBoxA(IntPtr.Zero, "You are running an outdated version of Windows.\n\nStarting on June 1st 2022, Ryujinx will only support Windows 10 1803 and newer.\n", $"Ryujinx {Version}", MB_ICONWARNING);
} }
PreviewerDetached = true; PreviewerDetached = true;
@@ -64,16 +63,16 @@ namespace Ryujinx.Ava
.With(new X11PlatformOptions .With(new X11PlatformOptions
{ {
EnableMultiTouch = true, EnableMultiTouch = true,
EnableIme = true, EnableIme = true,
UseEGL = false, UseEGL = false,
UseGpu = false UseGpu = false
}) })
.With(new Win32PlatformOptions .With(new Win32PlatformOptions
{ {
EnableMultitouch = true, EnableMultitouch = true,
UseWgl = false, UseWgl = false,
AllowEglInitialization = false, AllowEglInitialization = false,
CompositionBackdropCornerRadius = 8f, CompositionBackdropCornerRadius = 8.0f,
}) })
.UseSkia() .UseSkia()
.AfterSetup(_ => .AfterSetup(_ =>
@@ -122,12 +121,10 @@ namespace Ryujinx.Ava
PrintSystemInfo(); PrintSystemInfo();
// Enable OGL multithreading on the driver, when available. // Enable OGL multithreading on the driver, when available.
BackendThreading threadingMode = ConfigurationState.Instance.Graphics.BackendThreading; DriverUtilities.ToggleOGLThreading(ConfigurationState.Instance.Graphics.BackendThreading == BackendThreading.Off);
DriverUtilities.ToggleOGLThreading(threadingMode == BackendThreading.Off);
// Check if keys exists. // Check if keys exists.
bool hasSystemProdKeys = File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")); if (!File.Exists(Path.Combine(AppDataManager.KeysDirPath, "prod.keys")))
if (!hasSystemProdKeys)
{ {
if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys")))) if (!(AppDataManager.Mode == AppDataManager.LaunchMode.UserProfile && File.Exists(Path.Combine(AppDataManager.KeysDirPathUser, "prod.keys"))))
{ {
@@ -143,7 +140,7 @@ namespace Ryujinx.Ava
public static void ReloadConfig() public static void ReloadConfig()
{ {
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json"); string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json"); string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
// Now load the configuration as the other subsystems are now registered // Now load the configuration as the other subsystems are now registered
@@ -197,8 +194,7 @@ namespace Ryujinx.Ava
Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}"); Logger.Notice.Print(LogClass.Application, $"Ryujinx Version: {Version}");
SystemInfo.Gather().Print(); SystemInfo.Gather().Print();
var enabledLogs = Logger.GetEnabledLevels(); Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(Logger.GetEnabledLevels().Count == 0 ? "<None>" : string.Join(", ", Logger.GetEnabledLevels()))}");
Logger.Notice.Print(LogClass.Application, $"Logs Enabled: {(enabledLogs.Count == 0 ? "<None>" : string.Join(", ", enabledLogs))}");
if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom) if (AppDataManager.Mode == AppDataManager.LaunchMode.Custom)
{ {

View File

@@ -19,16 +19,16 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.18" /> <PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.15" /> <PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.15" /> <PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.15" /> <PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.15" /> <PackageReference Include="Avalonia.Markup.Xaml.Loader" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg" Version="0.10.14" /> <PackageReference Include="Avalonia.Svg" Version="0.10.18" />
<PackageReference Include="Avalonia.Svg.Skia" Version="0.10.14" /> <PackageReference Include="Avalonia.Svg.Skia" Version="0.10.18" />
<PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" /> <PackageReference Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
<PackageReference Include="DynamicData" Version="7.9.4" /> <PackageReference Include="DynamicData" Version="7.12.8" />
<PackageReference Include="FluentAvaloniaUI" Version="1.4.1" /> <PackageReference Include="FluentAvaloniaUI" Version="1.4.4" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" /> <PackageReference Include="XamlNameReferenceGenerator" Version="1.4.2" />
<PackageReference Include="OpenTK.Core" Version="4.7.2" /> <PackageReference Include="OpenTK.Core" Version="4.7.2" />
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" /> <PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />

View File

@@ -6,8 +6,8 @@ using SPB.Graphics;
using SPB.Platform; using SPB.Platform;
using SPB.Platform.GLX; using SPB.Platform.GLX;
using SPB.Platform.X11; using SPB.Platform.X11;
using SPB.Windowing;
using System; using System;
using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -15,12 +15,12 @@ using static Ryujinx.Ava.Ui.Controls.Win32NativeInterop;
namespace Ryujinx.Ava.Ui.Controls namespace Ryujinx.Ava.Ui.Controls
{ {
public unsafe class EmbeddedWindow : NativeControlHost public class EmbeddedWindow : NativeControlHost
{ {
private WindowProc _wndProcDelegate; private WindowProc _wndProcDelegate;
private string _className; private string _className;
protected GLXWindow X11Window { get; private set; } protected GLXWindow X11Window { get; set; }
protected IntPtr WindowHandle { get; set; } protected IntPtr WindowHandle { get; set; }
protected IntPtr X11Display { get; set; } protected IntPtr X11Display { get; set; }
@@ -94,19 +94,17 @@ namespace Ryujinx.Ava.Ui.Controls
} }
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
IPlatformHandle CreateLinux(IPlatformHandle parent) protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
{ {
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow; X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
WindowHandle = X11Window.WindowHandle.RawHandle; WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
return new PlatformHandle(WindowHandle, "X11"); return new PlatformHandle(WindowHandle, "X11");
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
unsafe IPlatformHandle CreateWin32(IPlatformHandle parent) IPlatformHandle CreateWin32(IPlatformHandle parent)
{ {
_className = "NativeWindow-" + Guid.NewGuid(); _className = "NativeWindow-" + Guid.NewGuid();
_wndProcDelegate = WndProc; _wndProcDelegate = WndProc;
@@ -142,7 +140,7 @@ namespace Ryujinx.Ava.Ui.Controls
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
internal IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam) IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
{ {
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF); var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
var root = VisualRoot as Window; var root = VisualRoot as Window;

View File

@@ -1,10 +1,13 @@
using Avalonia.Platform;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using SPB.Graphics.Vulkan; using SPB.Graphics.Vulkan;
using SPB.Platform.GLX;
using SPB.Platform.Win32; using SPB.Platform.Win32;
using SPB.Platform.X11; using SPB.Platform.X11;
using SPB.Windowing; using SPB.Windowing;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Ava.Ui namespace Ryujinx.Ava.Ui
{ {
@@ -12,6 +15,18 @@ namespace Ryujinx.Ava.Ui
{ {
private NativeWindowBase _window; private NativeWindowBase _window;
[SupportedOSPlatform("linux")]
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
{
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
WindowHandle = X11Window.WindowHandle.RawHandle;
X11Display = X11Window.DisplayHandle.RawHandle;
X11Window.Hide();
return new PlatformHandle(WindowHandle, "X11");
}
public SurfaceKHR CreateSurface(Instance instance) public SurfaceKHR CreateSurface(Instance instance)
{ {
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
@@ -20,7 +35,7 @@ namespace Ryujinx.Ava.Ui
} }
else if (OperatingSystem.IsLinux()) else if (OperatingSystem.IsLinux())
{ {
_window = X11Window; _window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
} }
else else
{ {

View File

@@ -1,8 +1,22 @@
namespace Ryujinx.Ava.Ui.Models using Ryujinx.Ava.Ui.ViewModels;
namespace Ryujinx.Ava.Ui.Models
{ {
public class DownloadableContentModel public class DownloadableContentModel : BaseModel
{ {
public bool Enabled { get; set; } private bool _enabled;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
OnPropertyChanged();
}
}
public string TitleId { get; } public string TitleId { get; }
public string ContainerPath { get; } public string ContainerPath { get; }
public string FullPath { get; } public string FullPath { get; }

View File

@@ -19,6 +19,7 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE; using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules; using Ryujinx.Modules;
using Ryujinx.Ui.App.Common; using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common; using Ryujinx.Ui.Common;
@@ -47,6 +48,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _loadHeading; private string _loadHeading;
private string _cacheLoadStatus; private string _cacheLoadStatus;
private string _searchText; private string _searchText;
private Timer _searchTimer;
private string _dockedStatusText; private string _dockedStatusText;
private string _fifoStatusText; private string _fifoStatusText;
private string _gameStatusText; private string _gameStatusText;
@@ -115,10 +117,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
_searchText = value; _searchText = value;
RefreshView(); _searchTimer?.Dispose();
_searchTimer = new Timer(TimerCallback, null, 1000, 0);
} }
} }
private void TimerCallback(object obj)
{
RefreshView();
_searchTimer.Dispose();
_searchTimer = null;
}
public ReadOnlyObservableCollection<ApplicationData> AppsObservableList public ReadOnlyObservableCollection<ApplicationData> AppsObservableList
{ {
get => _appsObservableList; get => _appsObservableList;
@@ -200,22 +212,19 @@ namespace Ryujinx.Ava.Ui.ViewModels
private string _showUikey = "F4"; private string _showUikey = "F4";
private string _pauseKey = "F5"; private string _pauseKey = "F5";
private string _screenshotkey = "F8"; private string _screenshotkey = "F8";
private float _volume; private float _volume;
private string _backendText; private string _backendText;
public ApplicationData SelectedApplication public ApplicationData SelectedApplication
{ {
get get
{ {
switch (Glyph) return Glyph switch
{ {
case Glyph.List: Glyph.List => _owner.GameList.SelectedApplication,
return _owner.GameList.SelectedApplication; Glyph.Grid => _owner.GameGrid.SelectedApplication,
case Glyph.Grid: _ => null,
return _owner.GameGrid.SelectedApplication; };
default:
return null;
}
} }
} }
@@ -408,6 +417,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
_owner.AppHost.Device.SetVolume(_volume); _owner.AppHost.Device.SetVolume(_volume);
} }
OnPropertyChanged(nameof(VolumeStatusText)); OnPropertyChanged(nameof(VolumeStatusText));
OnPropertyChanged(nameof(VolumeMuted)); OnPropertyChanged(nameof(VolumeMuted));
OnPropertyChanged(); OnPropertyChanged();
@@ -477,38 +487,36 @@ namespace Ryujinx.Ava.Ui.ViewModels
internal void Sort(bool isAscending) internal void Sort(bool isAscending)
{ {
IsAscending = isAscending; IsAscending = isAscending;
RefreshView(); RefreshView();
} }
internal void Sort(ApplicationSort sort) internal void Sort(ApplicationSort sort)
{ {
SortMode = sort; SortMode = sort;
RefreshView(); RefreshView();
} }
private IComparer<ApplicationData> GetComparer() private IComparer<ApplicationData> GetComparer()
{ {
switch (SortMode) return SortMode switch
{ {
case ApplicationSort.LastPlayed: ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
return new Models.Generic.LastPlayedSortComparer(IsAscending); ApplicationSort.FileSize => new Models.Generic.FileSizeSortComparer(IsAscending),
case ApplicationSort.FileSize: ApplicationSort.TotalTimePlayed => new Models.Generic.TimePlayedSortComparer(IsAscending),
return new Models.Generic.FileSizeSortComparer(IsAscending); ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
case ApplicationSort.TotalTimePlayed: : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
return new Models.Generic.TimePlayedSortComparer(IsAscending); ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
case ApplicationSort.Title: : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName) : SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName); ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
case ApplicationSort.Favorite: : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
return !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite) : SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite); ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
case ApplicationSort.Developer: : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer); ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
case ApplicationSort.FileType: : SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension); _ => null,
case ApplicationSort.Path: };
return IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) : SortExpressionComparer<ApplicationData>.Descending(app => app.Path);
default:
return null;
}
} }
private void RefreshView() private void RefreshView()
@@ -611,40 +619,31 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
} }
public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite;
public bool IsSortedByTitle => SortMode == ApplicationSort.Title; public bool IsSortedByTitle => SortMode == ApplicationSort.Title;
public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer;
public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed;
public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed;
public bool IsSortedByType => SortMode == ApplicationSort.FileType; public bool IsSortedByType => SortMode == ApplicationSort.FileType;
public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; public bool IsSortedBySize => SortMode == ApplicationSort.FileSize;
public bool IsSortedByPath => SortMode == ApplicationSort.Path; public bool IsSortedByPath => SortMode == ApplicationSort.Path;
public string SortName public string SortName
{ {
get get
{ {
switch (SortMode) return SortMode switch
{ {
case ApplicationSort.Title: ApplicationSort.Title => LocaleManager.Instance["GameListHeaderApplication"],
return LocaleManager.Instance["GameListHeaderApplication"]; ApplicationSort.Developer => LocaleManager.Instance["GameListHeaderDeveloper"],
case ApplicationSort.Developer: ApplicationSort.LastPlayed => LocaleManager.Instance["GameListHeaderLastPlayed"],
return LocaleManager.Instance["GameListHeaderDeveloper"]; ApplicationSort.TotalTimePlayed => LocaleManager.Instance["GameListHeaderTimePlayed"],
case ApplicationSort.LastPlayed: ApplicationSort.FileType => LocaleManager.Instance["GameListHeaderFileExtension"],
return LocaleManager.Instance["GameListHeaderLastPlayed"]; ApplicationSort.FileSize => LocaleManager.Instance["GameListHeaderFileSize"],
case ApplicationSort.TotalTimePlayed: ApplicationSort.Path => LocaleManager.Instance["GameListHeaderPath"],
return LocaleManager.Instance["GameListHeaderTimePlayed"]; ApplicationSort.Favorite => LocaleManager.Instance["CommonFavorite"],
case ApplicationSort.FileType: _ => string.Empty,
return LocaleManager.Instance["GameListHeaderFileExtension"]; };
case ApplicationSort.FileSize:
return LocaleManager.Instance["GameListHeaderFileSize"];
case ApplicationSort.Path:
return LocaleManager.Instance["GameListHeaderPath"];
case ApplicationSort.Favorite:
return LocaleManager.Instance["CommonFavorite"];
}
return string.Empty;
} }
} }
@@ -668,6 +667,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_showUikey); set get => KeyGesture.Parse(_showUikey); set
{ {
_showUikey = value.ToString(); _showUikey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -677,6 +677,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_screenshotkey); set get => KeyGesture.Parse(_screenshotkey); set
{ {
_screenshotkey = value.ToString(); _screenshotkey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
@@ -686,14 +687,15 @@ namespace Ryujinx.Ava.Ui.ViewModels
get => KeyGesture.Parse(_pauseKey); set get => KeyGesture.Parse(_pauseKey); set
{ {
_pauseKey = value.ToString(); _pauseKey = value.ToString();
OnPropertyChanged(); OnPropertyChanged();
} }
} }
public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1; public bool IsGridSmall => ConfigurationState.Instance.Ui.GridSize == 1;
public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2; public bool IsGridMedium => ConfigurationState.Instance.Ui.GridSize == 2;
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3; public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4; public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
public int GridSizeScale public int GridSizeScale
{ {
@@ -728,14 +730,14 @@ namespace Ryujinx.Ava.Ui.ViewModels
if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId)) if (_owner.AppHost.Device.System.SearchingForAmiibo(out int deviceId))
{ {
string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper(); string titleId = _owner.AppHost.Device.Application.TitleIdText.ToUpper();
AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId); AmiiboWindow window = new(_showAll, _lastScannedAmiiboId, titleId);
await window.ShowDialog(_owner); await window.ShowDialog(_owner);
if (window.IsScanned) if (window.IsScanned)
{ {
_showAll = window.ViewModel.ShowAllAmiibo; _showAll = window.ViewModel.ShowAllAmiibo;
_lastScannedAmiiboId = window.ScannedAmiibo.GetId(); _lastScannedAmiiboId = window.ScannedAmiibo.GetId();
_owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid); _owner.AppHost.Device.System.ScanAmiibo(deviceId, _lastScannedAmiiboId, window.ViewModel.UseRandomUuid);
@@ -766,8 +768,9 @@ namespace Ryujinx.Ava.Ui.ViewModels
private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e) private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
{ {
StatusBarProgressValue = e.NumAppsLoaded; StatusBarProgressValue = e.NumAppsLoaded;
StatusBarProgressMaximum = e.NumAppsFound; StatusBarProgressMaximum = e.NumAppsFound;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum); LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", StatusBarProgressValue, StatusBarProgressMaximum);
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
@@ -792,9 +795,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
await Dispatcher.UIThread.InvokeAsync(() => await Dispatcher.UIThread.InvokeAsync(() =>
{ {
Applications.Clear(); Applications.Clear();
_owner.LoadProgressBar.IsVisible = true; _owner.LoadProgressBar.IsVisible = true;
StatusBarProgressMaximum = 0; StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0; StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0); LocaleManager.Instance.UpdateDynamicValue("StatusBarGamesLoaded", 0, 0);
}); });
@@ -842,12 +847,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
} }
}); });
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } }); dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } }); dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } }); dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } }); dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } }); dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
string[] files = await dialog.ShowAsync(_owner); string[] files = await dialog.ShowAsync(_owner);
@@ -878,10 +883,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None); ShowUiKey = new KeyGesture(showUiKey, KeyModifiers.None);
} }
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{ {
ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None); ScreenshotKey = new KeyGesture(screenshotKey, KeyModifiers.None);
} }
if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) if (AvaloniaMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{ {
PauseKey = new KeyGesture(pauseKey, KeyModifiers.None); PauseKey = new KeyGesture(pauseKey, KeyModifiers.None);
@@ -941,9 +948,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_lastFullscreenToggle = Environment.TickCount64; _lastFullscreenToggle = Environment.TickCount64;
WindowState state = _owner.WindowState; if (_owner.WindowState == WindowState.FullScreen)
if (state == WindowState.FullScreen)
{ {
_owner.WindowState = WindowState.Normal; _owner.WindowState = WindowState.Normal;
@@ -971,8 +976,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
if (IsGameRunning) if (IsGameRunning)
{ {
ConfigurationState.Instance.System.EnableDockedMode.Value = ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value;
!ConfigurationState.Instance.System.EnableDockedMode.Value;
} }
} }
@@ -985,6 +989,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
else if (IsGameRunning) else if (IsGameRunning)
{ {
await Task.Delay(100); await Task.Delay(100);
_owner.AppHost?.ShowExitPrompt(); _owner.AppHost?.ShowExitPrompt();
} }
} }
@@ -994,6 +999,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager); _owner.SettingsWindow = new(_owner.VirtualFileSystem, _owner.ContentManager);
await _owner.SettingsWindow.ShowDialog(_owner); await _owner.SettingsWindow.ShowDialog(_owner);
LoadConfigurableHotKeys(); LoadConfigurableHotKeys();
} }
@@ -1004,9 +1010,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenAboutWindow() public async void OpenAboutWindow()
{ {
AboutWindow window = new(); await new AboutWindow().ShowDialog(_owner);
await window.ShowDialog(_owner);
} }
public void ChangeLanguage(object obj) public void ChangeLanguage(object obj)
@@ -1020,7 +1024,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
try try
{ {
ProgressMaximum = total; ProgressMaximum = total;
ProgressValue = current; ProgressValue = current;
switch (state) switch (state)
{ {
@@ -1030,13 +1034,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
case PtcLoadingState.Start: case PtcLoadingState.Start:
case PtcLoadingState.Loading: case PtcLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingPPTC"]; LoadHeading = LocaleManager.Instance["CompilingPPTC"];
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case PtcLoadingState.Loaded: case PtcLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = "";
break; break;
} }
break; break;
@@ -1046,13 +1050,13 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
case ShaderCacheLoadingState.Start: case ShaderCacheLoadingState.Start:
case ShaderCacheLoadingState.Loading: case ShaderCacheLoadingState.Loading:
LoadHeading = LocaleManager.Instance["CompilingShaders"]; LoadHeading = LocaleManager.Instance["CompilingShaders"];
IsLoadingIndeterminate = false; IsLoadingIndeterminate = false;
break; break;
case ShaderCacheLoadingState.Loaded: case ShaderCacheLoadingState.Loaded:
LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName); LoadHeading = string.Format(LocaleManager.Instance["LoadingHeading"], TitleName);
IsLoadingIndeterminate = true; IsLoadingIndeterminate = true;
CacheLoadStatus = ""; CacheLoadStatus = "";
break; break;
} }
break; break;
@@ -1065,14 +1069,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenUserSaveDirectory() public void OpenUserSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1082,8 +1084,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
return; return;
} }
var userId = new LibHac.Fs.UserId((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low); UserId userId = new((ulong)_owner.AccountManager.LastOpenedUser.UserId.High, (ulong)_owner.AccountManager.LastOpenedUser.UserId.Low);
var saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default); SaveDataFilter saveDataFilter = SaveDataFilter.Make(titleIdNumber, saveType: default, userId, saveDataId: default, index: default);
OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber); OpenSaveDirectory(in saveDataFilter, selection, titleIdNumber);
}); });
} }
@@ -1091,8 +1093,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void ToggleFavorite() public void ToggleFavorite()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
selection.Favorite = !selection.Favorite; selection.Favorite = !selection.Favorite;
@@ -1108,11 +1109,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenModsDirectory() public void OpenModsDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath(); string modsBasePath = _owner.VirtualFileSystem.ModLoader.GetModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId); string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(modsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
@@ -1121,12 +1121,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenSdModsDirectory() public void OpenSdModsDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath(); string sdModsBasePath = _owner.VirtualFileSystem.ModLoader.GetSdModsBasePath();
string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId); string titleModsPath = _owner.VirtualFileSystem.ModLoader.GetTitleDir(sdModsBasePath, selection.TitleId);
OpenHelper.OpenFolder(titleModsPath); OpenHelper.OpenFolder(titleModsPath);
} }
@@ -1134,13 +1134,11 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenPtcDirectory() public void OpenPtcDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu"); string ptcDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu");
string mainPath = Path.Combine(ptcDir, "0");
string mainPath = Path.Combine(ptcDir, "0");
string backupPath = Path.Combine(ptcDir, "1"); string backupPath = Path.Combine(ptcDir, "1");
if (!Directory.Exists(ptcDir)) if (!Directory.Exists(ptcDir))
@@ -1156,16 +1154,18 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgePtcCache() public async void PurgePtcCache()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0")); DirectoryInfo mainDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "0"));
DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1")); DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogPPTCDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<FileInfo> cacheFiles = new(); List<FileInfo> cacheFiles = new();
@@ -1198,8 +1198,7 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenShaderCacheDirectory() public void OpenShaderCacheDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"); string shaderCacheDir = Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader");
@@ -1220,18 +1219,20 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void PurgeShaderCache() public async void PurgeShaderCache()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader")); DirectoryInfo shaderCacheDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "shader"));
// FIXME: Found a way to reproduce the bold effect on the title name (fork?). // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"], UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance["DialogWarning"],
string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName), LocaleManager.Instance["InputDialogYes"], LocaleManager.Instance["InputDialogNo"], LocaleManager.Instance["RyujinxConfirm"]); string.Format(LocaleManager.Instance["DialogShaderDeletionMessage"], selection.TitleName),
LocaleManager.Instance["InputDialogYes"],
LocaleManager.Instance["InputDialogNo"],
LocaleManager.Instance["RyujinxConfirm"]);
List<DirectoryInfo> oldCacheDirectories = new List<DirectoryInfo>(); List<DirectoryInfo> oldCacheDirectories = new();
List<FileInfo> newCacheFiles = new List<FileInfo>(); List<FileInfo> newCacheFiles = new();
if (shaderCacheDir.Exists) if (shaderCacheDir.Exists)
{ {
@@ -1279,38 +1280,28 @@ namespace Ryujinx.Ava.Ui.ViewModels
public async void OpenTitleUpdateManager() public async void OpenTitleUpdateManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
TitleUpdateWindow titleUpdateManager = await new TitleUpdateWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName);
await titleUpdateManager.ShowDialog(_owner);
} }
} }
public async void OpenDownloadableContentManager() public async void OpenDownloadableContentManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
DownloadableContentManagerWindow downloadableContentManager = new(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName); await new DownloadableContentManagerWindow(_owner.VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(_owner);
await downloadableContentManager.ShowDialog(_owner);
} }
} }
public async void OpenCheatManager() public async void OpenCheatManager()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
CheatWindow cheatManager = new(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName); await new CheatWindow(_owner.VirtualFileSystem, selection.TitleId, selection.TitleName).ShowDialog(_owner);
await cheatManager.ShowDialog(_owner);
} }
} }
@@ -1321,13 +1312,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
return; return;
} }
var application = _owner.AppHost.Device.Application; ApplicationLoader application = _owner.AppHost.Device.Application;
if (application != null) if (application != null)
{ {
CheatWindow cheatManager = new(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName); await new CheatWindow(_owner.VirtualFileSystem, application.TitleIdText, application.TitleName).ShowDialog(_owner);
await cheatManager.ShowDialog(_owner);
_owner.AppHost.Device.EnableCheats(); _owner.AppHost.Device.EnableCheats();
} }
@@ -1335,14 +1323,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenDeviceSaveDirectory() public void OpenDeviceSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1360,14 +1346,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
public void OpenBcatSaveDirectory() public void OpenBcatSaveDirectory()
{ {
var selection = SelectedApplication; ApplicationData selection = SelectedApplication;
if (selection != null) if (selection != null)
{ {
Task.Run(() => Task.Run(() =>
{ {
if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, if (!ulong.TryParse(selection.TitleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
out ulong titleIdNumber))
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
@@ -1420,12 +1404,10 @@ namespace Ryujinx.Ava.Ui.ViewModels
_owner.Close(); _owner.Close();
} }
private async Task HandleFirmwareInstallation(string path) private async Task HandleFirmwareInstallation(string filename)
{ {
try try
{ {
string filename = path;
SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename); SystemVersion firmwareVersion = _owner.ContentManager.VerifyFirmwarePackage(filename);
if (firmwareVersion == null) if (firmwareVersion == null)
@@ -1437,7 +1419,6 @@ namespace Ryujinx.Ava.Ui.ViewModels
string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString); string dialogTitle = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallTitle"], firmwareVersion.VersionString);
SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion(); SystemVersion currentVersion = _owner.ContentManager.GetCurrentFirmwareVersion();
string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString); string dialogMessage = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallMessage"], firmwareVersion.VersionString);
@@ -1480,11 +1461,12 @@ namespace Ryujinx.Ava.Ui.ViewModels
string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString); string message = string.Format(LocaleManager.Instance["DialogFirmwareInstallerFirmwareInstallSuccessMessage"], firmwareVersion.VersionString);
await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]); await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance["InputDialogOk"], "", LocaleManager.Instance["RyujinxInfo"]);
Logger.Info?.Print(LogClass.Application, message); Logger.Info?.Print(LogClass.Application, message);
// Purge Applet Cache. // Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists) if (miiEditorCacheFolder.Exists)
{ {
@@ -1514,8 +1496,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
catch (LibHac.Common.Keys.MissingKeyException ex) catch (LibHac.Common.Keys.MissingKeyException ex)
{ {
Logger.Error?.Print(LogClass.Application, ex.ToString()); Logger.Error?.Print(LogClass.Application, ex.ToString());
Dispatcher.UIThread.Post(async () => await
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner)); Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, _owner));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -1527,8 +1509,8 @@ namespace Ryujinx.Ava.Ui.ViewModels
{ {
OpenFileDialog dialog = new() { AllowMultiple = false }; OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } }); dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance["FileDialogAllTypes"], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } }); dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } }); dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(_owner); string[] file = await dialog.ShowAsync(_owner);

View File

@@ -3,89 +3,128 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
Width="800"
Height="500"
MinWidth="800"
MinHeight="500"
MaxWidth="800"
MaxHeight="500"
SizeToContent="Height" SizeToContent="Height"
Width="600" MinHeight="500" Height="500"
WindowStartupLocation="CenterOwner" WindowStartupLocation="CenterOwner"
MinWidth="600"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Name="DownloadableContentGrid" Margin="15"> <Grid Name="DownloadableContentGrid" Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Name="Heading"
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap" TextAlignment="Center"
Text="{Binding Heading}" TextWrapping="Wrap" />
TextAlignment="Center" /> <DockPanel
<Border
Grid.Row="2" Grid.Row="2"
Margin="0"
HorizontalAlignment="Left">
<Button
Name="EnableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding EnableAll}">
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
</Button>
<Button
Name="DisableAllButton"
MinWidth="90"
Margin="5"
Command="{Binding DisableAll}">
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
</Button>
</DockPanel>
<Border
Grid.Row="3"
Margin="5" Margin="5"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<DataGrid <ScrollViewer
MinHeight="200"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
Items="{Binding DownloadableContents}"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
<DataGrid.Columns> <DataGrid
<DataGridTemplateColumn Width="90"> Name="DlcDataGrid"
<DataGridTemplateColumn.CellTemplate> MinHeight="200"
<DataTemplate> HorizontalAlignment="Stretch"
<CheckBox VerticalAlignment="Stretch"
Width="50" CanUserReorderColumns="False"
MinWidth="40" CanUserResizeColumns="True"
HorizontalAlignment="Right" CanUserSortColumns="True"
IsChecked="{Binding Enabled}" /> HorizontalScrollBarVisibility="Auto"
</DataTemplate> Items="{Binding _downloadableContents}"
</DataGridTemplateColumn.CellTemplate> SelectionMode="Extended"
<DataGridTemplateColumn.Header> VerticalScrollBarVisibility="Auto">
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" /> <DataGrid.Styles>
</DataGridTemplateColumn.Header> <Styles>
</DataGridTemplateColumn> <Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
<DataGridTextColumn <Setter Property="HorizontalAlignment" Value="Left" />
Width="190" <Setter Property="HorizontalContentAlignment" Value="Left" />
Binding="{Binding TitleId}" </Style>
CanUserResize="True"> </Styles>
<DataGridTextColumn.Header> <Styles>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" /> <Style Selector="DataGridCell:nth-child(1)">
</DataGridTextColumn.Header> <Setter Property="HorizontalAlignment" Value="Right" />
</DataGridTextColumn> <Setter Property="HorizontalContentAlignment" Value="Right" />
<DataGridTextColumn </Style>
Width="*" </Styles>
Binding="{Binding ContainerPath}" </DataGrid.Styles>
CanUserResize="True"> <DataGrid.Columns>
<DataGridTextColumn.Header> <DataGridTemplateColumn Width="90">
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" /> <DataGridTemplateColumn.CellTemplate>
</DataGridTextColumn.Header> <DataTemplate>
</DataGridTextColumn> <CheckBox
<DataGridTextColumn Width="50"
Width="*" MinWidth="40"
Binding="{Binding FullPath}" HorizontalAlignment="Center"
CanUserResize="True"> IsChecked="{Binding Enabled}" />
<DataGridTextColumn.Header> </DataTemplate>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" /> </DataGridTemplateColumn.CellTemplate>
</DataGridTextColumn.Header> <DataGridTemplateColumn.Header>
</DataGridTextColumn> <TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
</DataGrid.Columns> </DataGridTemplateColumn.Header>
</DataGrid> </DataGridTemplateColumn>
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding ContainerPath}">
<DataGridTextColumn.Header>
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Border> </Border>
<DockPanel <DockPanel
Grid.Row="3" Grid.Row="4"
Margin="0" Margin="0"
HorizontalAlignment="Stretch"> HorizontalAlignment="Stretch">
<DockPanel Margin="0" HorizontalAlignment="Left"> <DockPanel Margin="0" HorizontalAlignment="Left">

View File

@@ -18,6 +18,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reactive.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Path = System.IO.Path; using Path = System.IO.Path;
@@ -27,14 +28,13 @@ namespace Ryujinx.Ava.Ui.Windows
public partial class DownloadableContentManagerWindow : StyleableWindow public partial class DownloadableContentManagerWindow : StyleableWindow
{ {
private readonly List<DownloadableContentContainer> _downloadableContentContainerList; private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath; private readonly string _downloadableContentJsonPath;
public VirtualFileSystem VirtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
public AvaloniaList<DownloadableContentModel> DownloadableContents { get; set; } = new AvaloniaList<DownloadableContentModel>(); private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
public ulong TitleId { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["DlcWindowHeading"], TitleName, TitleId.ToString("X16")); private ulong _titleId { get; }
private string _titleName { get; }
public DownloadableContentManagerWindow() public DownloadableContentManagerWindow()
{ {
@@ -42,14 +42,16 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
} }
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
VirtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
TitleId = titleId; _downloadableContents = new AvaloniaList<DownloadableContentModel>();
TitleName = titleName;
_titleId = titleId;
_titleName = titleName;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json"); _downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
@@ -66,9 +68,24 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["DlcWindowTitle"]; RemoveButton.IsEnabled = false;
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["DlcWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadDownloadableContents(); LoadDownloadableContents();
PrintHeading();
}
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["DlcWindowHeading"], _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
} }
private void LoadDownloadableContents() private void LoadDownloadableContents()
@@ -79,23 +96,23 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath); using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
VirtualFileSystem.ImportTickets(pfs); _virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList) foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
{ {
using var ncaFile = new UniqueRef<IFile>(); using UniqueRef<IFile> ncaFile = new();
pfs.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure(); partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath); Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null) if (nca != null)
{ {
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), _downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath, downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath, downloadableContentNca.FullPath,
downloadableContentNca.Enabled)); downloadableContentNca.Enabled));
} }
} }
} }
@@ -105,11 +122,11 @@ namespace Ryujinx.Ava.Ui.Windows
Save(); Save();
} }
private Nca TryCreateNca(IStorage ncaStorage, string containerPath) private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
{ {
try try
{ {
return new Nca(VirtualFileSystem.KeySet, ncaStorage); return new Nca(_virtualFileSystem.KeySet, ncaStorage);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -124,61 +141,73 @@ namespace Ryujinx.Ava.Ui.Windows
private async Task AddDownloadableContent(string path) private async Task AddDownloadableContent(string path)
{ {
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null) if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
{ {
return; return;
} }
using (FileStream containerFile = File.OpenRead(path)) using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
{ {
PartitionFileSystem pfs = new PartitionFileSystem(containerFile.AsStorage()); using var ncaFile = new UniqueRef<IFile>();
bool containsDownloadableContent = false;
VirtualFileSystem.ImportTickets(pfs); partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca")) Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{ {
using var ncaFile = new UniqueRef<IFile>(); continue;
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
Nca nca = TryCreateNca(ncaFile.Get.AsStorage(), path);
if (nca == null)
{
continue;
}
if (nca.Header.ContentType == NcaContentType.PublicData)
{
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != TitleId)
{
break;
}
DownloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
}
} }
if (!containsDownloadableContent) if (nca.Header.ContentType == NcaContentType.PublicData)
{ {
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]); if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
{
break;
}
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
containsDownloadableContent = true;
} }
} }
if (!containsDownloadableContent)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogDlcNoDlcErrorMessage"]);
}
} }
private void RemoveDownloadableContents(bool removeSelectedOnly = false) private void RemoveDownloadableContents(bool removeSelectedOnly = false)
{ {
if (removeSelectedOnly) if (removeSelectedOnly)
{ {
DownloadableContents.RemoveAll(DownloadableContents.Where(x => x.Enabled).ToList()); AvaloniaList<DownloadableContentModel> removedItems = new();
foreach (var item in DlcDataGrid.SelectedItems)
{
removedItems.Add(item as DownloadableContentModel);
}
DlcDataGrid.SelectedItems.Clear();
foreach (var item in removedItems)
{
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
}
} }
else else
{ {
DownloadableContents.Clear(); _downloadableContents.Clear();
} }
PrintHeading();
} }
public void RemoveSelected() public void RemoveSelected()
@@ -191,6 +220,22 @@ namespace Ryujinx.Ava.Ui.Windows
RemoveDownloadableContents(); RemoveDownloadableContents();
} }
public void EnableAll()
{
foreach(var item in _downloadableContents)
{
item.Enabled = true;
}
}
public void DisableAll()
{
foreach (var item in _downloadableContents)
{
item.Enabled = false;
}
}
public async void Add() public async void Add()
{ {
OpenFileDialog dialog = new OpenFileDialog() OpenFileDialog dialog = new OpenFileDialog()
@@ -214,6 +259,8 @@ namespace Ryujinx.Ava.Ui.Windows
await AddDownloadableContent(file); await AddDownloadableContent(file);
} }
} }
PrintHeading();
} }
public void Save() public void Save()
@@ -222,7 +269,7 @@ namespace Ryujinx.Ava.Ui.Windows
DownloadableContentContainer container = default; DownloadableContentContainer container = default;
foreach (DownloadableContentModel downloadableContent in DownloadableContents) foreach (DownloadableContentModel downloadableContent in _downloadableContents)
{ {
if (container.ContainerPath != downloadableContent.ContainerPath) if (container.ContainerPath != downloadableContent.ContainerPath)
{ {

View File

@@ -90,8 +90,8 @@ namespace Ryujinx.Ava.Ui.Windows
Title = $"Ryujinx {Program.Version}"; Title = $"Ryujinx {Program.Version}";
Height = Height / Program.WindowScaleFactor; Height /= Program.WindowScaleFactor;
Width = Width / Program.WindowScaleFactor; Width /= Program.WindowScaleFactor;
if (Program.PreviewerDetached) if (Program.PreviewerDetached)
{ {
@@ -523,23 +523,20 @@ namespace Ryujinx.Ava.Ui.Windows
public static void UpdateGraphicsConfig() public static void UpdateGraphicsConfig()
{ {
int resScale = ConfigurationState.Instance.Graphics.ResScale; GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1 ? ConfigurationState.Instance.Graphics.ResScaleCustom : ConfigurationState.Instance.Graphics.ResScale;
float resScaleCustom = ConfigurationState.Instance.Graphics.ResScaleCustom; GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.ResScale = resScale == -1 ? resScaleCustom : resScale; GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression; GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE; GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
} }
public void LoadHotKeys() public void LoadHotKeys()
{ {
HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt)); HotKeyManager.SetHotKey(FullscreenHotKey, new KeyGesture(Key.Enter, KeyModifiers.Alt));
HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11)); HotKeyManager.SetHotKey(FullscreenHotKey2, new KeyGesture(Key.F11));
HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9)); HotKeyManager.SetHotKey(DockToggleHotKey, new KeyGesture(Key.F9));
HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape)); HotKeyManager.SetHotKey(ExitHotKey, new KeyGesture(Key.Escape));
} }
public static void SaveConfig() public static void SaveConfig()

View File

@@ -75,7 +75,7 @@
Spacing="10"> Spacing="10">
<ListBox <ListBox
Name="GameList" Name="GameList"
MinHeight="150" MinHeight="250"
Items="{Binding GameDirectories}" /> Items="{Binding GameDirectories}" />
<Grid HorizontalAlignment="Stretch"> <Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>

View File

@@ -16,7 +16,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone; using TimeZone = Ryujinx.Ava.Ui.Models.TimeZone;
namespace Ryujinx.Ava.Ui.Windows namespace Ryujinx.Ava.Ui.Windows
@@ -31,7 +30,7 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}"; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["Settings"]}";
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this); ViewModel = new SettingsViewModel(virtualFileSystem, contentManager, this);
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
@@ -48,7 +47,7 @@ namespace Ryujinx.Ava.Ui.Windows
public SettingsWindow() public SettingsWindow()
{ {
ViewModel = new SettingsViewModel(); ViewModel = new SettingsViewModel();
DataContext = ViewModel; DataContext = ViewModel;
InitializeComponent(); InitializeComponent();
@@ -79,7 +78,7 @@ namespace Ryujinx.Ava.Ui.Windows
PointerPressed += MouseClick; PointerPressed += MouseClick;
IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]); IKeyboard keyboard = (IKeyboard)ViewModel.AvaloniaKeyboardDriver.GetGamepad(ViewModel.AvaloniaKeyboardDriver.GamepadsIds[0]);
IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard); IButtonAssigner assigner = new KeyboardKeyAssigner(keyboard);
_currentAssigner.GetInputAndAssign(assigner); _currentAssigner.GetInputAndAssign(assigner);
@@ -92,6 +91,7 @@ namespace Ryujinx.Ava.Ui.Windows
_currentAssigner.Cancel(); _currentAssigner.Cancel();
_currentAssigner = null; _currentAssigner = null;
button.IsChecked = false; button.IsChecked = false;
} }
} }
@@ -122,36 +122,19 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
if (e.SelectedItem is NavigationViewItem navitem) if (e.SelectedItem is NavigationViewItem navitem)
{ {
switch (navitem.Tag.ToString()) NavPanel.Content = navitem.Tag.ToString() switch
{ {
case "UiPage": "UiPage" => UiPage,
NavPanel.Content = UiPage; "InputPage" => InputPage,
break; "HotkeysPage" => HotkeysPage,
case "InputPage": "SystemPage" => SystemPage,
NavPanel.Content = InputPage; "CpuPage" => CpuPage,
break; "GraphicsPage" => GraphicsPage,
case "HotkeysPage": "AudioPage" => AudioPage,
NavPanel.Content = HotkeysPage; "NetworkPage" => NetworkPage,
break; "LoggingPage" => LoggingPage,
case "SystemPage": _ => throw new NotImplementedException()
NavPanel.Content = SystemPage; };
break;
case "CpuPage":
NavPanel.Content = CpuPage;
break;
case "GraphicsPage":
NavPanel.Content = GraphicsPage;
break;
case "AudioPage":
NavPanel.Content = AudioPage;
break;
case "NetworkPage":
NavPanel.Content = NetworkPage;
break;
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
}
} }
} }
@@ -178,13 +161,18 @@ namespace Ryujinx.Ava.Ui.Windows
private void RemoveButton_OnClick(object sender, RoutedEventArgs e) private void RemoveButton_OnClick(object sender, RoutedEventArgs e)
{ {
List<string> selected = new(GameList.SelectedItems.Cast<string>()); int oldIndex = GameList.SelectedIndex;
foreach (string path in selected) foreach (string path in new List<string>(GameList.SelectedItems.Cast<string>()))
{ {
ViewModel.GameDirectories.Remove(path); ViewModel.GameDirectories.Remove(path);
ViewModel.DirectoryChanged = true; ViewModel.DirectoryChanged = true;
} }
if (GameList.ItemCount > 0)
{
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
}
} }
private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private void TimeZoneBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -214,7 +202,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveButton_Clicked(object sender, RoutedEventArgs e) private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{ {
SaveSettings(); SaveSettings();
Close(); Close();
} }
@@ -232,7 +219,6 @@ namespace Ryujinx.Ava.Ui.Windows
private void SaveSettings() private void SaveSettings()
{ {
ViewModel.SaveSettings(); ViewModel.SaveSettings();
ControllerSettings?.SaveCurrentProfile(); ControllerSettings?.SaveCurrentProfile();
if (Owner is MainWindow window && ViewModel.DirectoryChanged) if (Owner is MainWindow window && ViewModel.DirectoryChanged)
@@ -246,8 +232,10 @@ namespace Ryujinx.Ava.Ui.Windows
protected override void OnClosed(EventArgs e) protected override void OnClosed(EventArgs e)
{ {
ControllerSettings.Dispose(); ControllerSettings.Dispose();
_currentAssigner?.Cancel(); _currentAssigner?.Cancel();
_currentAssigner = null; _currentAssigner = null;
base.OnClosed(e); base.OnClosed(e);
} }
} }

View File

@@ -3,13 +3,17 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows" xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
SizeToContent="Height" Width="600"
Width="600" MinHeight="500" Height="500" Height="400"
WindowStartupLocation="CenterOwner"
MinWidth="600" MinWidth="600"
MinHeight="400"
MaxWidth="600"
MaxHeight="400"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Margin="15"> <Grid Margin="15">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -19,15 +23,15 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock <TextBlock
Name="Heading"
Grid.Row="1" Grid.Row="1"
MaxWidth="500"
Margin="20,15,20,20" Margin="20,15,20,20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
MaxWidth="500"
LineHeight="18" LineHeight="18"
TextWrapping="Wrap" TextAlignment="Center"
Text="{Binding Heading}" TextWrapping="Wrap" />
TextAlignment="Center" />
<Border <Border
Grid.Row="2" Grid.Row="2"
Margin="5" Margin="5"
@@ -36,8 +40,6 @@
BorderBrush="Gray" BorderBrush="Gray"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer <ScrollViewer
Width="550"
MinHeight="200"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"> VerticalScrollBarVisibility="Auto">
@@ -45,11 +47,19 @@
Margin="10" Margin="10"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Items="{Binding TitleUpdates}"> Items="{Binding _titleUpdates}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<RadioButton Padding="8, 0" VerticalContentAlignment="Center" GroupName="Update" IsChecked="{Binding IsEnabled, Mode=TwoWay}"> <RadioButton
<Label Margin="0" VerticalAlignment="Center" Content="{Binding Label}" /> Padding="8,0"
VerticalContentAlignment="Center"
GroupName="Update"
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
<Label
Margin="0"
VerticalAlignment="Center"
Content="{Binding Label}"
FontSize="12" />
</RadioButton> </RadioButton>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@@ -30,13 +30,11 @@ namespace Ryujinx.Ava.Ui.Windows
private readonly string _titleUpdateJsonPath; private readonly string _titleUpdateJsonPath;
private TitleUpdateMetadata _titleUpdateWindowData; private TitleUpdateMetadata _titleUpdateWindowData;
public VirtualFileSystem VirtualFileSystem { get; } private VirtualFileSystem _virtualFileSystem { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
internal AvaloniaList<TitleUpdateModel> TitleUpdates { get; set; } = new AvaloniaList<TitleUpdateModel>(); private ulong _titleId { get; }
public string TitleId { get; } private string _titleName { get; }
public string TitleName { get; }
public string Heading => string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], TitleName, TitleId.ToUpper());
public TitleUpdateWindow() public TitleUpdateWindow()
{ {
@@ -44,16 +42,18 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
} }
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName) public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
{ {
VirtualFileSystem = virtualFileSystem; _virtualFileSystem = virtualFileSystem;
TitleId = titleId; _titleUpdates = new AvaloniaList<TitleUpdateModel>();
TitleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId, "updates.json"); _titleId = titleId;
_titleName = titleName;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try try
{ {
@@ -64,7 +64,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData = new TitleUpdateMetadata _titleUpdateWindowData = new TitleUpdateMetadata
{ {
Selected = "", Selected = "",
Paths = new List<string>() Paths = new List<string>()
}; };
} }
@@ -72,14 +72,20 @@ namespace Ryujinx.Ava.Ui.Windows
InitializeComponent(); InitializeComponent();
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance["UpdateWindowTitle"]; Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance["UpdateWindowTitle"]} - {_titleName} ({_titleId:X16})";
LoadUpdates(); LoadUpdates();
PrintHeading();
}
private void PrintHeading()
{
Heading.Text = string.Format(LocaleManager.Instance["GameUpdateWindowHeading"], _titleUpdates.Count, _titleName, _titleId.ToString("X16"));
} }
private void LoadUpdates() private void LoadUpdates()
{ {
TitleUpdates.Add(new TitleUpdateModel(default, string.Empty, true)); _titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
foreach (string path in _titleUpdateWindowData.Paths) foreach (string path in _titleUpdateWindowData.Paths)
{ {
@@ -88,12 +94,12 @@ namespace Ryujinx.Ava.Ui.Windows
if (_titleUpdateWindowData.Selected == "") if (_titleUpdateWindowData.Selected == "")
{ {
TitleUpdates[0].IsEnabled = true; _titleUpdates[0].IsEnabled = true;
} }
else else
{ {
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected); TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
List<TitleUpdateModel> enabled = TitleUpdates.Where(x => x.IsEnabled).ToList(); List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
foreach (TitleUpdateModel update in enabled) foreach (TitleUpdateModel update in enabled)
{ {
@@ -111,43 +117,47 @@ namespace Ryujinx.Ava.Ui.Windows
private void AddUpdate(string path) private void AddUpdate(string path)
{ {
if (File.Exists(path) && !TitleUpdates.Any(x => x.Path == path)) if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
{ {
using (FileStream file = new(path, FileMode.Open, FileAccess.Read)) using FileStream file = new(path, FileMode.Open, FileAccess.Read);
try
{ {
PartitionFileSystem nsp = new PartitionFileSystem(file.AsStorage()); (Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
try if (controlNca != null && patchNca != null)
{ {
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(VirtualFileSystem, nsp, TitleId, 0); ApplicationControlProperty controlData = new();
if (controlNca != null && patchNca != null) using UniqueRef<IFile> nacpFile = new();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in _titleUpdates)
{ {
ApplicationControlProperty controlData = new ApplicationControlProperty(); update.IsEnabled = false;
using var nacpFile = new UniqueRef<IFile>();
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
}
else
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
});
} }
_titleUpdates.Last().IsEnabled = true;
} }
catch (Exception ex) else
{ {
Dispatcher.UIThread.Post(async () => Dispatcher.UIThread.Post(async () =>
{ {
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path)); await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance["DialogUpdateAddUpdateErrorMessage"]);
}); });
} }
} }
catch (Exception ex)
{
Dispatcher.UIThread.Post(async () =>
{
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance["DialogDlcLoadNcaErrorMessage"], ex.Message, path));
});
}
} }
} }
@@ -155,16 +165,17 @@ namespace Ryujinx.Ava.Ui.Windows
{ {
if (removeSelectedOnly) if (removeSelectedOnly)
{ {
TitleUpdates.RemoveAll(TitleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList()); _titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
} }
else else
{ {
TitleUpdates.RemoveAll(TitleUpdates.Where(x => !x.IsNoUpdate).ToList()); _titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
} }
TitleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true; _titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
SortUpdates(); SortUpdates();
PrintHeading();
} }
public void RemoveSelected() public void RemoveSelected()
@@ -179,7 +190,7 @@ namespace Ryujinx.Ava.Ui.Windows
public async void Add() public async void Add()
{ {
OpenFileDialog dialog = new OpenFileDialog() OpenFileDialog dialog = new()
{ {
Title = LocaleManager.Instance["SelectUpdateDialogTitle"], Title = LocaleManager.Instance["SelectUpdateDialogTitle"],
AllowMultiple = true AllowMultiple = true
@@ -202,11 +213,12 @@ namespace Ryujinx.Ava.Ui.Windows
} }
SortUpdates(); SortUpdates();
PrintHeading();
} }
private void SortUpdates() private void SortUpdates()
{ {
var list = TitleUpdates.ToList(); var list = _titleUpdates.ToList();
list.Sort((first, second) => list.Sort((first, second) =>
{ {
@@ -222,8 +234,8 @@ namespace Ryujinx.Ava.Ui.Windows
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1; return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
}); });
TitleUpdates.Clear(); _titleUpdates.Clear();
TitleUpdates.AddRange(list); _titleUpdates.AddRange(list);
} }
public void Save() public void Save()
@@ -232,7 +244,7 @@ namespace Ryujinx.Ava.Ui.Windows
_titleUpdateWindowData.Selected = ""; _titleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates) foreach (TitleUpdateModel update in _titleUpdates)
{ {
_titleUpdateWindowData.Paths.Add(update.Path); _titleUpdateWindowData.Paths.Add(update.Path);

View File

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

View File

@@ -128,6 +128,11 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) : GetStorageOffset(block, Utils.FindLastOperation(addrLow, block), baseAddressCbOffset) :
(null, 0); (null, 0);
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (byteOffset == null) if (byteOffset == null)
{ {
Operand baseAddrLow = Cbuf(0, baseAddressCbOffset); Operand baseAddrLow = Cbuf(0, baseAddressCbOffset);
@@ -156,11 +161,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
byteOffset = offset; byteOffset = offset;
} }
if (byteOffset != null)
{
ReplaceAddressAlignment(node.List, addrLow, byteOffset, constantOffset);
}
if (isStg16Or8) if (isStg16Or8)
{ {
return byteOffset; return byteOffset;

View File

@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services.Ptm.Psm
{ {
if (_stateChangeEventHandle == -1) if (_stateChangeEventHandle == -1)
{ {
KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out int stateChangeEventHandle); KernelResult resultCode = context.Process.HandleTable.GenerateHandle(_stateChangeEvent.ReadableEvent, out _stateChangeEventHandle);
if (resultCode != KernelResult.Success) if (resultCode != KernelResult.Success)
{ {

View File

@@ -315,6 +315,11 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
} }
} }
if (updateCount > 0)
{
break;
}
// If we are here, that mean nothing was availaible, sleep for 50ms // If we are here, that mean nothing was availaible, sleep for 50ms
context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000); context.Device.System.KernelContext.Syscall.SleepThread(50 * 1000000);
} }
@@ -972,11 +977,12 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
} }
[CommandHipc(31)] // 7.0.0+ [CommandHipc(31)] // 7.0.0+
// EventFd(u64 initval, nn::socket::EventFdFlags flags) -> (i32 ret, u32 bsd_errno) // EventFd(nn::socket::EventFdFlags flags, u64 initval) -> (i32 ret, u32 bsd_errno)
public ResultCode EventFd(ServiceCtx context) public ResultCode EventFd(ServiceCtx context)
{ {
ulong initialValue = context.RequestData.ReadUInt64();
EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32(); EventFdFlags flags = (EventFdFlags)context.RequestData.ReadUInt32();
context.RequestData.BaseStream.Position += 4; // Padding
ulong initialValue = context.RequestData.ReadUInt64();
EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags); EventFileDescriptor newEventFile = new EventFileDescriptor(initialValue, flags);

View File

@@ -26,8 +26,9 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value = value; _value = value;
_flags = flags; _flags = flags;
WriteEvent = new ManualResetEvent(true); WriteEvent = new ManualResetEvent(false);
ReadEvent = new ManualResetEvent(true); ReadEvent = new ManualResetEvent(false);
UpdateEventStates();
} }
public int Refcount { get; set; } public int Refcount { get; set; }
@@ -38,6 +39,25 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
ReadEvent.Dispose(); ReadEvent.Dispose();
} }
private void ResetEventStates()
{
WriteEvent.Reset();
ReadEvent.Reset();
}
private void UpdateEventStates()
{
if (_value > 0)
{
ReadEvent.Set();
}
if (_value != uint.MaxValue - 1)
{
WriteEvent.Set();
}
}
public LinuxError Read(out int readSize, Span<byte> buffer) public LinuxError Read(out int readSize, Span<byte> buffer)
{ {
if (buffer.Length < sizeof(ulong)) if (buffer.Length < sizeof(ulong))
@@ -47,10 +67,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EINVAL; return LinuxError.EINVAL;
} }
ReadEvent.Reset();
lock (_lock) lock (_lock)
{ {
ResetEventStates();
ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0]; ref ulong count = ref MemoryMarshal.Cast<byte, ulong>(buffer)[0];
if (_value == 0) if (_value == 0)
@@ -66,6 +86,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
readSize = 0; readSize = 0;
UpdateEventStates();
return LinuxError.EAGAIN; return LinuxError.EAGAIN;
} }
} }
@@ -85,8 +106,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value = 0; _value = 0;
} }
ReadEvent.Set(); UpdateEventStates();
return LinuxError.SUCCESS; return LinuxError.SUCCESS;
} }
} }
@@ -100,10 +120,10 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
return LinuxError.EINVAL; return LinuxError.EINVAL;
} }
WriteEvent.Reset();
lock (_lock) lock (_lock)
{ {
ResetEventStates();
if (_value > _value + count) if (_value > _value + count)
{ {
if (Blocking) if (Blocking)
@@ -114,6 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
writeSize = 0; writeSize = 0;
UpdateEventStates();
return LinuxError.EAGAIN; return LinuxError.EAGAIN;
} }
} }
@@ -123,8 +144,7 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
_value += count; _value += count;
Monitor.Pulse(_lock); Monitor.Pulse(_lock);
WriteEvent.Set(); UpdateEventStates();
return LinuxError.SUCCESS; return LinuxError.SUCCESS;
} }
} }

View File

@@ -68,20 +68,37 @@ namespace Ryujinx.HLE.HOS.Services.Sockets.Bsd
{ {
for (int i = 0; i < events.Count; i++) for (int i = 0; i < events.Count; i++)
{ {
PollEventTypeMask outputEvents = 0;
PollEvent evnt = events[i]; PollEvent evnt = events[i];
EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor; EventFileDescriptor socket = (EventFileDescriptor)evnt.FileDescriptor;
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input) || if (socket.ReadEvent.WaitOne(0))
evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
&& socket.ReadEvent.WaitOne(0))
{ {
waiters.Add(socket.ReadEvent); if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Input))
{
outputEvents |= PollEventTypeMask.Input;
}
if (evnt.Data.InputEvents.HasFlag(PollEventTypeMask.UrgentInput))
{
outputEvents |= PollEventTypeMask.UrgentInput;
}
} }
if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output)) if ((evnt.Data.InputEvents.HasFlag(PollEventTypeMask.Output))
&& socket.WriteEvent.WaitOne(0)) && socket.WriteEvent.WaitOne(0))
{ {
waiters.Add(socket.WriteEvent); outputEvents |= PollEventTypeMask.Output;
}
if (outputEvents != 0)
{
evnt.Data.OutputEvents = outputEvents;
updatedCount++;
} }
} }
} }