934b5a64e5
* Fix redundancies * Add back elses * Loading Screen fixes * Redesign User Profile Manager - Backported long selection bar in Grid/List view not working - Backported UserSelector is jank * Fix SelectionIndicator * Fix DataType * Fix SaveManager bug * Remove debug log * Load saves on UIThread * Reduce UI thread blocking * Fix locale keys * Use block namespaces * Fix close button width * Make UserProfile ordering consistent * Alphabetical order * Adjust layout, remove green circle for blue selector * Fix some inconsistencies * Fix no inital selected profile * Adjust appearance of edit button * Adjust SaveManager * Remove redundant warning dialog * Make firmware avatar selector clearer * View redesign again :hero_depressed: * Consistency adjustments * Adjust margins * Make `UserProfileImageSelector` consistent * Make `UserFirmwareAvatarSelector` consistent * Fix long grid view selector * Switch case * Remove long selection bar Handled in #4178 * Consistency * Started dialog titles * Fixes * Remaining titles * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml Co-authored-by: Mary-nyan <thog@protonmail.com> * Fix build * Hide UserRecoverer if no LostProfiles are found * UserEditor Avatar Placeholder * Watermark + locale adjustment * Border radius * Remove unnecessary styles * Fix firmware avatar image order * Cleanup `ColorPickerButton` * Make `UserId` copy/paste able * Make `FirmwareAvatarSelector` 6 images wide * Make selection bar better * Unsaved changes dialogue * Fix indentation * Remove extra check * Address suggestions * Reorganise - Remove unused views - Rename views to match convention - Fix weird namespacing * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml Co-authored-by: Ac_K <Acoustik666@gmail.com> * UserRecovererView empty placeholder * Update Ryujinx.Ava/UI/Views/User/UserSelectorView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserSaveManagerView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserRecovererView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Views/User/UserFirmwareAvatarSelectorView.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Models/UserProfile.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Update Ryujinx.Ava/UI/Controls/NavigationDialogHost.axaml.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Remove AddModel * Update Ryujinx.Ava/Assets/Locales/en_US.json Co-authored-by: Ac_K <Acoustik666@gmail.com> * Fix bug Co-authored-by: Mary-nyan <thog@protonmail.com> Co-authored-by: Ac_K <Acoustik666@gmail.com>
230 lines
No EOL
7.2 KiB
C#
230 lines
No EOL
7.2 KiB
C#
using Avalonia.Media;
|
|
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.PixelFormats;
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using Color = Avalonia.Media.Color;
|
|
|
|
namespace Ryujinx.Ava.UI.ViewModels
|
|
{
|
|
internal class UserFirmwareAvatarSelectorViewModel : BaseModel
|
|
{
|
|
private static readonly Dictionary<string, byte[]> _avatarStore = new();
|
|
|
|
private ObservableCollection<ProfileImageModel> _images;
|
|
private Color _backgroundColor = Colors.White;
|
|
|
|
private int _selectedIndex;
|
|
private byte[] _selectedImage;
|
|
|
|
public UserFirmwareAvatarSelectorViewModel()
|
|
{
|
|
_images = new ObservableCollection<ProfileImageModel>();
|
|
|
|
LoadImagesFromStore();
|
|
}
|
|
|
|
public Color BackgroundColor
|
|
{
|
|
get => _backgroundColor;
|
|
set
|
|
{
|
|
_backgroundColor = value;
|
|
OnPropertyChanged();
|
|
ChangeImageBackground();
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<ProfileImageModel> Images
|
|
{
|
|
get => _images;
|
|
set
|
|
{
|
|
_images = 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;
|
|
}
|
|
|
|
private void LoadImagesFromStore()
|
|
{
|
|
Images.Clear();
|
|
|
|
foreach (var image in _avatarStore)
|
|
{
|
|
Images.Add(new ProfileImageModel(image.Key, image.Value));
|
|
}
|
|
}
|
|
|
|
private void ChangeImageBackground()
|
|
{
|
|
foreach (var image in Images)
|
|
{
|
|
image.BackgroundColor = new SolidColorBrush(BackgroundColor);
|
|
}
|
|
}
|
|
|
|
public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
|
|
{
|
|
if (_avatarStore.Count > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} |