UserFirmwareAvatarSelectorViewModel.cs
1 using Avalonia.Media; 2 using LibHac.Common; 3 using LibHac.Fs; 4 using LibHac.Fs.Fsa; 5 using LibHac.FsSystem; 6 using LibHac.Ncm; 7 using LibHac.Tools.Fs; 8 using LibHac.Tools.FsSystem; 9 using LibHac.Tools.FsSystem.NcaUtils; 10 using Ryujinx.Ava.UI.Models; 11 using Ryujinx.HLE.FileSystem; 12 using SkiaSharp; 13 using System; 14 using System.Buffers.Binary; 15 using System.Collections.Generic; 16 using System.Collections.ObjectModel; 17 using System.IO; 18 using Color = Avalonia.Media.Color; 19 using Image = SkiaSharp.SKImage; 20 21 namespace Ryujinx.Ava.UI.ViewModels 22 { 23 internal class UserFirmwareAvatarSelectorViewModel : BaseModel 24 { 25 private static readonly Dictionary<string, byte[]> _avatarStore = new(); 26 27 private ObservableCollection<ProfileImageModel> _images; 28 private Color _backgroundColor = Colors.White; 29 30 private int _selectedIndex; 31 32 public UserFirmwareAvatarSelectorViewModel() 33 { 34 _images = new ObservableCollection<ProfileImageModel>(); 35 36 LoadImagesFromStore(); 37 } 38 39 public Color BackgroundColor 40 { 41 get => _backgroundColor; 42 set 43 { 44 _backgroundColor = value; 45 OnPropertyChanged(); 46 ChangeImageBackground(); 47 } 48 } 49 50 public ObservableCollection<ProfileImageModel> Images 51 { 52 get => _images; 53 set 54 { 55 _images = value; 56 OnPropertyChanged(); 57 } 58 } 59 60 public int SelectedIndex 61 { 62 get => _selectedIndex; 63 set 64 { 65 _selectedIndex = value; 66 67 if (_selectedIndex == -1) 68 { 69 SelectedImage = null; 70 } 71 else 72 { 73 SelectedImage = _images[_selectedIndex].Data; 74 } 75 76 OnPropertyChanged(); 77 } 78 } 79 80 public byte[] SelectedImage { get; private set; } 81 82 private void LoadImagesFromStore() 83 { 84 Images.Clear(); 85 86 foreach (var image in _avatarStore) 87 { 88 Images.Add(new ProfileImageModel(image.Key, image.Value)); 89 } 90 } 91 92 private void ChangeImageBackground() 93 { 94 foreach (var image in Images) 95 { 96 image.BackgroundColor = new SolidColorBrush(BackgroundColor); 97 } 98 } 99 100 public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem) 101 { 102 if (_avatarStore.Count > 0) 103 { 104 return; 105 } 106 107 string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data); 108 string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath); 109 110 if (!string.IsNullOrWhiteSpace(avatarPath)) 111 { 112 using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open); 113 114 Nca nca = new(virtualFileSystem.KeySet, ncaFileStream); 115 IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid); 116 117 foreach (DirectoryEntryEx item in romfs.EnumerateEntries()) 118 { 119 // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy. 120 if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs")) 121 { 122 using var file = new UniqueRef<IFile>(); 123 124 romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); 125 126 using MemoryStream stream = new(); 127 using MemoryStream streamPng = new(); 128 129 file.Get.AsStream().CopyTo(stream); 130 131 stream.Position = 0; 132 133 Image avatarImage = Image.FromPixelCopy(new SKImageInfo(256, 256, SKColorType.Rgba8888, SKAlphaType.Premul), DecompressYaz0(stream)); 134 135 using (SKData data = avatarImage.Encode(SKEncodedImageFormat.Png, 100)) 136 { 137 data.SaveTo(streamPng); 138 } 139 140 _avatarStore.Add(item.FullPath, streamPng.ToArray()); 141 } 142 } 143 } 144 } 145 146 private static byte[] DecompressYaz0(Stream stream) 147 { 148 using BinaryReader reader = new(stream); 149 150 reader.ReadInt32(); // Magic 151 152 uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32()); 153 154 reader.ReadInt64(); // Padding 155 156 byte[] input = new byte[stream.Length - stream.Position]; 157 stream.ReadExactly(input, 0, input.Length); 158 159 uint inputOffset = 0; 160 161 byte[] output = new byte[decodedLength]; 162 uint outputOffset = 0; 163 164 ushort mask = 0; 165 byte header = 0; 166 167 while (outputOffset < decodedLength) 168 { 169 if ((mask >>= 1) == 0) 170 { 171 header = input[inputOffset++]; 172 mask = 0x80; 173 } 174 175 if ((header & mask) != 0) 176 { 177 if (outputOffset == output.Length) 178 { 179 break; 180 } 181 182 output[outputOffset++] = input[inputOffset++]; 183 } 184 else 185 { 186 byte byte1 = input[inputOffset++]; 187 byte byte2 = input[inputOffset++]; 188 189 uint dist = (uint)((byte1 & 0xF) << 8) | byte2; 190 uint position = outputOffset - (dist + 1); 191 192 uint length = (uint)byte1 >> 4; 193 if (length == 0) 194 { 195 length = (uint)input[inputOffset++] + 0x12; 196 } 197 else 198 { 199 length += 2; 200 } 201 202 uint gap = outputOffset - position; 203 uint nonOverlappingLength = length; 204 205 if (nonOverlappingLength > gap) 206 { 207 nonOverlappingLength = gap; 208 } 209 210 Buffer.BlockCopy(output, (int)position, output, (int)outputOffset, (int)nonOverlappingLength); 211 outputOffset += nonOverlappingLength; 212 position += nonOverlappingLength; 213 length -= nonOverlappingLength; 214 215 while (length-- > 0) 216 { 217 output[outputOffset++] = output[position++]; 218 } 219 } 220 } 221 222 return output; 223 } 224 } 225 }