AvatarWindow.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. using Gtk;
  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.FsSystem;
  8. using LibHac.Tools.FsSystem.NcaUtils;
  9. using Ryujinx.Common.Memory;
  10. using Ryujinx.HLE.FileSystem;
  11. using Ryujinx.UI.Common.Configuration;
  12. using SixLabors.ImageSharp;
  13. using SixLabors.ImageSharp.Formats.Png;
  14. using SixLabors.ImageSharp.PixelFormats;
  15. using SixLabors.ImageSharp.Processing;
  16. using System;
  17. using System.Buffers.Binary;
  18. using System.Collections.Generic;
  19. using System.IO;
  20. using System.Reflection;
  21. using Image = SixLabors.ImageSharp.Image;
  22. namespace Ryujinx.UI.Windows
  23. {
  24. public class AvatarWindow : Window
  25. {
  26. public byte[] SelectedProfileImage;
  27. public bool NewUser;
  28. private static readonly Dictionary<string, byte[]> _avatarDict = new();
  29. private readonly ListStore _listStore;
  30. private readonly IconView _iconView;
  31. private readonly Button _setBackgroungColorButton;
  32. private Gdk.RGBA _backgroundColor;
  33. public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar")
  34. {
  35. Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
  36. CanFocus = false;
  37. Resizable = false;
  38. Modal = true;
  39. TypeHint = Gdk.WindowTypeHint.Dialog;
  40. SetDefaultSize(740, 400);
  41. SetPosition(WindowPosition.Center);
  42. Box vbox = new(Orientation.Vertical, 0);
  43. Add(vbox);
  44. ScrolledWindow scrolledWindow = new()
  45. {
  46. ShadowType = ShadowType.EtchedIn,
  47. };
  48. scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
  49. Box hbox = new(Orientation.Horizontal, 0);
  50. Button chooseButton = new()
  51. {
  52. Label = "Choose",
  53. CanFocus = true,
  54. ReceivesDefault = true,
  55. };
  56. chooseButton.Clicked += ChooseButton_Pressed;
  57. _setBackgroungColorButton = new Button()
  58. {
  59. Label = "Set Background Color",
  60. CanFocus = true,
  61. };
  62. _setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed;
  63. _backgroundColor.Red = 1;
  64. _backgroundColor.Green = 1;
  65. _backgroundColor.Blue = 1;
  66. _backgroundColor.Alpha = 1;
  67. Button closeButton = new()
  68. {
  69. Label = "Close",
  70. CanFocus = true,
  71. };
  72. closeButton.Clicked += CloseButton_Pressed;
  73. vbox.PackStart(scrolledWindow, true, true, 0);
  74. hbox.PackStart(chooseButton, true, true, 0);
  75. hbox.PackStart(_setBackgroungColorButton, true, true, 0);
  76. hbox.PackStart(closeButton, true, true, 0);
  77. vbox.PackStart(hbox, false, false, 0);
  78. _listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf));
  79. _listStore.SetSortColumnId(0, SortType.Ascending);
  80. _iconView = new IconView(_listStore)
  81. {
  82. ItemWidth = 64,
  83. ItemPadding = 10,
  84. PixbufColumn = 1,
  85. };
  86. _iconView.SelectionChanged += IconView_SelectionChanged;
  87. scrolledWindow.Add(_iconView);
  88. _iconView.GrabFocus();
  89. ProcessAvatars();
  90. ShowAll();
  91. }
  92. public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
  93. {
  94. if (_avatarDict.Count > 0)
  95. {
  96. return;
  97. }
  98. string contentPath = contentManager.GetInstalledContentPath(0x010000000000080A, StorageId.BuiltInSystem, NcaContentType.Data);
  99. string avatarPath = VirtualFileSystem.SwitchPathToSystemPath(contentPath);
  100. if (!string.IsNullOrWhiteSpace(avatarPath))
  101. {
  102. using IStorage ncaFileStream = new LocalStorage(avatarPath, FileAccess.Read, FileMode.Open);
  103. Nca nca = new(virtualFileSystem.KeySet, ncaFileStream);
  104. IFileSystem romfs = nca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.ErrorOnInvalid);
  105. foreach (var item in romfs.EnumerateEntries())
  106. {
  107. // TODO: Parse DatabaseInfo.bin and table.bin files for more accuracy.
  108. if (item.Type == DirectoryEntryType.File && item.FullPath.Contains("chara") && item.FullPath.Contains("szs"))
  109. {
  110. using var file = new UniqueRef<IFile>();
  111. romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
  112. using MemoryStream stream = MemoryStreamManager.Shared.GetStream();
  113. using MemoryStream streamPng = MemoryStreamManager.Shared.GetStream();
  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. _avatarDict.Add(item.FullPath, streamPng.ToArray());
  119. }
  120. }
  121. }
  122. }
  123. private void ProcessAvatars()
  124. {
  125. _listStore.Clear();
  126. foreach (var avatar in _avatarDict)
  127. {
  128. _listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96));
  129. }
  130. _iconView.SelectPath(new TreePath(new[] { 0 }));
  131. }
  132. private byte[] ProcessImage(byte[] data)
  133. {
  134. using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
  135. Image avatarImage = Image.Load(data, new PngDecoder());
  136. avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
  137. (byte)(_backgroundColor.Red * 255),
  138. (byte)(_backgroundColor.Green * 255),
  139. (byte)(_backgroundColor.Blue * 255),
  140. (byte)(_backgroundColor.Alpha * 255)
  141. )));
  142. avatarImage.SaveAsJpeg(streamJpg);
  143. return streamJpg.ToArray();
  144. }
  145. private void CloseButton_Pressed(object sender, EventArgs e)
  146. {
  147. SelectedProfileImage = null;
  148. Close();
  149. }
  150. private void IconView_SelectionChanged(object sender, EventArgs e)
  151. {
  152. if (_iconView.SelectedItems.Length > 0)
  153. {
  154. _listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]);
  155. SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]);
  156. }
  157. }
  158. private void SetBackgroungColorButton_Pressed(object sender, EventArgs e)
  159. {
  160. using ColorChooserDialog colorChooserDialog = new("Set Background Color", this);
  161. colorChooserDialog.UseAlpha = false;
  162. colorChooserDialog.Rgba = _backgroundColor;
  163. if (colorChooserDialog.Run() == (int)ResponseType.Ok)
  164. {
  165. _backgroundColor = colorChooserDialog.Rgba;
  166. ProcessAvatars();
  167. }
  168. colorChooserDialog.Hide();
  169. }
  170. private void ChooseButton_Pressed(object sender, EventArgs e)
  171. {
  172. Close();
  173. }
  174. private static byte[] DecompressYaz0(Stream stream)
  175. {
  176. using BinaryReader reader = new(stream);
  177. reader.ReadInt32(); // Magic
  178. uint decodedLength = BinaryPrimitives.ReverseEndianness(reader.ReadUInt32());
  179. reader.ReadInt64(); // Padding
  180. byte[] input = new byte[stream.Length - stream.Position];
  181. stream.ReadExactly(input, 0, input.Length);
  182. long inputOffset = 0;
  183. byte[] output = new byte[decodedLength];
  184. long outputOffset = 0;
  185. ushort mask = 0;
  186. byte header = 0;
  187. while (outputOffset < decodedLength)
  188. {
  189. if ((mask >>= 1) == 0)
  190. {
  191. header = input[inputOffset++];
  192. mask = 0x80;
  193. }
  194. if ((header & mask) > 0)
  195. {
  196. if (outputOffset == output.Length)
  197. {
  198. break;
  199. }
  200. output[outputOffset++] = input[inputOffset++];
  201. }
  202. else
  203. {
  204. byte byte1 = input[inputOffset++];
  205. byte byte2 = input[inputOffset++];
  206. int dist = ((byte1 & 0xF) << 8) | byte2;
  207. int position = (int)outputOffset - (dist + 1);
  208. int length = byte1 >> 4;
  209. if (length == 0)
  210. {
  211. length = input[inputOffset++] + 0x12;
  212. }
  213. else
  214. {
  215. length += 2;
  216. }
  217. while (length-- > 0)
  218. {
  219. output[outputOffset++] = output[position++];
  220. }
  221. }
  222. }
  223. return output;
  224. }
  225. }
  226. }