ava: Rework DLC Manager, Add various fixes and cleanup (#3896)

* Fixes Everything Part.2

* Change sorting, fix remove and heading
This commit is contained in:
Ac_K 2022-11-25 12:41:34 +01:00 committed by GitHub
parent 7aa6abc120
commit 3fbacd0f49
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 411 additions and 339 deletions

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",
@ -567,7 +569,7 @@
"DlcWindowTitle": "Manage Game DLC", "DlcWindowTitle": "Manage Game DLC",
"UpdateWindowTitle": "Manage Game Updates", "UpdateWindowTitle": "Manage Game Updates",
"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",

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

@ -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, selection.TitleId, 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,126 @@
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="800"
Width="600" MinHeight="500" Height="500" Height="500"
WindowStartupLocation="CenterOwner"
MinWidth="600" MinWidth="600"
MinHeight="500"
SizeToContent="Height"
WindowStartupLocation="CenterOwner"
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

@ -8,6 +8,7 @@ using LibHac.FsSystem;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.FsSystem.Save;
using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Ui.Controls; using Ryujinx.Ava.Ui.Controls;
using Ryujinx.Ava.Ui.Models; using Ryujinx.Ava.Ui.Models;
@ -16,8 +17,11 @@ using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.FileSystem;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
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 +31,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 +45,15 @@ 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 +70,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 +98,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 pfs = new(containerFile.AsStorage());
VirtualFileSystem.ImportTickets(pfs); _virtualFileSystem.ImportTickets(pfs);
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(); pfs.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 +124,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 +143,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 +222,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 +261,8 @@ namespace Ryujinx.Ava.Ui.Windows
await AddDownloadableContent(file); await AddDownloadableContent(file);
} }
} }
PrintHeading();
} }
public void Save() public void Save()
@ -222,7 +271,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

@ -131,6 +131,13 @@ namespace Ryujinx.Ava.Ui.Windows
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure(); nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
TitleUpdates.Add(new TitleUpdateModel(controlData, path)); TitleUpdates.Add(new TitleUpdateModel(controlData, path));
foreach (var update in TitleUpdates)
{
update.IsEnabled = false;
}
TitleUpdates.Last().IsEnabled = true;
} }
else else
{ {