AvatarWindow.cs 9.8 KB

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