Ryujinx/Ryujinx.Ava/UI/ViewModels/UserFirmwareAvatarSelectorViewModel.cs
Steveice10 ecee34a50c
Update LibHac to 0.18.0 (#4414)
* Update LibHac to 0.18.0

* Change instance of AsBytes(CreateReadOnlySpan(...)) to AsReadOnlyByteSpan(...)
2023-03-01 23:42:27 -03:00

230 lines
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;
}
}
}
}