UserFirmwareAvatarSelectorViewModel.cs 7.2 KB

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