|
|
@@ -22,230 +22,231 @@ using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.IO;
|
|
|
using System.Linq;
|
|
|
-using System.Text;
|
|
|
using Path = System.IO.Path;
|
|
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
|
|
|
|
|
-namespace Ryujinx.Ava.UI.ViewModels;
|
|
|
-
|
|
|
-public class TitleUpdateViewModel : BaseModel
|
|
|
+namespace Ryujinx.Ava.UI.ViewModels
|
|
|
{
|
|
|
- public TitleUpdateMetadata _titleUpdateWindowData;
|
|
|
- public readonly string _titleUpdateJsonPath;
|
|
|
- private VirtualFileSystem _virtualFileSystem { get; }
|
|
|
- private ulong _titleId { get; }
|
|
|
- private string _titleName { get; }
|
|
|
+ public class TitleUpdateViewModel : BaseModel
|
|
|
+ {
|
|
|
+ public TitleUpdateMetadata _titleUpdateWindowData;
|
|
|
+ public readonly string _titleUpdateJsonPath;
|
|
|
+ private VirtualFileSystem _virtualFileSystem { get; }
|
|
|
+ private ulong _titleId { get; }
|
|
|
+ private string _titleName { get; }
|
|
|
|
|
|
- private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
|
|
- private AvaloniaList<object> _views = new();
|
|
|
- private object _selectedUpdate;
|
|
|
+ private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
|
|
+ private AvaloniaList<object> _views = new();
|
|
|
+ private object _selectedUpdate;
|
|
|
|
|
|
- public AvaloniaList<TitleUpdateModel> TitleUpdates
|
|
|
- {
|
|
|
- get => _titleUpdates;
|
|
|
- set
|
|
|
+ private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
|
|
+
|
|
|
+ public AvaloniaList<TitleUpdateModel> TitleUpdates
|
|
|
{
|
|
|
- _titleUpdates = value;
|
|
|
- OnPropertyChanged();
|
|
|
+ get => _titleUpdates;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _titleUpdates = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public AvaloniaList<object> Views
|
|
|
- {
|
|
|
- get => _views;
|
|
|
- set
|
|
|
+ public AvaloniaList<object> Views
|
|
|
{
|
|
|
- _views = value;
|
|
|
- OnPropertyChanged();
|
|
|
+ get => _views;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _views = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public object SelectedUpdate
|
|
|
- {
|
|
|
- get => _selectedUpdate;
|
|
|
- set
|
|
|
+ public object SelectedUpdate
|
|
|
{
|
|
|
- _selectedUpdate = value;
|
|
|
- OnPropertyChanged();
|
|
|
+ get => _selectedUpdate;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _selectedUpdate = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
|
|
- {
|
|
|
- _virtualFileSystem = virtualFileSystem;
|
|
|
-
|
|
|
- _titleId = titleId;
|
|
|
- _titleName = titleName;
|
|
|
+ public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
|
|
+ {
|
|
|
+ _virtualFileSystem = virtualFileSystem;
|
|
|
|
|
|
- _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
|
|
+ _titleId = titleId;
|
|
|
+ _titleName = titleName;
|
|
|
|
|
|
- try
|
|
|
- {
|
|
|
- _titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
|
|
- }
|
|
|
- catch
|
|
|
- {
|
|
|
- Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
|
|
+ _titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
|
|
|
|
|
- _titleUpdateWindowData = new TitleUpdateMetadata
|
|
|
+ try
|
|
|
{
|
|
|
- Selected = "",
|
|
|
- Paths = new List<string>()
|
|
|
- };
|
|
|
+ _titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
|
|
|
+ }
|
|
|
+ catch
|
|
|
+ {
|
|
|
+ Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
|
|
|
|
|
- Save();
|
|
|
- }
|
|
|
+ _titleUpdateWindowData = new TitleUpdateMetadata
|
|
|
+ {
|
|
|
+ Selected = "",
|
|
|
+ Paths = new List<string>()
|
|
|
+ };
|
|
|
|
|
|
- LoadUpdates();
|
|
|
- }
|
|
|
+ Save();
|
|
|
+ }
|
|
|
|
|
|
- private void LoadUpdates()
|
|
|
- {
|
|
|
- foreach (string path in _titleUpdateWindowData.Paths)
|
|
|
- {
|
|
|
- AddUpdate(path);
|
|
|
+ LoadUpdates();
|
|
|
}
|
|
|
|
|
|
- TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
|
|
+ private void LoadUpdates()
|
|
|
+ {
|
|
|
+ foreach (string path in _titleUpdateWindowData.Paths)
|
|
|
+ {
|
|
|
+ AddUpdate(path);
|
|
|
+ }
|
|
|
|
|
|
- SelectedUpdate = selected;
|
|
|
+ TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
|
|
|
|
|
- // NOTE: Save the list again to remove leftovers.
|
|
|
- Save();
|
|
|
+ SelectedUpdate = selected;
|
|
|
|
|
|
- SortUpdates();
|
|
|
- }
|
|
|
-
|
|
|
- public void SortUpdates()
|
|
|
- {
|
|
|
- var list = TitleUpdates.ToList();
|
|
|
+ // NOTE: Save the list again to remove leftovers.
|
|
|
+ Save();
|
|
|
+ SortUpdates();
|
|
|
+ }
|
|
|
|
|
|
- list.Sort((first, second) =>
|
|
|
+ public void SortUpdates()
|
|
|
{
|
|
|
- if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
|
|
- {
|
|
|
- return -1;
|
|
|
- }
|
|
|
- else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
|
|
+ var list = TitleUpdates.ToList();
|
|
|
+
|
|
|
+ list.Sort((first, second) =>
|
|
|
{
|
|
|
- return 1;
|
|
|
- }
|
|
|
+ if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
|
|
+ {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
|
|
+ {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
|
|
|
- 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;
|
|
|
+ });
|
|
|
|
|
|
- Views.Clear();
|
|
|
- Views.Add(new BaseModel());
|
|
|
- Views.AddRange(list);
|
|
|
+ Views.Clear();
|
|
|
+ Views.Add(new BaseModel());
|
|
|
+ Views.AddRange(list);
|
|
|
|
|
|
- if (SelectedUpdate == null)
|
|
|
- {
|
|
|
- SelectedUpdate = Views[0];
|
|
|
- }
|
|
|
- else if (!TitleUpdates.Contains(SelectedUpdate))
|
|
|
- {
|
|
|
- if (Views.Count > 1)
|
|
|
+ if (SelectedUpdate == null)
|
|
|
{
|
|
|
- SelectedUpdate = Views[1];
|
|
|
+ SelectedUpdate = Views[0];
|
|
|
}
|
|
|
- else
|
|
|
+ else if (!TitleUpdates.Contains(SelectedUpdate))
|
|
|
{
|
|
|
- SelectedUpdate = Views[0];
|
|
|
+ if (Views.Count > 1)
|
|
|
+ {
|
|
|
+ SelectedUpdate = Views[1];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ SelectedUpdate = Views[0];
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private void AddUpdate(string path)
|
|
|
- {
|
|
|
- if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
|
|
+ private void AddUpdate(string path)
|
|
|
{
|
|
|
- using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
|
|
-
|
|
|
- try
|
|
|
+ if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
|
|
{
|
|
|
- (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
|
|
+ using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
|
|
|
|
|
- if (controlNca != null && patchNca != null)
|
|
|
+ try
|
|
|
{
|
|
|
- ApplicationControlProperty controlData = new();
|
|
|
+ (Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
|
|
|
|
|
- using UniqueRef<IFile> nacpFile = new();
|
|
|
+ if (controlNca != null && patchNca != null)
|
|
|
+ {
|
|
|
+ ApplicationControlProperty controlData = new();
|
|
|
+
|
|
|
+ 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();
|
|
|
+ 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));
|
|
|
+ TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Dispatcher.UIThread.Post(async () =>
|
|
|
+ {
|
|
|
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
- else
|
|
|
+ catch (Exception ex)
|
|
|
{
|
|
|
Dispatcher.UIThread.Post(async () =>
|
|
|
{
|
|
|
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
|
|
+ await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- Dispatcher.UIThread.Post(async () =>
|
|
|
- {
|
|
|
- await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path));
|
|
|
- });
|
|
|
- }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public void RemoveUpdate(TitleUpdateModel update)
|
|
|
- {
|
|
|
- TitleUpdates.Remove(update);
|
|
|
-
|
|
|
- SortUpdates();
|
|
|
- }
|
|
|
-
|
|
|
- public async void Add()
|
|
|
- {
|
|
|
- OpenFileDialog dialog = new()
|
|
|
+ public void RemoveUpdate(TitleUpdateModel update)
|
|
|
{
|
|
|
- Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
|
|
- AllowMultiple = true
|
|
|
- };
|
|
|
+ TitleUpdates.Remove(update);
|
|
|
|
|
|
- dialog.Filters.Add(new FileDialogFilter
|
|
|
- {
|
|
|
- Name = "NSP",
|
|
|
- Extensions = { "nsp" }
|
|
|
- });
|
|
|
+ SortUpdates();
|
|
|
+ }
|
|
|
|
|
|
- if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
+ public async void Add()
|
|
|
{
|
|
|
- string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
|
|
+ OpenFileDialog dialog = new()
|
|
|
+ {
|
|
|
+ Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
|
|
+ AllowMultiple = true
|
|
|
+ };
|
|
|
|
|
|
- if (files != null)
|
|
|
+ dialog.Filters.Add(new FileDialogFilter
|
|
|
{
|
|
|
- foreach (string file in files)
|
|
|
+ Name = "NSP",
|
|
|
+ Extensions = { "nsp" }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
|
|
+ {
|
|
|
+ string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
|
|
+
|
|
|
+ if (files != null)
|
|
|
{
|
|
|
- AddUpdate(file);
|
|
|
+ foreach (string file in files)
|
|
|
+ {
|
|
|
+ AddUpdate(file);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- SortUpdates();
|
|
|
- }
|
|
|
|
|
|
- public void Save()
|
|
|
- {
|
|
|
- _titleUpdateWindowData.Paths.Clear();
|
|
|
- _titleUpdateWindowData.Selected = "";
|
|
|
+ SortUpdates();
|
|
|
+ }
|
|
|
|
|
|
- foreach (TitleUpdateModel update in TitleUpdates)
|
|
|
+ public void Save()
|
|
|
{
|
|
|
- _titleUpdateWindowData.Paths.Add(update.Path);
|
|
|
+ _titleUpdateWindowData.Paths.Clear();
|
|
|
+ _titleUpdateWindowData.Selected = "";
|
|
|
|
|
|
- if (update == SelectedUpdate)
|
|
|
+ foreach (TitleUpdateModel update in TitleUpdates)
|
|
|
{
|
|
|
- _titleUpdateWindowData.Selected = update.Path;
|
|
|
+ _titleUpdateWindowData.Paths.Add(update.Path);
|
|
|
+
|
|
|
+ if (update == SelectedUpdate)
|
|
|
+ {
|
|
|
+ _titleUpdateWindowData.Selected = update.Path;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- File.WriteAllBytes(_titleUpdateJsonPath, Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
|
|
+ JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
|
|
|
+ }
|
|
|
}
|
|
|
}
|