deb99d2cae
* avalonia part 1 * remove vulkan ui backend * move ui common files to ui common project * get name for oading screen from device * rebase. * review 1 * review 1.1 * review * cleanup * addressed review * use cancellation token * review * review * rebased * cancel library loading when closing window * remove star image, use fonticon instead * delete render control frame buffer when game ends. change position of fav star * addressed @Thog review * ensure the right ui is downloaded in updates * fix crash when showing not supported dialog during controller request * add prefix to artifact names * Auto-format Avalonia project * Fix input * Fix build, simplify app disposal * remove nv stutter thread * addressed review * add missing change * maintain window size if new size is zero length * add game, handheld, docked to local * reverse scale main window * Update de_DE.json * Update de_DE.json * Update de_DE.json * Update italian json * Update it_IT.json * let render timer poll with no wait * remove unused code * more unused code * enabled tiered compilation and trimming * check if window event is not closed before signaling * fix atmospher case * locale fix * locale fix * remove explicit tiered compilation declarations * Remove ) it_IT.json * Remove ) de_DE.json * Update it_IT.json * Update pt_BR locale with latest strings * Remove ')' * add more strings to locale * update locale * remove extra slash * remove extra slash * set firmware version to 0 if key's not found * fix * revert timer changes * lock on object instead * Update it_IT.json * remove unused method * add load screen text to locale * drop swap event * Update de_DE.json * Update de_DE.json * do null check when stopping emulator * Update de_DE.json * Create tr_TR.json * Add tr_TR * Add tr_TR + Turkish * Update it_IT.json * Update Ryujinx.Ava/Input/AvaloniaMappingHelper.cs Co-authored-by: Ac_K <Acoustik666@gmail.com> * Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * Apply suggestions from code review Co-authored-by: Ac_K <Acoustik666@gmail.com> * addressed review * Update Ryujinx.Ava/Ui/Backend/OpenGl/OpenGlRenderTarget.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * use avalonia's inbuilt renderer on linux * removed whitespace * workaround for queue render crash with vsync off * drop custom backend * format files * fix not closing issue * remove warnings * rebase * update avalonia library * Reposition the Text and Button on About Page * Assign build version * Remove appveyor text Co-authored-by: gdk <gab.dark.100@gmail.com> Co-authored-by: Niwu34 <67392333+Niwu34@users.noreply.github.com> Co-authored-by: Antonio Brugnolo <36473846+AntoSkate@users.noreply.github.com> Co-authored-by: aegiff <99728970+aegiff@users.noreply.github.com> Co-authored-by: Ac_K <Acoustik666@gmail.com> Co-authored-by: MostlyWhat <78652091+MostlyWhat@users.noreply.github.com>
293 lines
10 KiB
C#
293 lines
10 KiB
C#
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.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 Dictionary<string, byte[]> _avatarDict = new Dictionary<string, byte[]>();
|
|
|
|
private ListStore _listStore;
|
|
private IconView _iconView;
|
|
private 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);
|
|
|
|
VBox vbox = new VBox(false, 0);
|
|
Add(vbox);
|
|
|
|
ScrolledWindow scrolledWindow = new ScrolledWindow
|
|
{
|
|
ShadowType = ShadowType.EtchedIn
|
|
};
|
|
scrolledWindow.SetPolicy(PolicyType.Automatic, PolicyType.Automatic);
|
|
|
|
HBox hbox = new HBox(false, 0);
|
|
|
|
Button chooseButton = new Button()
|
|
{
|
|
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 Button()
|
|
{
|
|
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);
|
|
_iconView.ItemWidth = 64;
|
|
_iconView.ItemPadding = 10;
|
|
_iconView.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 Nca(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 = new MemoryStream())
|
|
using (MemoryStream streamPng = new MemoryStream())
|
|
{
|
|
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 int[] { 0 }));
|
|
}
|
|
|
|
private byte[] ProcessImage(byte[] data)
|
|
{
|
|
using (MemoryStream streamJpg = new MemoryStream())
|
|
{
|
|
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 ColorChooserDialog("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 BinaryReader(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;
|
|
}
|
|
}
|
|
}
|
|
} |