[Ryujinx.Ava] Address dotnet-format issues (#5361)

* dotnet format style --severity info

Some changes were manually reverted.

* dotnet format analyzers --serverity info

Some changes have been minimally adapted.

* Restore a few unused methods and variables

* Silence dotnet format IDE0060 warnings

* Silence dotnet format IDE0052 warnings

* Silence dotnet format IDE0059 warnings

* Address or silence dotnet format IDE1006 warnings

* Address dotnet format CA1816 warnings

* Address dotnet format CA1822 warnings

* Address or silence dotnet format CA1069 warnings

* Make dotnet format succeed in style mode

* Address dotnet format CA1401 warnings

* Address remaining dotnet format analyzer warnings

* Address review comments

* dotnet-format fixes after rebase

* Address most dotnet format whitespace warnings

* Apply dotnet format whitespace formatting

A few of them have been manually reverted and the corresponding warning was silenced

* Format if-blocks correctly

* Another rebase, another dotnet format run

* Run dotnet format whitespace after rebase

* Run dotnet format style after rebase

* Run dotnet format after rebase and remove unused usings

- analyzers
- style
- whitespace

* Add comments to disabled warnings

* Remove a few unused parameters

* Simplify properties and array initialization, Use const when possible, Remove trailing commas

* Start working on disabled warnings

* Fix and silence a few dotnet-format warnings again

* Address IDE0260 warnings

* Address a few disabled IDE0060 warnings

* Silence IDE0060 in .editorconfig

* Revert "Simplify properties and array initialization, Use const when possible, Remove trailing commas"

This reverts commit 9462e4136c0a2100dc28b20cf9542e06790aa67e.

* dotnet format whitespace after rebase

* dotnet format pass with new editorconfig

* Fix naming style issues

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Revert one suggestion

* Second dotnet format pass and fix build issues

* Final pass of dotnet format

* Add trailing commas

* Fix formatting issues

* Keep unnecessary assignment in IconColorPicker.cs

* Use using declarations and extend resource lifetimes

* Fix rebase issues

* Adjust comment spacing

* Fix typo

* Fix naming issues

* Apply suggestions from code review

Co-authored-by: Ac_K <Acoustik666@gmail.com>

* Revert unintentional change

* Remove unused file

* Remove static keyword from ViewModels

Binding of static members doesn't work and is silently ignored.

---------

Co-authored-by: Ac_K <Acoustik666@gmail.com>
This commit is contained in:
TSRBerry
2023-07-07 23:03:27 +02:00
committed by GitHub
parent 8a363b5df2
commit 6c515e1822
108 changed files with 1140 additions and 1503 deletions

View File

@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Threading;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Common.Utilities;
@@ -87,7 +88,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Version = Program.Version;
var assets = AvaloniaLocator.Current.GetService<Avalonia.Platform.IAssetLoader>();
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
if (ConfigurationState.Instance.Ui.BaseStyle.Value == "Light")
{
@@ -130,4 +131,4 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
}
}

View File

@@ -18,7 +18,6 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using AmiiboJsonSerializerContext = Ryujinx.Ui.Common.Models.Amiibo.AmiiboJsonSerializerContext;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -44,7 +43,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _useRandomUuid;
private string _usage;
private static readonly AmiiboJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly AmiiboJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AmiiboWindowViewModel(StyleableWindow owner, string lastScannedAmiiboId, string titleId)
{
@@ -52,7 +51,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
Timeout = TimeSpan.FromSeconds(30),
};
LastScannedAmiiboId = lastScannedAmiiboId;
@@ -185,6 +184,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Dispose()
{
GC.SuppressFinalize(this);
_httpClient.Dispose();
}
@@ -196,7 +196,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
amiiboJsonString = await File.ReadAllTextAsync(_amiiboJsonPath);
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).LastUpdated))
if (await NeedsUpdate(JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).LastUpdated))
{
amiiboJsonString = await DownloadAmiiboJson();
}
@@ -215,7 +215,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, SerializerContext.AmiiboJson).Amiibo;
_amiiboList = JsonHelper.Deserialize(amiiboJsonString, _serializerContext.AmiiboJson).Amiibo;
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
ParseAmiiboData();
@@ -426,18 +426,17 @@ namespace Ryujinx.Ava.UI.ViewModels
if (response.IsSuccessStatusCode)
{
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
using (MemoryStream memoryStream = new(amiiboPreviewBytes))
{
Bitmap bitmap = new(memoryStream);
using MemoryStream memoryStream = new(amiiboPreviewBytes);
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
Bitmap bitmap = new(memoryStream);
double ratio = Math.Min(AmiiboImageSize / bitmap.Size.Width,
AmiiboImageSize / bitmap.Size.Height);
int resizeHeight = (int)(bitmap.Size.Height * ratio);
int resizeWidth = (int)(bitmap.Size.Width * ratio);
int resizeHeight = (int)(bitmap.Size.Height * ratio);
int resizeWidth = (int)(bitmap.Size.Width * ratio);
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
}
AmiiboImage = bitmap.CreateScaledBitmap(new PixelSize(resizeWidth, resizeHeight));
}
else
{
@@ -447,15 +446,14 @@ namespace Ryujinx.Ava.UI.ViewModels
private void ResetAmiiboPreview()
{
using (MemoryStream memoryStream = new(_amiiboLogoBytes))
{
Bitmap bitmap = new(memoryStream);
using MemoryStream memoryStream = new(_amiiboLogoBytes);
AmiiboImage = bitmap;
}
Bitmap bitmap = new(memoryStream);
AmiiboImage = bitmap;
}
private async void ShowInfoDialog()
private static async void ShowInfoDialog()
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],
@@ -464,4 +462,4 @@ namespace Ryujinx.Ava.UI.ViewModels
LocaleManager.Instance[LocaleKeys.RyujinxInfo]);
}
}
}
}

View File

@@ -1,363 +0,0 @@
using Avalonia.Media;
using DynamicData;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.UI.Models;
using Ryujinx.HLE.FileSystem;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Color = Avalonia.Media.Color;
namespace Ryujinx.Ava.UI.ViewModels
{
internal class AvatarProfileViewModel : BaseModel, IDisposable
{
private const int MaxImageTasks = 4;
private static readonly Dictionary<string, byte[]> _avatarStore = new();
private static bool _isPreloading;
private static Action _loadCompleteAction;
private ObservableCollection<ProfileImageModel> _images;
private Color _backgroundColor = Colors.White;
private int _selectedIndex;
private int _imagesLoaded;
private bool _isActive;
private byte[] _selectedImage;
private bool _isIndeterminate = true;
public bool IsActive
{
get => _isActive;
set => _isActive = value;
}
public AvatarProfileViewModel()
{
_images = new ObservableCollection<ProfileImageModel>();
}
public AvatarProfileViewModel(Action loadCompleteAction)
{
_images = new ObservableCollection<ProfileImageModel>();
if (_isPreloading)
{
_loadCompleteAction = loadCompleteAction;
}
else
{
ReloadImages();
}
}
public Color BackgroundColor
{
get => _backgroundColor;
set
{
_backgroundColor = value;
IsActive = false;
ReloadImages();
}
}
public ObservableCollection<ProfileImageModel> Images
{
get => _images;
set
{
_images = value;
OnPropertyChanged();
}
}
public bool IsIndeterminate
{
get => _isIndeterminate;
set
{
_isIndeterminate = value;
OnPropertyChanged();
}
}
public int ImageCount => _avatarStore.Count;
public int ImagesLoaded
{
get => _imagesLoaded;
set
{
_imagesLoaded = value;
OnPropertyChanged();
}
}
public int SelectedIndex
{
get => _selectedIndex;
set
{
_selectedIndex = value;
if (_selectedIndex == -1)
{
SelectedImage = null;
}
else
{
SelectedImage = _images[_selectedIndex].Data;
}
OnPropertyChanged();
}
}
public byte[] SelectedImage
{
get => _selectedImage;
private set => _selectedImage = value;
}
public void ReloadImages()
{
if (_isPreloading)
{
IsIndeterminate = false;
return;
}
Task.Run(() =>
{
IsActive = true;
Images.Clear();
int selectedIndex = _selectedIndex;
int index = 0;
ImagesLoaded = 0;
IsIndeterminate = false;
var keys = _avatarStore.Keys.ToList();
var newImages = new List<ProfileImageModel>();
var tasks = new List<Task>();
for (int i = 0; i < MaxImageTasks; i++)
{
var start = i;
tasks.Add(Task.Run(() => ImageTask(start)));
}
Task.WaitAll(tasks.ToArray());
Images.AddRange(newImages);
void ImageTask(int start)
{
for (int i = start; i < keys.Count; i += MaxImageTasks)
{
if (!IsActive)
{
return;
}
var key = keys[i];
var image = _avatarStore[keys[i]];
var data = ProcessImage(image);
newImages.Add(new ProfileImageModel(key, data));
if (index++ == selectedIndex)
{
SelectedImage = data;
}
Interlocked.Increment(ref _imagesLoaded);
OnPropertyChanged(nameof(ImagesLoaded));
}
}
});
}
private byte[] ProcessImage(byte[] data)
{
using (MemoryStream streamJpg = new())
{
Image avatarImage = Image.Load(data, new PngDecoder());
avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(BackgroundColor.R,
BackgroundColor.G,
BackgroundColor.B,
BackgroundColor.A)));
avatarImage.SaveAsJpeg(streamJpg);
return streamJpg.ToArray();
}
}
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
{
try
{
if (_avatarStore.Count > 0)
{
return;
}
_isPreloading = true;
string contentPath =
contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem,
NcaContentType.Data);
string avatarPath = virtualFileSystem.SwitchPathToSystemPath(contentPath);
if (!string.IsNullOrWhiteSpace(avatarPath))
{
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
{
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
{
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") &&
item.FullPath.Contains("szs"))
{
using var file = new UniqueRef<IFile>();
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
.ThrowIfFailure();
using (MemoryStream stream = new())
using (MemoryStream streamPng = new())
{
file.Get.AsStream().CopyTo(stream);
stream.Position = 0;
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
avatarImage.SaveAsPng(streamPng);
_avatarStore.Add(item.FullPath, streamPng.ToArray());
}
}
}
}
}
}
finally
{
_isPreloading = false;
_loadCompleteAction?.Invoke();
}
}
private static byte[] DecompressYaz0(Stream stream)
{
using (BinaryReader reader = new(stream))
{
reader.ReadInt32(); // Magic
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
stream.Read(input, 0, input.Length);
uint inputOffset = 0;
byte[] output = new byte[decodedLength];
uint outputOffset = 0;
ushort mask = 0;
byte header = 0;
while (outputOffset < decodedLength)
{
if ((mask >>= 1) == 0)
{
header = input[inputOffset++];
mask = 0x80;
}
if ((header & mask) != 0)
{
if (outputOffset == output.Length)
{
break;
}
output[outputOffset++] = input[inputOffset++];
}
else
{
byte byte1 = input[inputOffset++];
byte byte2 = input[inputOffset++];
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
uint position = outputOffset - (dist + 1);
uint length = (uint)byte1 >> 4;
if (length == 0)
{
length = (uint)input[inputOffset++] + 0x12;
}
else
{
length += 2;
}
uint gap = outputOffset - position;
uint nonOverlappingLength = length;
if (nonOverlappingLength > gap)
{
nonOverlappingLength = gap;
}
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
outputOffset += nonOverlappingLength;
position += nonOverlappingLength;
length -= nonOverlappingLength;
while (length-- > 0)
{
output[outputOffset++] = output[position++];
}
}
}
return output;
}
}
public void Dispose()
{
_loadCompleteAction = null;
IsActive = false;
}
}
}

View File

@@ -12,4 +12,4 @@ namespace Ryujinx.Ava.UI.ViewModels
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

View File

@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@@ -44,15 +45,14 @@ namespace Ryujinx.Ava.UI.ViewModels
private PlayerIndex _playerId;
private int _controller;
private int _controllerNumber = 0;
private int _controllerNumber;
private string _controllerImage;
private int _device;
private object _configuration;
private string _profileName;
private bool _isLoaded;
private readonly UserControl _owner;
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public IGamepadDriver AvaloniaKeyboardDriver { get; }
public IGamepad SelectedGamepad { get; private set; }
@@ -176,11 +176,11 @@ namespace Ryujinx.Ava.UI.ViewModels
{
get
{
SvgImage image = new SvgImage();
SvgImage image = new();
if (!string.IsNullOrWhiteSpace(_controllerImage))
{
SvgSource source = new SvgSource();
SvgSource source = new();
source.Load(EmbeddedResources.GetStream(_controllerImage));
@@ -234,22 +234,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public ControllerInputViewModel(UserControl owner) : this()
{
_owner = owner;
if (Program.PreviewerDetached)
{
_mainWindow =
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Avalonia.Application.Current
(MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current
.ApplicationLifetime).MainWindow;
AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner);
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected;
if (_mainWindow.ViewModel.AppHost != null)
{
_mainWindow.ViewModel.AppHost.NpadManager.BlockInputUpdates();
}
_mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates();
_isLoaded = false;
@@ -351,7 +347,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{
return;
}
else if (type == DeviceType.Keyboard)
if (type == DeviceType.Keyboard)
{
if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver)
{
@@ -448,7 +445,7 @@ namespace Ryujinx.Ava.UI.ViewModels
const string Hyphen = "-";
const int Offset = 1;
return str.Substring(str.IndexOf(Hyphen) + Offset);
return str[(str.IndexOf(Hyphen) + Offset)..];
}
public void LoadDevices()
@@ -562,7 +559,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ButtonL = Key.E,
ButtonZl = Key.Q,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound
ButtonSr = Key.Unbound,
},
LeftJoyconStick =
new JoyconConfigKeyboardStick<Key>
@@ -571,7 +568,7 @@ namespace Ryujinx.Ava.UI.ViewModels
StickDown = Key.S,
StickLeft = Key.A,
StickRight = Key.D,
StickButton = Key.F
StickButton = Key.F,
},
RightJoycon = new RightJoyconCommonConfig<Key>
{
@@ -583,7 +580,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ButtonR = Key.U,
ButtonZr = Key.O,
ButtonSl = Key.Unbound,
ButtonSr = Key.Unbound
ButtonSr = Key.Unbound,
},
RightJoyconStick = new JoyconConfigKeyboardStick<Key>
{
@@ -591,8 +588,8 @@ namespace Ryujinx.Ava.UI.ViewModels
StickDown = Key.K,
StickLeft = Key.J,
StickRight = Key.L,
StickButton = Key.H
}
StickButton = Key.H,
},
};
}
else if (activeDevice.Type == DeviceType.Controller)
@@ -622,14 +619,14 @@ namespace Ryujinx.Ava.UI.ViewModels
ButtonL = ConfigGamepadInputId.LeftShoulder,
ButtonZl = ConfigGamepadInputId.LeftTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound
ButtonSr = ConfigGamepadInputId.Unbound,
},
LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Left,
StickButton = ConfigGamepadInputId.LeftStick,
InvertStickX = false,
InvertStickY = false
InvertStickY = false,
},
RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId>
{
@@ -641,28 +638,28 @@ namespace Ryujinx.Ava.UI.ViewModels
ButtonR = ConfigGamepadInputId.RightShoulder,
ButtonZr = ConfigGamepadInputId.RightTrigger,
ButtonSl = ConfigGamepadInputId.Unbound,
ButtonSr = ConfigGamepadInputId.Unbound
ButtonSr = ConfigGamepadInputId.Unbound,
},
RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId>
{
Joystick = ConfigStickInputId.Right,
StickButton = ConfigGamepadInputId.RightStick,
InvertStickX = false,
InvertStickY = false
InvertStickY = false,
},
Motion = new StandardMotionConfigController
{
MotionBackend = MotionInputBackendType.GamepadDriver,
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1
GyroDeadzone = 1,
},
Rumble = new RumbleConfigController
{
StrongRumble = 1f,
WeakRumble = 1f,
EnableRumble = false
}
EnableRumble = false,
},
};
}
else
@@ -709,7 +706,7 @@ namespace Ryujinx.Ava.UI.ViewModels
try
{
config = JsonHelper.DeserializeFromFile(path, SerializerContext.InputConfig);
config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig);
}
catch (JsonException) { }
catch (InvalidOperationException)
@@ -754,37 +751,35 @@ namespace Ryujinx.Ava.UI.ViewModels
return;
}
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
}
else if (IsController)
{
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1;
if (validFileName)
{
string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json");
InputConfig config = null;
if (IsKeyboard)
{
config = (Configuration as InputConfiguration<Key, ConfigStickInputId>).GetConfig();
}
else if (IsController)
{
config = (Configuration as InputConfiguration<GamepadInputId, ConfigStickInputId>).GetConfig();
}
config.ControllerType = Controllers[_controller].Type;
string jsonString = JsonHelper.Serialize(config, SerializerContext.InputConfig);
await File.WriteAllTextAsync(path, jsonString);
LoadProfiles();
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
}
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]);
}
}
@@ -887,6 +882,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Dispose()
{
GC.SuppressFinalize(this);
_mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected;
_mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected;
@@ -897,4 +894,4 @@ namespace Ryujinx.Ava.UI.ViewModels
AvaloniaKeyboardDriver.Dispose();
}
}
}
}

View File

@@ -22,6 +22,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Application = Avalonia.Application;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.ViewModels
@@ -29,18 +30,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public class DownloadableContentManagerViewModel : BaseModel
{
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
private readonly string _downloadableContentJsonPath;
private readonly string _downloadableContentJsonPath;
private VirtualFileSystem _virtualFileSystem;
private readonly VirtualFileSystem _virtualFileSystem;
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
private AvaloniaList<DownloadableContentModel> _views = new();
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
private string _search;
private ulong _titleId;
private string _titleName;
private readonly ulong _titleId;
private static readonly DownloadableContentJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<DownloadableContentModel> DownloadableContents
{
@@ -90,18 +90,17 @@ namespace Ryujinx.Ava.UI.ViewModels
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
}
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
_virtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
_titleId = titleId;
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
try
{
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, SerializerContext.ListDownloadableContentContainer);
_downloadableContentContainerList = JsonHelper.DeserializeFromFile(_downloadableContentJsonPath, _serializerContext.ListDownloadableContentContainer);
}
catch
{
@@ -132,7 +131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
if (nca != null)
{
{
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
downloadableContentContainer.ContainerPath,
downloadableContentNca.FullPath,
@@ -196,19 +195,19 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void Add()
{
OpenFileDialog dialog = new OpenFileDialog()
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
AllowMultiple = true,
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
Name = "NSP",
Extensions = { "nsp" },
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
@@ -231,8 +230,8 @@ namespace Ryujinx.Ava.UI.ViewModels
using FileStream containerFile = File.OpenRead(path);
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
bool containsDownloadableContent = false;
_virtualFileSystem.ImportTickets(partitionFileSystem);
@@ -313,16 +312,16 @@ namespace Ryujinx.Ava.UI.ViewModels
container = new DownloadableContentContainer
{
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>()
ContainerPath = downloadableContent.ContainerPath,
DownloadableContentNcaList = new List<DownloadableContentNca>(),
};
}
container.DownloadableContentNcaList.Add(new DownloadableContentNca
{
Enabled = downloadableContent.Enabled,
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath
Enabled = downloadableContent.Enabled,
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
FullPath = downloadableContent.FullPath,
});
}
@@ -331,8 +330,8 @@ namespace Ryujinx.Ava.UI.ViewModels
_downloadableContentContainerList.Add(container);
}
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, SerializerContext.ListDownloadableContentContainer);
JsonHelper.SerializeToFile(_downloadableContentJsonPath, _downloadableContentContainerList, _serializerContext.ListDownloadableContentContainer);
}
}
}
}

View File

@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
@@ -12,6 +13,7 @@ using Ryujinx.Ava.Input;
using Ryujinx.Ava.UI.Controls;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.Models.Generic;
using Ryujinx.Ava.UI.Renderer;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
@@ -23,6 +25,7 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.Ui;
using Ryujinx.Modules;
using Ryujinx.Ui.App.Common;
using Ryujinx.Ui.Common;
using Ryujinx.Ui.Common.Configuration;
@@ -34,7 +37,10 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path;
using Image = SixLabors.ImageSharp.Image;
using InputManager = Ryujinx.Input.HLE.InputManager;
using Key = Ryujinx.Input.Key;
using MissingKeyException = LibHac.Common.Keys.MissingKeyException;
using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
namespace Ryujinx.Ava.UI.ViewModels
@@ -89,7 +95,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private Cursor _cursor;
private string _title;
private string _currentEmulatedGamePath;
private AutoResetEvent _rendererWaitEvent;
private readonly AutoResetEvent _rendererWaitEvent;
private WindowState _windowState;
private double _windowWidth;
private double _windowHeight;
@@ -128,7 +134,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationLibrary applicationLibrary,
VirtualFileSystem virtualFileSystem,
AccountManager accountManager,
Ryujinx.Input.HLE.InputManager inputManager,
InputManager inputManager,
UserChannelPersistence userChannelPersistence,
LibHacHorizonManager libHacHorizonManager,
IHostUiHandler uiHandler,
@@ -152,7 +158,7 @@ namespace Ryujinx.Ava.UI.ViewModels
TopLevel = topLevel;
}
#region Properties
#region Properties
public string SearchText
{
@@ -177,7 +183,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool CanUpdate
{
get => _canUpdate && EnableNonGameRunningControls && Modules.Updater.CanUpdate(false);
get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false);
set
{
_canUpdate = value;
@@ -343,11 +349,11 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool OpenUserSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0;
public bool OpenDeviceSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool OpenBcatSaveDirectoryEnabled => !Utilities.IsZeros(SelectedApplication.ControlHolder.ByteSpan) && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public string LoadHeading
{
@@ -888,7 +894,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public ApplicationLibrary ApplicationLibrary { get; private set; }
public VirtualFileSystem VirtualFileSystem { get; private set; }
public AccountManager AccountManager { get; private set; }
public Ryujinx.Input.HLE.InputManager InputManager { get; private set; }
public InputManager InputManager { get; private set; }
public UserChannelPersistence UserChannelPersistence { get; private set; }
public Action<bool> ShowLoading { get; private set; }
public Action<bool> SwitchToGameControl { get; private set; }
@@ -911,15 +917,16 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsGridLarge => ConfigurationState.Instance.Ui.GridSize == 3;
public bool IsGridHuge => ConfigurationState.Instance.Ui.GridSize == 4;
#endregion
#endregion
#region PrivateMethods
#region PrivateMethods
private IComparer<ApplicationData> GetComparer()
{
return SortMode switch
{
ApplicationSort.LastPlayed => new Models.Generic.LastPlayedSortComparer(IsAscending),
#pragma warning disable IDE0055 // Disable formatting
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
@@ -935,6 +942,7 @@ namespace Ryujinx.Ava.UI.ViewModels
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
_ => null,
#pragma warning restore IDE0055
};
}
@@ -1023,7 +1031,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Purge Applet Cache.
DirectoryInfo miiEditorCacheFolder = new DirectoryInfo(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache"));
if (miiEditorCacheFolder.Exists)
{
@@ -1044,18 +1052,21 @@ namespace Ryujinx.Ava.UI.ViewModels
{
RefreshFirmwareStatus();
}
}) { Name = "GUI.FirmwareInstallerThread" };
})
{
Name = "GUI.FirmwareInstallerThread",
};
thread.Start();
}
}
catch (LibHac.Common.Keys.MissingKeyException ex)
catch (MissingKeyException ex)
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
Logger.Error?.Print(LogClass.Application, ex.ToString());
async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, (desktop.MainWindow as MainWindow));
static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys);
Dispatcher.UIThread.Post(Action);
}
@@ -1120,7 +1131,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private void PrepareLoadScreen()
{
using MemoryStream stream = new(SelectedIcon);
using var gameIconBmp = SixLabors.ImageSharp.Image.Load<Bgra32>(stream);
using var gameIconBmp = Image.Load<Bgra32>(stream);
var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp).ToPixel<Bgra32>();
@@ -1175,7 +1186,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Dispatcher.UIThread.InvokeAsync(() =>
{
Avalonia.Application.Current.Styles.TryGetResource(args.VSyncEnabled
Application.Current.Styles.TryGetResource(args.VSyncEnabled
? "VsyncEnabled"
: "VsyncDisabled", out object color);
@@ -1204,11 +1215,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.Set();
}
#endregion
#endregion
#region PublicMethods
#region PublicMethods
public void SetUIProgressHandlers(Switch emulationContext)
public void SetUiProgressHandlers(Switch emulationContext)
{
if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null)
{
@@ -1222,17 +1233,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public void LoadConfigurableHotKeys()
{
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUi, out var showUiKey))
{
ShowUiKey = new KeyGesture(showUiKey);
}
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey))
{
ScreenshotKey = new KeyGesture(screenshotKey);
}
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Ryujinx.Input.Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey))
{
PauseKey = new KeyGesture(pauseKey);
}
@@ -1260,12 +1271,12 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void InstallFirmwareFromFile()
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
OpenFileDialog dialog = new() { AllowMultiple = false };
dialog.Filters.Add(new FileDialogFilter { Name = LocaleManager.Instance[LocaleKeys.FileDialogAllTypes], Extensions = { "xci", "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "ZIP", Extensions = { "zip" } });
string[] file = await dialog.ShowAsync(desktop.MainWindow);
@@ -1278,7 +1289,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void InstallFirmwareFromFolder()
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
OpenFolderDialog dialog = new();
@@ -1327,7 +1338,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public void ChangeLanguage(object languageCode)
public static void ChangeLanguage(object languageCode)
{
LocaleManager.Instance.LoadLanguage((string)languageCode);
@@ -1342,6 +1353,7 @@ namespace Ryujinx.Ava.UI.ViewModels
{
_ = fileType switch
{
#pragma warning disable IDE0055 // Disable formatting
"NSP" => ConfigurationState.Instance.Ui.ShownFileTypes.NSP.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSP,
"PFS0" => ConfigurationState.Instance.Ui.ShownFileTypes.PFS0.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.PFS0,
"XCI" => ConfigurationState.Instance.Ui.ShownFileTypes.XCI.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.XCI,
@@ -1349,6 +1361,7 @@ namespace Ryujinx.Ava.UI.ViewModels
"NRO" => ConfigurationState.Instance.Ui.ShownFileTypes.NRO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NRO,
"NSO" => ConfigurationState.Instance.Ui.ShownFileTypes.NSO.Value = !ConfigurationState.Instance.Ui.ShownFileTypes.NSO,
_ => throw new ArgumentOutOfRangeException(fileType),
#pragma warning restore IDE0055
};
ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
@@ -1371,9 +1384,9 @@ namespace Ryujinx.Ava.UI.ViewModels
{
Applications.Clear();
StatusBarVisible = true;
StatusBarVisible = true;
StatusBarProgressMaximum = 0;
StatusBarProgressValue = 0;
StatusBarProgressValue = 0;
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
});
@@ -1383,11 +1396,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void OpenFile()
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle]
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
};
dialog.Filters.Add(new FileDialogFilter
@@ -1400,16 +1413,18 @@ namespace Ryujinx.Ava.UI.ViewModels
"xci",
"nca",
"nro",
"nso"
}
"nso",
},
});
#pragma warning disable IDE0055 // Disable formatting
dialog.Filters.Add(new FileDialogFilter { Name = "NSP", Extensions = { "nsp" } });
dialog.Filters.Add(new FileDialogFilter { Name = "PFS0", Extensions = { "pfs0" } });
dialog.Filters.Add(new FileDialogFilter { Name = "XCI", Extensions = { "xci" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NCA", Extensions = { "nca" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NRO", Extensions = { "nro" } });
dialog.Filters.Add(new FileDialogFilter { Name = "NSO", Extensions = { "nso" } });
#pragma warning restore IDE0055
string[] files = await dialog.ShowAsync(desktop.MainWindow);
@@ -1422,11 +1437,11 @@ namespace Ryujinx.Ava.UI.ViewModels
public async void OpenFolder()
{
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
OpenFolderDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle]
Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle],
};
string folder = await dialog.ShowAsync(desktop.MainWindow);
@@ -1458,10 +1473,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.RestartTime();
if (SelectedIcon == null)
{
SelectedIcon = ApplicationLibrary.GetApplicationIcon(path);
}
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
PrepareLoadScreen();
@@ -1495,7 +1507,7 @@ namespace Ryujinx.Ava.UI.ViewModels
if (string.IsNullOrWhiteSpace(titleName))
{
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name);
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
TitleName = AppHost.Device.Processes.ActiveApplication.Name;
}
SwitchToRenderer(startFullscreen);
@@ -1521,7 +1533,7 @@ namespace Ryujinx.Ava.UI.ViewModels
});
}
public void UpdateGameMetadata(string titleId)
public static void UpdateGameMetadata(string titleId)
{
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
{
@@ -1675,6 +1687,6 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
#endregion
#endregion
}
}

View File

@@ -90,4 +90,4 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
}
}

View File

@@ -24,4 +24,4 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
}
}

View File

@@ -18,7 +18,6 @@ using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Configuration.System;
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -248,7 +247,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public AvaloniaList<string> NetworkInterfaceList
{
get => new AvaloniaList<string>(_networkInterfaces.Keys);
get => new(_networkInterfaces.Keys);
}
public KeyboardHotkeys KeyboardHotkeys
@@ -561,7 +560,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_directoryChanged = false;
}
public void RevertIfNotSaved()
private static void RevertIfNotSaved()
{
Program.ReloadConfig();
}
@@ -583,4 +582,4 @@ namespace Ryujinx.Ava.UI.ViewModels
CloseWindow?.Invoke();
}
}
}
}

View File

@@ -1,3 +1,4 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
@@ -16,7 +17,6 @@ using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS;
using Ryujinx.Ui.App.Common;
using System;
using System.Collections.Generic;
@@ -29,17 +29,16 @@ namespace Ryujinx.Ava.UI.ViewModels
{
public class TitleUpdateViewModel : BaseModel
{
public TitleUpdateMetadata _titleUpdateWindowData;
public readonly string _titleUpdateJsonPath;
private VirtualFileSystem _virtualFileSystem { get; }
private ulong _titleId { get; }
private string _titleName { get; }
public TitleUpdateMetadata TitleUpdateWindowData;
public readonly string TitleUpdateJsonPath;
private VirtualFileSystem VirtualFileSystem { get; }
private ulong TitleId { get; }
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
private AvaloniaList<object> _views = new();
private object _selectedUpdate;
private static readonly TitleUpdateMetadataJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
private static readonly TitleUpdateMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
public AvaloniaList<TitleUpdateModel> TitleUpdates
{
@@ -71,27 +70,26 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
{
_virtualFileSystem = virtualFileSystem;
VirtualFileSystem = virtualFileSystem;
_titleId = titleId;
_titleName = titleName;
TitleId = titleId;
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
TitleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
try
{
_titleUpdateWindowData = JsonHelper.DeserializeFromFile(_titleUpdateJsonPath, SerializerContext.TitleUpdateMetadata);
TitleUpdateWindowData = JsonHelper.DeserializeFromFile(TitleUpdateJsonPath, _serializerContext.TitleUpdateMetadata);
}
catch
{
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {TitleId} at {TitleUpdateJsonPath}");
_titleUpdateWindowData = new TitleUpdateMetadata
TitleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "",
Paths = new List<string>()
Paths = new List<string>(),
};
Save();
@@ -102,12 +100,12 @@ namespace Ryujinx.Ava.UI.ViewModels
private void LoadUpdates()
{
foreach (string path in _titleUpdateWindowData.Paths)
foreach (string path in TitleUpdateWindowData.Paths)
{
AddUpdate(path);
}
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == TitleUpdateWindowData.Selected, null);
SelectedUpdate = selected;
@@ -126,7 +124,8 @@ namespace Ryujinx.Ava.UI.ViewModels
{
return -1;
}
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
{
return 1;
}
@@ -163,7 +162,7 @@ namespace Ryujinx.Ava.UI.ViewModels
try
{
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
if (controlNca != null && patchNca != null)
{
@@ -205,17 +204,17 @@ namespace Ryujinx.Ava.UI.ViewModels
{
OpenFileDialog dialog = new()
{
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
AllowMultiple = true,
};
dialog.Filters.Add(new FileDialogFilter
{
Name = "NSP",
Extensions = { "nsp" }
Name = "NSP",
Extensions = { "nsp" },
});
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
string[] files = await dialog.ShowAsync(desktop.MainWindow);
@@ -233,20 +232,20 @@ namespace Ryujinx.Ava.UI.ViewModels
public void Save()
{
_titleUpdateWindowData.Paths.Clear();
_titleUpdateWindowData.Selected = "";
TitleUpdateWindowData.Paths.Clear();
TitleUpdateWindowData.Selected = "";
foreach (TitleUpdateModel update in TitleUpdates)
{
_titleUpdateWindowData.Paths.Add(update.Path);
TitleUpdateWindowData.Paths.Add(update.Path);
if (update == SelectedUpdate)
{
_titleUpdateWindowData.Selected = update.Path;
TitleUpdateWindowData.Selected = update.Path;
}
}
JsonHelper.SerializeToFile(_titleUpdateJsonPath, _titleUpdateWindowData, SerializerContext.TitleUpdateMetadata);
JsonHelper.SerializeToFile(TitleUpdateJsonPath, TitleUpdateWindowData, _serializerContext.TitleUpdateMetadata);
}
}
}
}

View File

@@ -28,7 +28,6 @@ namespace Ryujinx.Ava.UI.ViewModels
private Color _backgroundColor = Colors.White;
private int _selectedIndex;
private byte[] _selectedImage;
public UserFirmwareAvatarSelectorViewModel()
{
@@ -78,11 +77,7 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public byte[] SelectedImage
{
get => _selectedImage;
private set => _selectedImage = value;
}
public byte[] SelectedImage { get; private set; }
private void LoadImagesFromStore()
{
@@ -114,34 +109,32 @@ namespace Ryujinx.Ava.UI.ViewModels
if (!string.IsNullOrWhiteSpace(avatarPath))
{
using (IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open))
using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open);
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
{
Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
foreach (DirectoryEntryEx item in romfs.EnumerateEntries())
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs"))
{
// TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs"))
{
using var file = new UniqueRef<IFile>();
using var file = new UniqueRef<IFile>();
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
using (MemoryStream stream = new())
using (MemoryStream streamPng = new())
{
file.Get.AsStream().CopyTo(stream);
using MemoryStream stream = new();
using MemoryStream streamPng = new();
stream.Position = 0;
file.Get.AsStream().CopyTo(stream);
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
stream.Position = 0;
avatarImage.SaveAsPng(streamPng);
Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
_avatarStore.Add(item.FullPath, streamPng.ToArray());
}
}
avatarImage.SaveAsPng(streamPng);
_avatarStore.Add(item.FullPath, streamPng.ToArray());
}
}
}
@@ -149,82 +142,81 @@ namespace Ryujinx.Ava.UI.ViewModels
private static byte[] DecompressYaz0(Stream stream)
{
using (BinaryReader reader = new(stream))
using BinaryReader reader = new(stream);
reader.ReadInt32(); // Magic
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
stream.Read(input, 0, input.Length);
uint inputOffset = 0;
byte[] output = new byte[decodedLength];
uint outputOffset = 0;
ushort mask = 0;
byte header = 0;
while (outputOffset < decodedLength)
{
reader.ReadInt32(); // Magic
uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
reader.ReadInt64(); // Padding
byte[] input = new byte[stream.Length - stream.Position];
stream.Read(input, 0, input.Length);
uint inputOffset = 0;
byte[] output = new byte[decodedLength];
uint outputOffset = 0;
ushort mask = 0;
byte header = 0;
while (outputOffset < decodedLength)
if ((mask >>= 1) == 0)
{
if ((mask >>= 1) == 0)
header = input[inputOffset++];
mask = 0x80;
}
if ((header & mask) != 0)
{
if (outputOffset == output.Length)
{
header = input[inputOffset++];
mask = 0x80;
break;
}
if ((header & mask) != 0)
{
if (outputOffset == output.Length)
{
break;
}
output[outputOffset++] = input[inputOffset++];
}
else
{
byte byte1 = input[inputOffset++];
byte byte2 = input[inputOffset++];
output[outputOffset++] = input[inputOffset++];
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
uint position = outputOffset - (dist + 1);
uint length = (uint)byte1 >> 4;
if (length == 0)
{
length = (uint)input[inputOffset++] + 0x12;
}
else
{
byte byte1 = input[inputOffset++];
byte byte2 = input[inputOffset++];
length += 2;
}
uint dist = (uint)((byte1 & 0xF) << 8) | byte2;
uint position = outputOffset - (dist + 1);
uint gap = outputOffset - position;
uint nonOverlappingLength = length;
uint length = (uint)byte1 >> 4;
if (length == 0)
{
length = (uint)input[inputOffset++] + 0x12;
}
else
{
length += 2;
}
if (nonOverlappingLength > gap)
{
nonOverlappingLength = gap;
}
uint gap = outputOffset - position;
uint nonOverlappingLength = length;
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
outputOffset += nonOverlappingLength;
position += nonOverlappingLength;
length -= nonOverlappingLength;
if (nonOverlappingLength > gap)
{
nonOverlappingLength = gap;
}
Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength);
outputOffset += nonOverlappingLength;
position += nonOverlappingLength;
length -= nonOverlappingLength;
while (length-- > 0)
{
output[outputOffset++] = output[position++];
}
while (length-- > 0)
{
output[outputOffset++] = output[position++];
}
}
return output;
}
return output;
}
}
}
}

View File

@@ -7,7 +7,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool FirmwareFound
{
get => _firmwareFound;
set
{
_firmwareFound = value;
@@ -15,4 +15,4 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
using Microsoft.IdentityModel.Tokens;
using Ryujinx.Ava.UI.Models;
using System;
using System.Collections.ObjectModel;
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
namespace Ryujinx.Ava.UI.ViewModels
{
@@ -20,6 +20,9 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsEmpty { get; set; }
public void Dispose() { }
public void Dispose()
{
GC.SuppressFinalize(this);
}
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _search;
private ObservableCollection<SaveModel> _saves = new();
private ObservableCollection<SaveModel> _views = new();
private AccountManager _accountManager;
private readonly AccountManager _accountManager;
public string SaveManagerHeading => LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SaveManagerHeading, _accountManager.LastOpenedUser.Name, _accountManager.LastOpenedUser.UserId);
@@ -102,19 +102,16 @@ namespace Ryujinx.Ava.UI.ViewModels
private IComparer<SaveModel> GetComparer()
{
switch (SortIndex)
return SortIndex switch
{
case 0:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
: SortExpressionComparer<SaveModel>.Descending(save => save.Title);
case 1:
return OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
: SortExpressionComparer<SaveModel>.Descending(save => save.Size);
default:
return null;
}
0 => OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Title)
: SortExpressionComparer<SaveModel>.Descending(save => save.Title),
1 => OrderIndex == 0
? SortExpressionComparer<SaveModel>.Ascending(save => save.Size)
: SortExpressionComparer<SaveModel>.Descending(save => save.Size),
_ => null,
};
}
}
}
}