aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx/UI/Windows/AvatarWindow.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx/UI/Windows/AvatarWindow.cs')
-rw-r--r--src/Ryujinx/UI/Windows/AvatarWindow.cs291
1 files changed, 291 insertions, 0 deletions
diff --git a/src/Ryujinx/UI/Windows/AvatarWindow.cs b/src/Ryujinx/UI/Windows/AvatarWindow.cs
new file mode 100644
index 00000000..7cddc362
--- /dev/null
+++ b/src/Ryujinx/UI/Windows/AvatarWindow.cs
@@ -0,0 +1,291 @@
+using Gtk;
+using LibHac.Common;
+using LibHac.Fs;
+using LibHac.Fs.Fsa;
+using LibHac.FsSystem;
+using LibHac.Ncm;
+using LibHac.Tools.FsSystem;
+using LibHac.Tools.FsSystem.NcaUtils;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.FileSystem;
+using Ryujinx.UI.Common.Configuration;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using Image = SixLabors.ImageSharp.Image;
+
+namespace Ryujinx.UI.Windows
+{
+ public class AvatarWindow : Window
+ {
+ public byte[] SelectedProfileImage;
+ public bool NewUser;
+
+ private static readonly Dictionary<string, byte[]> _avatarDict = new();
+
+ private readonly ListStore _listStore;
+ private readonly IconView _iconView;
+ private readonly Button _setBackgroungColorButton;
+ private Gdk.RGBA _backgroundColor;
+
+ public AvatarWindow() : base($"Ryujinx {Program.Version} - Manage Accounts - Avatar")
+ {
+ Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
+
+ CanFocus = false;
+ Resizable = false;
+ Modal = true;
+ TypeHint = Gdk.WindowTypeHint.Dialog;
+
+ SetDefaultSize(740, 400);
+ SetPosition(WindowPosition.Center);
+
+ Box vbox = new(Orientation.Vertical, 0);
+ Add(vbox);
+
+ ScrolledWindow scrolledWindow = new()
+ {
+ ShadowType = ShadowType.EtchedIn,
+ };
+ scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
+
+ Box hbox = new(Orientation.Horizontal, 0);
+
+ Button chooseButton = new()
+ {
+ Label = "Choose",
+ CanFocus = true,
+ ReceivesDefault = true,
+ };
+ chooseButton.Clicked += ChooseButton_Pressed;
+
+ _setBackgroungColorButton = new Button()
+ {
+ Label = "Set Background Color",
+ CanFocus = true,
+ };
+ _setBackgroungColorButton.Clicked += SetBackgroungColorButton_Pressed;
+
+ _backgroundColor.Red = 1;
+ _backgroundColor.Green = 1;
+ _backgroundColor.Blue = 1;
+ _backgroundColor.Alpha = 1;
+
+ Button closeButton = new()
+ {
+ Label = "Close",
+ CanFocus = true,
+ };
+ closeButton.Clicked += CloseButton_Pressed;
+
+ vbox.PackStart(scrolledWindow, true, true, 0);
+ hbox.PackStart(chooseButton, true, true, 0);
+ hbox.PackStart(_setBackgroungColorButton, true, true, 0);
+ hbox.PackStart(closeButton, true, true, 0);
+ vbox.PackStart(hbox, false, false, 0);
+
+ _listStore = new ListStore(typeof(string), typeof(Gdk.Pixbuf));
+ _listStore.SetSortColumnId(0, SortType.Ascending);
+
+ _iconView = new IconView(_listStore)
+ {
+ ItemWidth = 64,
+ ItemPadding = 10,
+ PixbufColumn = 1,
+ };
+
+ _iconView.SelectionChanged += IconView_SelectionChanged;
+
+ scrolledWindow.Add(_iconView);
+
+ _iconView.GrabFocus();
+
+ ProcessAvatars();
+
+ ShowAll();
+ }
+
+ public static void PreloadAvatars(ContentManager contentManager, VirtualFileSystem virtualFileSystem)
+ {
+ if (_avatarDict.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 (var 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 = MemoryStreamManager.Shared.GetStream();
+ using MemoryStream streamPng = MemoryStreamManager.Shared.GetStream();
+ file.Get.AsStream().CopyTo(stream);
+
+ stream.Position = 0;
+
+ Image avatarImage = Image.LoadPixelData<Rgba32>(DecompressYaz0(stream), 256, 256);
+
+ avatarImage.SaveAsPng(streamPng);
+
+ _avatarDict.Add(item.FullPath, streamPng.ToArray());
+ }
+ }
+ }
+ }
+
+ private void ProcessAvatars()
+ {
+ _listStore.Clear();
+
+ foreach (var avatar in _avatarDict)
+ {
+ _listStore.AppendValues(avatar.Key, new Gdk.Pixbuf(ProcessImage(avatar.Value), 96, 96));
+ }
+
+ _iconView.SelectPath(new TreePath(new[] { 0 }));
+ }
+
+ private byte[] ProcessImage(byte[] data)
+ {
+ using MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream();
+
+ Image avatarImage = Image.Load(data, new PngDecoder());
+
+ avatarImage.Mutate(x => x.BackgroundColor(new Rgba32(
+ (byte)(_backgroundColor.Red * 255),
+ (byte)(_backgroundColor.Green * 255),
+ (byte)(_backgroundColor.Blue * 255),
+ (byte)(_backgroundColor.Alpha * 255)
+ )));
+ avatarImage.SaveAsJpeg(streamJpg);
+
+ return streamJpg.ToArray();
+ }
+
+ private void CloseButton_Pressed(object sender, EventArgs e)
+ {
+ SelectedProfileImage = null;
+
+ Close();
+ }
+
+ private void IconView_SelectionChanged(object sender, EventArgs e)
+ {
+ if (_iconView.SelectedItems.Length > 0)
+ {
+ _listStore.GetIter(out TreeIter iter, _iconView.SelectedItems[0]);
+
+ SelectedProfileImage = ProcessImage(_avatarDict[(string)_listStore.GetValue(iter, 0)]);
+ }
+ }
+
+ private void SetBackgroungColorButton_Pressed(object sender, EventArgs e)
+ {
+ using ColorChooserDialog colorChooserDialog = new("Set Background Color", this);
+
+ colorChooserDialog.UseAlpha = false;
+ colorChooserDialog.Rgba = _backgroundColor;
+
+ if (colorChooserDialog.Run() == (int)ResponseType.Ok)
+ {
+ _backgroundColor = colorChooserDialog.Rgba;
+
+ ProcessAvatars();
+ }
+
+ colorChooserDialog.Hide();
+ }
+
+ private void ChooseButton_Pressed(object sender, EventArgs e)
+ {
+ Close();
+ }
+
+ 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);
+
+ long inputOffset = 0;
+
+ byte[] output = new byte[decodedLength];
+ long 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++];
+
+ int dist = ((byte1 & 0xF) << 8) | byte2;
+ int position = (int)outputOffset - (dist + 1);
+
+ int length = byte1 >> 4;
+ if (length == 0)
+ {
+ length = input[inputOffset++] + 0x12;
+ }
+ else
+ {
+ length += 2;
+ }
+
+ while (length-- > 0)
+ {
+ output[outputOffset++] = output[position++];
+ }
+ }
+ }
+
+ return output;
+ }
+ }
+}