From ff76997b0b186272000ef0f2e84ca4b81974105b Mon Sep 17 00:00:00 2001 From: Anna Clemens Date: Sat, 31 Oct 2020 21:31:10 -0400 Subject: [PATCH] chore(desktop): add initial code --- XIVChat Desktop/App.xaml | 17 +- XIVChat Desktop/App.xaml.cs | 75 +++- XIVChat Desktop/ConfigWindow.xaml | 77 ++++ XIVChat Desktop/ConfigWindow.xaml.cs | 51 +++ XIVChat Desktop/Configuration.cs | 192 ++++++++++ XIVChat Desktop/ConnectDialog.xaml | 43 +++ XIVChat Desktop/ConnectDialog.xaml.cs | 42 +++ XIVChat Desktop/Connection.cs | 267 ++++++++++++++ XIVChat Desktop/Controls/SavedServers.xaml | 51 +++ XIVChat Desktop/Controls/SavedServers.xaml.cs | 73 ++++ XIVChat Desktop/Converters.cs | 33 ++ XIVChat Desktop/Filters.cs | 347 ++++++++++++++++++ XIVChat Desktop/MainWindow.xaml | 108 +++++- XIVChat Desktop/MainWindow.xaml.cs | 129 ++++++- XIVChat Desktop/ManageServer.xaml | 78 ++++ XIVChat Desktop/ManageServer.xaml.cs | 64 ++++ XIVChat Desktop/ManageTab.xaml | 34 ++ XIVChat Desktop/ManageTab.xaml.cs | 116 ++++++ XIVChat Desktop/ManageTabs.xaml | 31 ++ XIVChat Desktop/ManageTabs.xaml.cs | 59 +++ XIVChat Desktop/MessageFormatter.cs | 213 +++++++++++ .../Properties/DesignTimeResources.xaml | 7 + .../Properties/Resources.Designer.cs | 72 ++++ XIVChat Desktop/Properties/Resources.resx | 123 +++++++ .../Resources/fonticon_ps4.tex.png | Bin 0 -> 51083 bytes XIVChat Desktop/TrustDialog.xaml | 116 ++++++ XIVChat Desktop/TrustDialog.xaml.cs | 85 +++++ XIVChat Desktop/XIVChat Desktop.csproj | 61 ++- XIVChat Desktop/fonts/ffxiv.ttf | Bin 0 -> 60613 bytes XIVChat Desktop/ic_launcher-playstore.png | Bin 0 -> 15470 bytes XIVChat.sln | 2 +- 31 files changed, 2532 insertions(+), 34 deletions(-) create mode 100644 XIVChat Desktop/ConfigWindow.xaml create mode 100644 XIVChat Desktop/ConfigWindow.xaml.cs create mode 100644 XIVChat Desktop/Configuration.cs create mode 100644 XIVChat Desktop/ConnectDialog.xaml create mode 100644 XIVChat Desktop/ConnectDialog.xaml.cs create mode 100644 XIVChat Desktop/Connection.cs create mode 100644 XIVChat Desktop/Controls/SavedServers.xaml create mode 100644 XIVChat Desktop/Controls/SavedServers.xaml.cs create mode 100644 XIVChat Desktop/Converters.cs create mode 100644 XIVChat Desktop/Filters.cs create mode 100644 XIVChat Desktop/ManageServer.xaml create mode 100644 XIVChat Desktop/ManageServer.xaml.cs create mode 100644 XIVChat Desktop/ManageTab.xaml create mode 100644 XIVChat Desktop/ManageTab.xaml.cs create mode 100644 XIVChat Desktop/ManageTabs.xaml create mode 100644 XIVChat Desktop/ManageTabs.xaml.cs create mode 100644 XIVChat Desktop/MessageFormatter.cs create mode 100644 XIVChat Desktop/Properties/DesignTimeResources.xaml create mode 100644 XIVChat Desktop/Properties/Resources.Designer.cs create mode 100644 XIVChat Desktop/Properties/Resources.resx create mode 100644 XIVChat Desktop/Resources/fonticon_ps4.tex.png create mode 100644 XIVChat Desktop/TrustDialog.xaml create mode 100644 XIVChat Desktop/TrustDialog.xaml.cs create mode 100644 XIVChat Desktop/fonts/ffxiv.ttf create mode 100644 XIVChat Desktop/ic_launcher-playstore.png diff --git a/XIVChat Desktop/App.xaml b/XIVChat Desktop/App.xaml index ceab6dd..bffbd17 100644 --- a/XIVChat Desktop/App.xaml +++ b/XIVChat Desktop/App.xaml @@ -2,8 +2,19 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:XIVChat_Desktop" - StartupUri="MainWindow.xaml"> + xmlns:ui="http://schemas.modernwpf.com/2019" + Startup="Application_Startup"> - + + + + + + + + + + + - + \ No newline at end of file diff --git a/XIVChat Desktop/App.xaml.cs b/XIVChat Desktop/App.xaml.cs index e1eeccd..6458f98 100644 --- a/XIVChat Desktop/App.xaml.cs +++ b/XIVChat Desktop/App.xaml.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; +using System.ComponentModel; using System.Threading.Tasks; using System.Windows; @@ -10,6 +7,74 @@ namespace XIVChat_Desktop { /// /// Interaction logic for App.xaml /// - public partial class App : Application { + public partial class App : INotifyPropertyChanged { + public MainWindow Window { get; private set; } = null!; + public Configuration Config { get; private set; } = null!; + + public string? LastHost { get; set; } + + private Connection? connection; + + public Connection? Connection { + get => this.connection; + set { + this.connection = value; + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Connection))); + this.ConnectionStatusChanged(); + } + } + + public bool Connected => this.Connection != null; + public bool Disconnected => this.Connection == null; + + public event PropertyChangedEventHandler? PropertyChanged; + + private void Application_Startup(object sender, StartupEventArgs e) { + try { + this.Config = Configuration.Load() ?? new Configuration(); + } catch (Exception) { + this.Config = new Configuration(); + } + + try { + this.Config.Save(); + } catch (Exception) { + // TODO + } + + var wnd = new MainWindow(); + this.Window = wnd; + + // I guess this gets initialised where you call it the first time, so initialise it on the UI thread + this.Dispatcher.Invoke(() => { }); + + wnd.Show(); + + // initialise a config window to apply all our settings + _ = new ConfigWindow(wnd, this.Config); + } + + private void ConnectionStatusChanged() { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Connected))); + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Disconnected))); + } + + public void Connect(string host, ushort port) { + if (this.Connected) { + return; + } + + this.Connection = new Connection(this, host, port); + Task.Run(this.Connection.Connect); + } + + public void Disconnect() { + if (!this.Connected) { + return; + } + + this.Connection?.Disconnect(); + this.Connection = null; + } } } diff --git a/XIVChat Desktop/ConfigWindow.xaml b/XIVChat Desktop/ConfigWindow.xaml new file mode 100644 index 0000000..231f5be --- /dev/null +++ b/XIVChat Desktop/ConfigWindow.xaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XIVChat Desktop/ConfigWindow.xaml.cs b/XIVChat Desktop/ConfigWindow.xaml.cs new file mode 100644 index 0000000..9ec34f0 --- /dev/null +++ b/XIVChat Desktop/ConfigWindow.xaml.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using System.Windows; +using System.Windows.Input; + +namespace XIVChat_Desktop { + /// + /// Interaction logic for ConfigWindow.xaml + /// + public partial class ConfigWindow { + public Configuration Config { get; private set; } + + public ConfigWindow(Window owner, Configuration config) { + this.Owner = owner; + this.Config = config; + + this.InitializeComponent(); + this.DataContext = this; + } + + private void AlwaysOnTop_Checked(object? sender, RoutedEventArgs e) { + this.SetAlwaysOnTop(true); + } + + private void AlwaysOnTop_Unchecked(object? sender, RoutedEventArgs e) { + this.SetAlwaysOnTop(false); + } + + private void SetAlwaysOnTop(bool onTop) { + this.Owner.Topmost = onTop; + this.Config.AlwaysOnTop = onTop; + } + + private void Save_Click(object? sender, RoutedEventArgs e) { + this.Config.Save(); + } + + private void SavedServers_ItemDoubleClick(SavedServer? server) { + new ManageServer(this, server).ShowDialog(); + } + + private void ConfigWindow_OnContentRendered(object? sender, EventArgs e) { + this.InvalidateVisual(); + } + + private void NumericInputFilter(object sender, TextCompositionEventArgs e) { + var allDigits = e.Text.All(c => char.IsDigit(c)); + e.Handled = !allDigits; + } + } +} diff --git a/XIVChat Desktop/Configuration.cs b/XIVChat Desktop/Configuration.cs new file mode 100644 index 0000000..74362ce --- /dev/null +++ b/XIVChat Desktop/Configuration.cs @@ -0,0 +1,192 @@ +using Newtonsoft.Json; +using Sodium; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using XIVChatCommon; + +namespace XIVChat_Desktop { + [JsonObject] + public class Configuration : INotifyPropertyChanged { + public event PropertyChangedEventHandler? PropertyChanged; + + public KeyPair KeyPair { get; set; } = PublicKeyBox.GenerateKeyPair(); + + public ObservableCollection Servers { get; set; } = new ObservableCollection(); + public HashSet TrustedKeys { get; set; } = new HashSet(); + + public ObservableCollection Tabs { get; set; } = Tab.Defaults(); + + public bool AlwaysOnTop { get; set; } + + private double fontSize = 14d; + + public double FontSize { + get => this.fontSize; + set { + this.fontSize = value; + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.FontSize))); + } + } + + public ushort BacklogMessages { get; set; } = 500; + + #region io + + private static string FilePath() => Path.Join( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "XIVChat for Windows", + "config.json" + ); + + public static Configuration? Load() { + var path = FilePath(); + if (!File.Exists(path)) { + return null; + } + + using var reader = File.OpenText(path); + using var json = new JsonTextReader(reader); + + var serializer = new JsonSerializer { + ObjectCreationHandling = ObjectCreationHandling.Replace, + }; + return serializer.Deserialize(json); + } + + public void Save() { + var path = FilePath(); + if (!File.Exists(path)) { + var dir = Path.GetDirectoryName(path); + Directory.CreateDirectory(dir); + } + + using var file = File.CreateText(path); + using var json = new JsonTextWriter(file); + + var serialiser = new JsonSerializer(); + serialiser.Serialize(json, this); + } + + #endregion + } + + [JsonObject] + public class SavedServer { + public string Name { get; set; } + public string Host { get; set; } + public ushort Port { get; set; } + + public SavedServer(string name, string host, ushort port) { + this.Name = name; + this.Host = host; + this.Port = port; + } + } + + [JsonObject] + public class TrustedKey { + public string Name { get; set; } + public byte[] Key { get; set; } + + public TrustedKey(string name, byte[] key) { + this.Name = name; + this.Key = key; + } + } + + [JsonObject] + public class Tab : INotifyPropertyChanged { + private string name; + + public Tab(string name) { + this.name = name; + } + + public string Name { + get => this.name; + set { + this.name = value; + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Name))); + } + } + + public Filter Filter { get; set; } = new Filter(); + + [JsonIgnore] + public ObservableCollection Messages { get; } = new ObservableCollection(); + + public event PropertyChangedEventHandler? PropertyChanged; + + public void RepopulateMessages(ConcurrentStack mainMessages) { + this.Messages.Clear(); + + foreach (var message in mainMessages.Where(msg => this.Filter.Allowed(msg)).Reverse()) { + this.Messages.Add(message); + } + + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages))); + } + + public void AddMessage(ServerMessage message) { + if (message.Channel != 0 && !this.Filter.Allowed(message)) { + return; + } + + this.Messages.Add(message); + + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages))); + } + + public void ClearMessages() { + this.Messages.Clear(); + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Messages))); + } + + public static Filter GeneralFilter() { + var generalFilters = FilterCategory.Chat.Types() + .Concat(FilterCategory.Announcements.Types()) + .ToHashSet(); + generalFilters.Remove(FilterType.OwnBattleSystem); + generalFilters.Remove(FilterType.OthersBattleSystem); + generalFilters.Remove(FilterType.NpcDialogue); + generalFilters.Remove(FilterType.OthersFishing); + return new Filter { + Types = generalFilters, + }; + } + + public static ObservableCollection Defaults() { + var battleFilters = FilterCategory.Battle.Types() + .Append(FilterType.OwnBattleSystem) + .Append(FilterType.OthersBattleSystem) + .ToHashSet(); + + return new ObservableCollection { + new Tab("General") { + Filter = GeneralFilter(), + }, + new Tab("Battle") { + Filter = new Filter { + Types = battleFilters, + }, + }, + }; + } + } + + [JsonObject] + public class Filter { + public HashSet Types { get; set; } = new HashSet(); + + public bool Allowed(ServerMessage message) { + return this.Types + .SelectMany(type => type.Types()) + .Contains(new ChatCode((ushort)message.Channel).Type); + } + } +} diff --git a/XIVChat Desktop/ConnectDialog.xaml b/XIVChat Desktop/ConnectDialog.xaml new file mode 100644 index 0000000..dc4c049 --- /dev/null +++ b/XIVChat Desktop/ConnectDialog.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XIVChat Desktop/ConnectDialog.xaml.cs b/XIVChat Desktop/ConnectDialog.xaml.cs new file mode 100644 index 0000000..03ff6a0 --- /dev/null +++ b/XIVChat Desktop/ConnectDialog.xaml.cs @@ -0,0 +1,42 @@ +using System; +using System.Windows; + +namespace XIVChat_Desktop { + /// + /// Interaction logic for ConnectDialog.xaml + /// + public partial class ConnectDialog { + public App App => (App)Application.Current; + + public ConnectDialog() { + this.InitializeComponent(); + this.DataContext = this; + } + + private void Connect_Clicked(object? sender, RoutedEventArgs e) { + this.ConnectTo(this.Servers.SelectedServer); + } + + private void Cancel_Click(object? sender, RoutedEventArgs e) { + this.Close(); + } + + private void Servers_ItemDoubleClick(SavedServer? server) { + this.ConnectTo(server); + } + + private void ConnectTo(SavedServer? server) { + if (server == null) { + return; + } + + this.App.Connect(server.Host, server.Port); + + this.Close(); + } + + private void ConnectDialog_OnContentRendered(object? sender, EventArgs e) { + this.InvalidateVisual(); + } + } +} diff --git a/XIVChat Desktop/Connection.cs b/XIVChat Desktop/Connection.cs new file mode 100644 index 0000000..30d5893 --- /dev/null +++ b/XIVChat Desktop/Connection.cs @@ -0,0 +1,267 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using System.Windows; +using XIVChatCommon; + +namespace XIVChat_Desktop { + public class Connection : INotifyPropertyChanged { + private readonly App app; + + private readonly string host; + private readonly ushort port; + + private TcpClient? client; + + private readonly Channel outgoing = Channel.CreateUnbounded(); + private readonly Channel incoming = Channel.CreateUnbounded(); + private readonly Channel cancelChannel = Channel.CreateBounded(2); + + public readonly CancellationTokenSource cancel = new CancellationTokenSource(); + + public event PropertyChangedEventHandler? PropertyChanged; + public string? CurrentChannel { get; private set; } + + public Connection(App app, string host, ushort port) { + this.app = app; + + this.host = host; + this.port = port; + } + + public void SendMessage(string message) { + this.outgoing.Writer.TryWrite(message); + } + + public void Disconnect() { + this.cancel.Cancel(); + for (var i = 0; i < 2; i++) { + this.cancelChannel.Writer.TryWrite(1); + } + } + + public async Task Connect() { + this.client = new TcpClient(this.host, this.port); + var stream = this.client.GetStream(); + + await stream.WriteAsync(new byte[] { + 14, 20, 67, + }); + + var handshake = await KeyExchange.ClientHandshake(this.app.Config.KeyPair, stream); + + if (!this.app.Config.TrustedKeys.Any(trusted => trusted.Key.SequenceEqual(handshake.RemotePublicKey))) { + var trustChannel = Channel.CreateBounded(1); + + this.Dispatch(() => { + new TrustDialog(this.app.Window, trustChannel.Writer, handshake.RemotePublicKey).Show(); + }); + + var trusted = await trustChannel.Reader.ReadAsync(this.cancel.Token); + + if (!trusted) { + goto Close; + } + } + + // clear messages if connecting to a different host + var currentHost = $"{this.host}:{this.port}"; + var sameHost = this.app.LastHost == currentHost; + if (!sameHost) { + this.Dispatch(() => { + this.app.Window.ClearAllMessages(); + this.app.LastHost = currentHost; + }); + } + + this.Dispatch(() => { + this.app.Window.AddSystemMessage("Connected"); + }); + + // check if backlog or catch-up is needed + if (sameHost) { + // catch-up + var lastRealMessage = this.app.Window.Messages.LastOrDefault(msg => msg.Channel != 0); + if (lastRealMessage != null) { + var catchUp = new ClientCatchUp { + After = lastRealMessage.Timestamp, + }; + await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, catchUp, this.cancel.Token); + } + } else if (this.app.Config.BacklogMessages > 0) { + // backlog + var backlogReq = new ClientBacklog { + Amount = this.app.Config.BacklogMessages, + }; + await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, backlogReq, this.cancel.Token); + } + + // start a task for accepting incoming messages and sending them down the channel + _ = Task.Run(async () => { + var inc = SecretMessage.ReadSecretMessage(stream, handshake.Keys.rx, this.cancel.Token); + var cancel = this.cancelChannel.Reader.ReadAsync().AsTask(); + + while (!this.cancel.IsCancellationRequested) { + var result = await Task.WhenAny(inc, cancel); + if (result == inc) { + if (inc.Exception != null) { + this.Dispatch(() => { + this.app.Window.AddSystemMessage("Error reading incoming message."); + // ReSharper disable once LocalizableElement + Console.WriteLine($"Error reading incoming message: {inc.Exception.Message}"); + foreach (var inner in inc.Exception.InnerExceptions) { + Console.WriteLine(inner.StackTrace); + } + }); + break; + } + + var rawMessage = await inc; + inc = SecretMessage.ReadSecretMessage(stream, handshake.Keys.rx, this.cancel.Token); + await this.incoming.Writer.WriteAsync(rawMessage); + } else if (result == cancel) { + break; + } + } + }); + + var incoming = this.incoming.Reader.ReadAsync().AsTask(); + var outgoing = this.outgoing.Reader.ReadAsync().AsTask(); + var cancel = this.cancelChannel.Reader.ReadAsync().AsTask(); + + // listen for incoming and outgoing messages and cancel requests + while (!this.cancel.IsCancellationRequested) { + var result = await Task.WhenAny(incoming, outgoing, cancel); + if (result == incoming) { + if (this.incoming.Reader.Completion.IsCompleted) { + break; + } + var rawMessage = await incoming; + incoming = this.incoming.Reader.ReadAsync().AsTask(); + + await this.HandleIncoming(rawMessage); + } else if (result == outgoing) { + var toSend = await outgoing; + outgoing = this.outgoing.Reader.ReadAsync().AsTask(); + + var message = new ClientMessage { + Content = toSend, + }; + try { + await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, message, this.cancel.Token); + } catch (Exception ex) { + this.Dispatch(() => { + this.app.Window.AddSystemMessage("Error sending message."); + // ReSharper disable once LocalizableElement + Console.WriteLine($"Error sending message: {ex.Message}"); + Console.WriteLine(ex.StackTrace); + }); + break; + } + } else if (result == cancel) { + try { + await SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, ClientShutdown.Instance, this.cancel.Token); + } catch (Exception ex) { + this.Dispatch(() => { + this.app.Window.AddSystemMessage("Error sending message."); + // ReSharper disable once LocalizableElement + Console.WriteLine($"Error sending message: {ex.Message}"); + Console.WriteLine(ex.StackTrace); + }); + } + + break; + } + } + + // at this point, we are disconnected, so log it + this.Dispatch(() => { + this.app.Window.AddSystemMessage("Disconnected"); + }); + + // wait up to a second to send the shutdown packet + await Task.WhenAny(Task.Delay(1_000), SecretMessage.SendSecretMessage(stream, handshake.Keys.tx, ClientShutdown.Instance)); + + Close: + try { + this.client.Close(); + } catch (ObjectDisposedException) { } + } + + private async Task HandleIncoming(byte[] rawMessage) { + var type = (ServerOperation)rawMessage[0]; + var payload = new byte[rawMessage.Length - 1]; + Array.Copy(rawMessage, 1, payload, 0, payload.Length); + + switch (type) { + case ServerOperation.Pong: + break; + case ServerOperation.Message: + var message = ServerMessage.Decode(payload); + + this.Dispatch(() => { + this.app.Window.AddMessage(message); + }); + break; + case ServerOperation.Shutdown: + this.Disconnect(); + break; + case ServerOperation.PlayerData: + var playerData = payload.Length == 0 ? null : PlayerData.Decode(payload); + + var visibility = playerData == null ? Visibility.Collapsed : Visibility.Visible; + + this.Dispatch(() => { + var window = this.app.Window; + + window.LoggedInAs.Content = playerData?.name; + window.LoggedInAs.Visibility = visibility; + + window.LoggedInAsSeparator.Visibility = visibility; + + window.CurrentWorld.Content = playerData?.currentWorld; + window.CurrentWorld.Visibility = visibility; + + window.CurrentWorldSeparator.Visibility = visibility; + + window.Location.Content = playerData?.location; + window.Location.Visibility = visibility; + }); + break; + case ServerOperation.Availability: + break; + case ServerOperation.Channel: + var channel = ServerChannel.Decode(payload); + + this.CurrentChannel = channel.name; + + this.Dispatch(() => { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.CurrentChannel))); + }); + break; + case ServerOperation.Backlog: + var backlog = ServerBacklog.Decode(payload); + + foreach (var msg in backlog.messages) { + this.Dispatch(() => { + this.app.Window.AddMessage(msg); + }); + } + + break; + case ServerOperation.PlayerList: + break; + case ServerOperation.LinkshellList: + break; + } + } + + private void Dispatch(Action action) { + this.app.Dispatcher.BeginInvoke(action); + } + } +} diff --git a/XIVChat Desktop/Controls/SavedServers.xaml b/XIVChat Desktop/Controls/SavedServers.xaml new file mode 100644 index 0000000..62978d2 --- /dev/null +++ b/XIVChat Desktop/Controls/SavedServers.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XIVChat Desktop/Controls/SavedServers.xaml.cs b/XIVChat Desktop/Controls/SavedServers.xaml.cs new file mode 100644 index 0000000..7117fb7 --- /dev/null +++ b/XIVChat Desktop/Controls/SavedServers.xaml.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Windows; + +namespace XIVChat_Desktop.Controls { + /// + /// Interaction logic for SavedServers.xaml + /// + public partial class SavedServers { + public App App => (App)Application.Current; + private Configuration Config => this.App.Config; + private Window Window => Window.GetWindow(this)!; + + public IEnumerable ItemsSource { + get { return (IEnumerable)this.GetValue(ItemsSourceProperty); } + set { this.SetValue(ItemsSourceProperty, value); } + } + + public SavedServer? SelectedServer { + get { + var item = this.Servers.SelectedItem; + + return item as SavedServer; + } + } + + public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( + "ItemsSource", + typeof(IEnumerable), + typeof(SavedServers), + new PropertyMetadata(null) + ); + + public SavedServers() { + this.InitializeComponent(); + } + + private void AddServer_Click(object sender, RoutedEventArgs e) { + new ManageServer(this.Window, null).ShowDialog(); + } + + private void DeleteServer_Click(object sender, RoutedEventArgs e) { + var server = this.SelectedServer; + if (server == null) { + return; + } + + this.Config.Servers.Remove(server); + this.Config.Save(); + } + + private void EditServer_Click(object sender, RoutedEventArgs e) { + var server = this.SelectedServer; + if (server == null) { + return; + } + + new ManageServer(this.Window, server).ShowDialog(); + } + + private void Item_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) { + var server = ((FrameworkElement)e.OriginalSource).DataContext; + if (!(server is SavedServer)) { + return; + } + + this.ItemDoubleClick?.Invoke((SavedServer)server); + } + + public delegate void MouseDoubleClickHandler(SavedServer server); + + public event MouseDoubleClickHandler? ItemDoubleClick; + } +} diff --git a/XIVChat Desktop/Converters.cs b/XIVChat Desktop/Converters.cs new file mode 100644 index 0000000..e363fee --- /dev/null +++ b/XIVChat Desktop/Converters.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace XIVChat_Desktop { + public class DoubleConverter : IValueConverter { + public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { + return value.ToString(); + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + if (double.TryParse(value.ToString(), out var res)) { + return res; + } + + return null; + } + } + + public class UShortConverter : IValueConverter { + public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { + return value.ToString(); + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { + if (ushort.TryParse(value.ToString(), out var res)) { + return res; + } + + return null; + } + } +} diff --git a/XIVChat Desktop/Filters.cs b/XIVChat Desktop/Filters.cs new file mode 100644 index 0000000..a61a1d7 --- /dev/null +++ b/XIVChat Desktop/Filters.cs @@ -0,0 +1,347 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using XIVChatCommon; + +namespace XIVChat_Desktop { + public enum FilterCategory { + [Category("Chat", + FilterType.Say, + FilterType.Yell, + FilterType.Shout, + FilterType.Tell, + FilterType.Party, + FilterType.Alliance, + FilterType.FreeCompany, + FilterType.PvpTeam, + FilterType.CrossLinkshell1, + FilterType.CrossLinkshell2, + FilterType.CrossLinkshell3, + FilterType.CrossLinkshell4, + FilterType.CrossLinkshell5, + FilterType.CrossLinkshell6, + FilterType.CrossLinkshell7, + FilterType.CrossLinkshell8, + FilterType.Linkshell1, + FilterType.Linkshell2, + FilterType.Linkshell3, + FilterType.Linkshell4, + FilterType.Linkshell5, + FilterType.Linkshell6, + FilterType.Linkshell7, + FilterType.Linkshell8, + FilterType.NoviceNetwork, + FilterType.StandardEmote, + FilterType.CustomEmote, + FilterType.Gm + )] + Chat, + + [Category("Battle", FilterType.Battle)] + Battle, + + [Category("Announcements", + FilterType.Debug, + FilterType.Urgent, + FilterType.Notice, + FilterType.SystemMessages, + FilterType.OwnBattleSystem, + FilterType.OthersBattleSystem, + FilterType.GatheringSystem, + FilterType.ErrorMessages, + FilterType.Echo, + FilterType.NoviceNetworkAnnouncements, + FilterType.FreeCompanyAnnouncements, + FilterType.PvpTeamAnnouncements, + FilterType.FreeCompanyLoginLogout, + FilterType.PvpTeamLoginLogout, + FilterType.RetainerSale, + FilterType.NpcDialogue, + FilterType.NpcAnnouncement, + FilterType.Loot, + FilterType.OwnProgression, + FilterType.PartyProgression, + FilterType.OthersProgression, + FilterType.OwnLoot, + FilterType.OthersLoot, + FilterType.OwnCrafting, + FilterType.OthersCrafting, + FilterType.OwnGathering, + FilterType.OthersFishing, + FilterType.PeriodicRecruitment, + FilterType.Sign, + FilterType.Random, + FilterType.Orchestrion, + FilterType.MessageBook, + FilterType.Alarm + )] + Announcements, + } + + public class CategoryAttribute : Attribute { + public string Name { get; } + public FilterType[] Types { get; } + + public CategoryAttribute(string name, params FilterType[] types) { + this.Name = name; + this.Types = types; + } + } + + public static class FilterCategoryExtensions { + private static CategoryAttribute? Info(this FilterCategory filter) => filter + .GetType() + .GetField(filter.ToString()) + ?.GetCustomAttribute(false); + + public static string? Name(this FilterCategory category) => category.Info()?.Name; + + public static IEnumerable Types(this FilterCategory category) => category.Info()?.Types ?? new FilterType[0]; + } + + // NOTE: Changing the order of these is a breaking change + public enum FilterType { + [Filter("Say", ChatType.Say)] Say, + [Filter("Shout", ChatType.Shout)] Shout, + [Filter("Yell", ChatType.Yell)] Yell, + + [Filter("Tell", ChatType.TellOutgoing, ChatType.TellIncoming)] + Tell, + + [Filter("Party", ChatType.Party, ChatType.CrossParty)] + Party, + + [Filter("Alliance", ChatType.Alliance)] + Alliance, + + [Filter("Free Company", ChatType.FreeCompany)] + FreeCompany, + [Filter("PvP Team", ChatType.PvpTeam)] PvpTeam, + + [Filter("Cross-world Linkshell [1]", ChatType.CrossLinkshell1)] + CrossLinkshell1, + + [Filter("Cross-world Linkshell [2]", ChatType.CrossLinkshell2)] + CrossLinkshell2, + + [Filter("Cross-world Linkshell [3]", ChatType.CrossLinkshell3)] + CrossLinkshell3, + + [Filter("Cross-world Linkshell [4]", ChatType.CrossLinkshell4)] + CrossLinkshell4, + + [Filter("Cross-world Linkshell [5]", ChatType.CrossLinkshell5)] + CrossLinkshell5, + + [Filter("Cross-world Linkshell [6]", ChatType.CrossLinkshell6)] + CrossLinkshell6, + + [Filter("Cross-world Linkshell [7]", ChatType.CrossLinkshell7)] + CrossLinkshell7, + + [Filter("Cross-world Linkshell [8]", ChatType.CrossLinkshell8)] + CrossLinkshell8, + + [Filter("Linkshell [1]", ChatType.Linkshell1)] + Linkshell1, + + [Filter("Linkshell [2]", ChatType.Linkshell2)] + Linkshell2, + + [Filter("Linkshell [3]", ChatType.Linkshell3)] + Linkshell3, + + [Filter("Linkshell [4]", ChatType.Linkshell4)] + Linkshell4, + + [Filter("Linkshell [5]", ChatType.Linkshell5)] + Linkshell5, + + [Filter("Linkshell [6]", ChatType.Linkshell6)] + Linkshell6, + + [Filter("Linkshell [7]", ChatType.Linkshell7)] + Linkshell7, + + [Filter("Linkshell [8]", ChatType.Linkshell8)] + Linkshell8, + + [Filter("Novice Network", ChatType.NoviceNetwork)] + NoviceNetwork, + + [Filter("Standard Emotes", ChatType.StandardEmote)] + StandardEmote, + + [Filter("Custom Emotes", ChatType.CustomEmote)] + CustomEmote, + + [Filter("Battle", + ChatType.Damage, + ChatType.Miss, + ChatType.Action, + ChatType.Item, + ChatType.Healing, + ChatType.GainBuff, + ChatType.LoseBuff, + ChatType.GainDebuff, + ChatType.LoseDebuff, + ChatType.BattleSystem + )] + Battle, + + [Filter("Debug", ChatType.Debug)] Debug, + [Filter("Urgent", ChatType.Urgent)] Urgent, + [Filter("Notice", ChatType.Notice)] Notice, + + [Filter("System Messages", ChatType.System)] + SystemMessages, + + [Filter("Own Battle System Messages", ChatType.BattleSystem, Source = FilterSource.Self)] + OwnBattleSystem, + + [Filter("Others' Battle System Messages", ChatType.BattleSystem, Source = FilterSource.Others)] + OthersBattleSystem, + + [Filter("Gathering System Messages", ChatType.GatheringSystem)] + GatheringSystem, + + [Filter("Error Messages", ChatType.Error)] + ErrorMessages, + [Filter("Echo", ChatType.Echo)] Echo, + + [Filter("Novice Network Notifications", ChatType.NoviceNetworkSystem)] + NoviceNetworkAnnouncements, + + [Filter("Free Company Announcements", ChatType.FreeCompanyAnnouncement)] + FreeCompanyAnnouncements, + + [Filter("PvP Team Announcements", ChatType.PvpTeamAnnouncement)] + PvpTeamAnnouncements, + + [Filter("Free Company Member Login Notifications", ChatType.FreeCompanyLoginLogout)] + FreeCompanyLoginLogout, + + [Filter("PvP Team Member Login Notifications", ChatType.PvpTeamLoginLogout)] + PvpTeamLoginLogout, + + [Filter("Retainer Sale Notifications", ChatType.RetainerSale)] + RetainerSale, + + [Filter("NPC Dialogue", ChatType.NpcDialogue)] + NpcDialogue, + + [Filter("NPC Dialogue (Announcements)", ChatType.NpcAnnouncement)] + NpcAnnouncement, + + [Filter("Loot Notices", ChatType.LootNotice)] + Loot, + + [Filter("Own Progression Messages", ChatType.Progress, Source = FilterSource.Self)] + OwnProgression, + + [Filter("Party Members' Progression Messages", ChatType.Progress, Source = FilterSource.Party)] + PartyProgression, + + [Filter("Others' Progression Messages", ChatType.Progress, Source = FilterSource.Others)] + OthersProgression, + + [Filter("Own Loot Messages", ChatType.LootRoll, Source = FilterSource.Self)] + OwnLoot, + + [Filter("Others' Loot Messages", ChatType.LootRoll, Source = FilterSource.Others)] + OthersLoot, + + [Filter("Own Synthesis Messages", ChatType.Crafting, Source = FilterSource.Self)] + OwnCrafting, + + [Filter("Others' Synthesis Messages", ChatType.Crafting, Source = FilterSource.Self)] + OthersCrafting, + + [Filter("Own Gathering Messages", ChatType.Gathering, Source = FilterSource.Self)] + OwnGathering, + + [Filter("Others' Fishing Messages", ChatType.Gathering, Source = FilterSource.Others)] + OthersFishing, + + [Filter("Periodic Recruitment Notifications", ChatType.PeriodicRecruitmentNotification)] + PeriodicRecruitment, + + [Filter("Sign Messages for PC Targets", ChatType.Sign)] + Sign, + + [Filter("Random Number Messages", ChatType.RandomNumber)] + Random, + + [Filter("Current Orchestrion Track Messages", ChatType.Orchestrion)] + Orchestrion, + + [Filter("Message Book Alert", ChatType.MessageBook)] + MessageBook, + + [Filter("Alarm Notifications", ChatType.Alarm)] + Alarm, + + [Filter("GM Messages", + ChatType.GmTell, + ChatType.GmSay, + ChatType.GmShout, + ChatType.GmYell, + ChatType.GmParty, + ChatType.GmFreeCompany, + ChatType.GmLinkshell1, + ChatType.GmLinkshell2, + ChatType.GmLinkshell3, + ChatType.GmLinkshell4, + ChatType.GmLinkshell5, + ChatType.GmLinkshell6, + ChatType.GmLinkshell7, + ChatType.GmLinkshell8, + ChatType.GmNoviceNetwork + )] + Gm, + } + + public enum FilterSource { + None, + Self, + Party, + Others, + } + + public class FilterAttribute : Attribute { + public string Name { get; } + public ChatType[] Types { get; } + public FilterSource Source { get; set; } = FilterSource.None; + + public FilterAttribute(string name, params ChatType[] types) { + this.Name = name; + this.Types = types; + } + } + + public static class FilterTypeExtensions { + private static readonly ChatSource[] Others = { + ChatSource.PartyMember, ChatSource.AllianceMember, ChatSource.Other, ChatSource.EngagedEnemy, ChatSource.UnengagedEnemy, ChatSource.FriendlyNpc, ChatSource.PartyPet, ChatSource.AlliancePet, ChatSource.OtherPet, + }; + + private static FilterAttribute? Info(this FilterType filter) => filter + .GetType() + .GetField(filter.ToString()) + ?.GetCustomAttribute(false); + + public static string? Name(this FilterType filter) => filter.Info()?.Name; + + public static IEnumerable Types(this FilterType filter) => filter.Info()?.Types ?? new ChatType[0]; + + public static ChatSource[] Sources(this FilterType filter) => filter.Info()?.Source switch { + FilterSource.Self => new[] { + ChatSource.Self, + }, + FilterSource.Party => new[] { + ChatSource.PartyMember, + }, + FilterSource.Others => Others, + _ => new ChatSource[0], + }; + } +} diff --git a/XIVChat Desktop/MainWindow.xaml b/XIVChat Desktop/MainWindow.xaml index 95e683b..543560b 100644 --- a/XIVChat Desktop/MainWindow.xaml +++ b/XIVChat Desktop/MainWindow.xaml @@ -4,22 +4,110 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:XIVChat_Desktop" + xmlns:ui="http://schemas.modernwpf.com/2019" + ui:WindowHelper.UseModernWindowStyle="True" mc:Ignorable="d" - Title="MainWindow" Height="450" Width="800"> + Title="XIVChat for Windows" + Height="450" + Width="800" + x:Name="Main" + Icon="/ic_launcher-playstore.png" + d:DataContext="{d:DesignInstance local:MainWindow}"> - - - + + + - + - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/XIVChat Desktop/MainWindow.xaml.cs b/XIVChat Desktop/MainWindow.xaml.cs index 4333cfc..c5a795e 100644 --- a/XIVChat Desktop/MainWindow.xaml.cs +++ b/XIVChat Desktop/MainWindow.xaml.cs @@ -1,25 +1,136 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using XIVChatCommon; namespace XIVChat_Desktop { /// /// Interaction logic for MainWindow.xaml /// - public partial class MainWindow : Window { + public partial class MainWindow { + public App App => (App)Application.Current; + + public ConcurrentStack Messages { get; } = new ConcurrentStack(); + public MainWindow() { - InitializeComponent(); + this.InitializeComponent(); + this.DataContext = this; + } + + private T? FindElementByName(DependencyObject element, string sChildName) where T : FrameworkElement { + T? childElement = null; + var nChildCount = VisualTreeHelper.GetChildrenCount(element); + for (int i = 0; i < nChildCount; i++) { + if (!(VisualTreeHelper.GetChild(element, i) is FrameworkElement child)) { + continue; + } + + if (child is T t && child.Name.Equals(sChildName)) { + childElement = t; + break; + } + + childElement = this.FindElementByName(child, sChildName); + + if (childElement != null) { + break; + } + } + + return childElement; + } + + public void ClearAllMessages() { + this.Messages.Clear(); + foreach (var tab in this.App.Config.Tabs) { + tab.ClearMessages(); + } + } + + public void AddSystemMessage(string content) { + var message = new ServerMessage { + Channel = 0, + Content = Encoding.UTF8.GetBytes(content), + Timestamp = DateTime.UtcNow, + Chunks = new List { + new TextChunk { + Foreground = 0xb38cffff, + Content = content, + }, + }, + }; + this.AddMessage(message); + } + + public void AddMessage(ServerMessage message) { + // detect if scroller is at the bottom + var scroller = this.FindElementByName(this.Tabs, "scroller"); + var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001; + + // add message to main list + this.Messages.Push(message); + // add message to each tab if the filter allows for it + foreach (var tab in this.App.Config.Tabs) { + tab.AddMessage(message); + } + + // scroll to the bottom if previously at the bottom + if (wasAtBottom) { + scroller.ScrollToBottom(); + } + } + + private void Connect_Click(object sender, RoutedEventArgs e) { + var dialog = new ConnectDialog { + Owner = this, + }; + dialog.ShowDialog(); + } + + private void Disconnect_Click(object sender, RoutedEventArgs e) { + this.App.Disconnect(); + } + + private void Input_Submit(object sender, KeyEventArgs e) { + if (e.Key != Key.Return) { + return; + } + + var conn = this.App.Connection; + if (conn == null) { + return; + } + + if (!(sender is TextBox)) { + return; + } + + var textBox = (TextBox)sender; + + conn.SendMessage(textBox.Text); + textBox.Text = ""; + } + + private void Configuration_Click(object sender, RoutedEventArgs e) { + new ConfigWindow(this, this.App.Config).Show(); + } + + private void Tabs_Loaded(object sender, RoutedEventArgs e) { + this.Tabs.SelectedIndex = 0; + } + + private void Tabs_SelectionChanged(object sender, SelectionChangedEventArgs e) { + var scroller = this.FindElementByName(this.Tabs, "scroller"); + scroller?.ScrollToBottom(); + } + + private void ManageTabs_Click(object sender, RoutedEventArgs e) { + new ManageTabs(this).Show(); } } } diff --git a/XIVChat Desktop/ManageServer.xaml b/XIVChat Desktop/ManageServer.xaml new file mode 100644 index 0000000..97c5ab2 --- /dev/null +++ b/XIVChat Desktop/ManageServer.xaml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XIVChat Desktop/ManageServer.xaml.cs b/XIVChat Desktop/ManageServer.xaml.cs new file mode 100644 index 0000000..5059af8 --- /dev/null +++ b/XIVChat Desktop/ManageServer.xaml.cs @@ -0,0 +1,64 @@ +using System; +using System.Windows; + +namespace XIVChat_Desktop { + /// + /// Interaction logic for ManageServer.xaml + /// + public partial class ManageServer { + public App App => (App)Application.Current; + public SavedServer? Server { get; private set; } + + private readonly bool isNewServer; + + public ManageServer(Window owner, SavedServer? server) { + this.Owner = owner; + this.Server = server; + this.isNewServer = server == null; + + this.InitializeComponent(); + this.DataContext = this; + } + + private void Save_Click(object sender, RoutedEventArgs e) { + var serverName = this.ServerName.Text; + var serverHost = this.ServerHost.Text; + + if (serverName.Length == 0 || serverHost.Length == 0) { + MessageBox.Show("Server must have a name and host."); + return; + } + + ushort port; + if (this.ServerPort.Text.Length == 0) { + port = 14777; + } else { + if (!ushort.TryParse(this.ServerPort.Text, out port) || port < 1) { + MessageBox.Show("Port was not valid. It must be a number between 1 and 65535."); + return; + } + } + + if (this.isNewServer) { + this.Server = new SavedServer( + serverName, + serverHost, + port + ); + this.App.Config.Servers.Add(this.Server); + } + + this.App.Config.Save(); + + this.Close(); + } + + private void Cancel_Click(object sender, RoutedEventArgs e) { + this.Close(); + } + + private void ManageServer_OnContentRendered(object? sender, EventArgs e) { + this.InvalidateVisual(); + } + } +} diff --git a/XIVChat Desktop/ManageTab.xaml b/XIVChat Desktop/ManageTab.xaml new file mode 100644 index 0000000..dcd38c8 --- /dev/null +++ b/XIVChat Desktop/ManageTab.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + diff --git a/XIVChat Desktop/ManageTabs.xaml.cs b/XIVChat Desktop/ManageTabs.xaml.cs new file mode 100644 index 0000000..698f70f --- /dev/null +++ b/XIVChat Desktop/ManageTabs.xaml.cs @@ -0,0 +1,59 @@ +using System.Windows; +using System.Windows.Input; + +namespace XIVChat_Desktop { + /// + /// Interaction logic for ManageTabs.xaml + /// + public partial class ManageTabs { + public App App => (App)Application.Current; + + private Tab? SelectedTab { + get { + var item = this.Tabs.SelectedItem; + + return item as Tab; + } + } + + public ManageTabs(Window owner) { + this.Owner = owner; + this.InitializeComponent(); + this.DataContext = this; + } + + private void AddTab_Click(object sender, RoutedEventArgs e) { + new FiltersSelection(this, null).ShowDialog(); + } + + private void EditTab_Click(object sender, RoutedEventArgs e) { + var tab = this.SelectedTab; + if (tab == null) { + return; + } + new FiltersSelection(this, tab).ShowDialog(); + } + + private void DeleteTab_Click(object sender, RoutedEventArgs e) { + var tab = this.SelectedTab; + if (tab == null) { + return; + } + + this.App.Config.Tabs.Remove(tab); + this.App.Config.Save(); + + } + + private void Tab_MouseDoubleClick(object sender, MouseButtonEventArgs e) { + var item = ((FrameworkElement)e.OriginalSource).DataContext; + if (!(item is Tab)) { + return; + } + + var tab = item as Tab; + + new FiltersSelection(this, tab).ShowDialog(); + } + } +} diff --git a/XIVChat Desktop/MessageFormatter.cs b/XIVChat Desktop/MessageFormatter.cs new file mode 100644 index 0000000..a27b5dc --- /dev/null +++ b/XIVChat Desktop/MessageFormatter.cs @@ -0,0 +1,213 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using XIVChatCommon; + +namespace XIVChat_Desktop { + public class MessageFormatter { + private static readonly BitmapFrame FontIcon = + BitmapFrame.Create(new Uri("pack://application:,,,/Resources/fonticon_ps4.tex.png")); + + public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached( + "FormattedText", + typeof(ServerMessage), + typeof(MessageFormatter), + new PropertyMetadata(null, FormattedTextPropertyChanged) + ); + + public static void SetFormattedText(DependencyObject textBlock, ServerMessage value) { + textBlock.SetValue(FormattedTextProperty, value); + } + + public static string GetFormattedText(DependencyObject textBlock) { + return (string)textBlock.GetValue(FormattedTextProperty); + } + + private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { + // Clear current textBlock + if (!(d is TextBlock textBlock)) { + return; + } + + textBlock.ClearValue(TextBlock.TextProperty); + textBlock.Inlines.Clear(); + + // Create new formatted text + var lineHeight = textBlock.FontFamily.LineSpacing * textBlock.FontSize; + foreach (var inline in ChunksToTextBlock(lineHeight, (ServerMessage)e.NewValue)) { + textBlock.Inlines.Add(inline); + } + } + + private static IEnumerable ChunksToTextBlock(double lineHeight, ServerMessage message) { + var elements = new List(); + + var timestampString = message.Timestamp.ToLocalTime().ToString("t", CultureInfo.CurrentUICulture); + elements.Add(new Run($"[{timestampString}]") { + Foreground = new SolidColorBrush(Colors.White), + }); + + foreach (var chunk in message.Chunks) { + switch (chunk) { + case TextChunk textChunk: + var colour = textChunk.Foreground ?? textChunk.FallbackColour ?? 0; + + var r = (byte)((colour >> 24) & 0xFF); + var g = (byte)((colour >> 16) & 0xFF); + var b = (byte)((colour >> 8) & 0xFF); + var a = (byte)(colour & 0xFF); + + var brush = new SolidColorBrush(Color.FromArgb(a, r, g, b)); + var style = textChunk.Italic ? FontStyles.Italic : FontStyles.Normal; + + //var part = string.Empty; + //foreach (char c in textChunk.Content) { + // if (c >= '\ue000' && c <= '\uf8ff') { + // // private use + + // // add existing text if necessary + // if (part.Length != 0) { + // elements.Add(new Run(part) { + // Foreground = brush, + // FontStyle = style, + // }); + // part = string.Empty; + // } + + // // add private use segment with font + // elements.Add(new Run(c.ToString()) { + // Foreground = brush, + // FontStyle = style, + // FontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/fonts/#XIV AXIS Std ATK"), + // }); + // continue; + // } + + // part += c; + //} + + elements.Add(new Run(textChunk.Content) { + Foreground = brush, + FontStyle = style, + }); + break; + case IconChunk iconChunk: + var bounds = GetBounds(iconChunk.Index); + if (bounds == null) { + break; + } + + var width = lineHeight / bounds.Value.Height * bounds.Value.Width; + + var cropped = new CroppedBitmap(FontIcon, bounds.Value); + var image = new Image { + Source = cropped, + Width = width, + Height = lineHeight, + }; + elements.Add(new InlineUIContainer(image) { + BaselineAlignment = BaselineAlignment.Bottom, + }); + break; + } + } + + return elements; + } + + private static Int32Rect? GetBounds(byte id) => id switch { + 1 => new Int32Rect(0, 0, 20, 20), + 2 => new Int32Rect(20, 0, 20, 20), + 3 => new Int32Rect(40, 0, 20, 20), + 4 => new Int32Rect(60, 0, 20, 20), + 5 => new Int32Rect(80, 0, 20, 20), + 6 => new Int32Rect(0, 20, 20, 20), + 7 => new Int32Rect(20, 20, 20, 20), + 8 => new Int32Rect(40, 20, 20, 20), + 9 => new Int32Rect(60, 20, 20, 20), + 10 => new Int32Rect(80, 20, 20, 20), + 11 => new Int32Rect(0, 40, 20, 20), + 12 => new Int32Rect(20, 40, 20, 20), + 13 => new Int32Rect(40, 40, 20, 20), + 14 => new Int32Rect(60, 40, 20, 20), + 15 => new Int32Rect(80, 40, 20, 20), + 16 => new Int32Rect(60, 100, 20, 20), + 17 => new Int32Rect(80, 100, 20, 20), + 18 => new Int32Rect(0, 60, 54, 20), + 19 => new Int32Rect(54, 60, 54, 20), + 20 => new Int32Rect(60, 80, 20, 20), + 21 => new Int32Rect(0, 80, 28, 20), + 22 => new Int32Rect(28, 80, 32, 20), + 23 => new Int32Rect(80, 80, 20, 20), + 24 => new Int32Rect(0, 100, 28, 20), + 25 => new Int32Rect(28, 100, 32, 20), + 51 => new Int32Rect(124, 0, 20, 20), + 52 => new Int32Rect(144, 0, 20, 20), + 53 => new Int32Rect(164, 0, 20, 20), + 54 => new Int32Rect(100, 0, 12, 20), + 55 => new Int32Rect(112, 0, 12, 20), + 56 => new Int32Rect(100, 20, 20, 20), + 57 => new Int32Rect(120, 20, 20, 20), + 58 => new Int32Rect(140, 20, 20, 20), + 59 => new Int32Rect(100, 40, 20, 20), + 60 => new Int32Rect(120, 40, 20, 20), + 61 => new Int32Rect(140, 40, 20, 20), + 62 => new Int32Rect(160, 20, 20, 20), + 63 => new Int32Rect(160, 40, 20, 20), + 64 => new Int32Rect(184, 0, 20, 20), + 65 => new Int32Rect(204, 0, 20, 20), + 66 => new Int32Rect(224, 0, 20, 20), + 67 => new Int32Rect(180, 20, 20, 20), + 68 => new Int32Rect(200, 20, 20, 20), + 69 => new Int32Rect(236, 236, 20, 20), + 70 => new Int32Rect(180, 40, 20, 20), + 71 => new Int32Rect(200, 40, 20, 20), + 72 => new Int32Rect(220, 40, 20, 20), + 73 => new Int32Rect(220, 20, 20, 20), + 74 => new Int32Rect(108, 60, 20, 20), + 75 => new Int32Rect(128, 60, 20, 20), + 76 => new Int32Rect(148, 60, 20, 20), + 77 => new Int32Rect(168, 60, 20, 20), + 78 => new Int32Rect(188, 60, 20, 20), + 79 => new Int32Rect(208, 60, 20, 20), + 80 => new Int32Rect(228, 60, 20, 20), + 81 => new Int32Rect(100, 80, 20, 20), + 82 => new Int32Rect(120, 80, 20, 20), + 83 => new Int32Rect(140, 80, 20, 20), + 84 => new Int32Rect(160, 80, 20, 20), + 85 => new Int32Rect(180, 80, 20, 20), + 86 => new Int32Rect(200, 80, 20, 20), + 87 => new Int32Rect(220, 80, 20, 20), + 88 => new Int32Rect(100, 100, 20, 20), + 89 => new Int32Rect(120, 100, 20, 20), + 90 => new Int32Rect(140, 100, 20, 20), + 91 => new Int32Rect(160, 100, 20, 20), + 92 => new Int32Rect(180, 100, 20, 20), + 93 => new Int32Rect(200, 100, 20, 20), + 94 => new Int32Rect(220, 100, 20, 20), + 95 => new Int32Rect(0, 120, 20, 20), + 96 => new Int32Rect(20, 120, 20, 20), + 97 => new Int32Rect(40, 120, 20, 20), + 98 => new Int32Rect(60, 120, 20, 20), + 99 => new Int32Rect(80, 120, 20, 20), + 100 => new Int32Rect(100, 120, 20, 20), + 101 => new Int32Rect(120, 120, 20, 20), + 102 => new Int32Rect(140, 120, 20, 20), + 103 => new Int32Rect(160, 120, 20, 20), + 104 => new Int32Rect(180, 120, 20, 20), + 105 => new Int32Rect(200, 120, 20, 20), + 106 => new Int32Rect(220, 120, 20, 20), + 107 => new Int32Rect(0, 140, 20, 20), + 108 => new Int32Rect(20, 140, 20, 20), + 109 => new Int32Rect(40, 140, 20, 20), + 110 => new Int32Rect(60, 140, 20, 20), + 111 => new Int32Rect(80, 140, 20, 20), + _ => null, + }; + } +} diff --git a/XIVChat Desktop/Properties/DesignTimeResources.xaml b/XIVChat Desktop/Properties/DesignTimeResources.xaml new file mode 100644 index 0000000..b269956 --- /dev/null +++ b/XIVChat Desktop/Properties/DesignTimeResources.xaml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/XIVChat Desktop/Properties/Resources.Designer.cs b/XIVChat Desktop/Properties/Resources.Designer.cs new file mode 100644 index 0000000..669e441 --- /dev/null +++ b/XIVChat Desktop/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace XIVChat_Desktop.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XIVChat_Desktop.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to XIVChat for Windows. + /// + public static string AppName { + get { + return ResourceManager.GetString("AppName", resourceCulture); + } + } + } +} diff --git a/XIVChat Desktop/Properties/Resources.resx b/XIVChat Desktop/Properties/Resources.resx new file mode 100644 index 0000000..845753f --- /dev/null +++ b/XIVChat Desktop/Properties/Resources.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + XIVChat for Windows + + \ No newline at end of file diff --git a/XIVChat Desktop/Resources/fonticon_ps4.tex.png b/XIVChat Desktop/Resources/fonticon_ps4.tex.png new file mode 100644 index 0000000000000000000000000000000000000000..5026b15dda1b316abdbd781d4a338ac458bca1af GIT binary patch literal 51083 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 literal 0 HcmV?d00001 diff --git a/XIVChat Desktop/TrustDialog.xaml b/XIVChat Desktop/TrustDialog.xaml new file mode 100644 index 0000000..b68925a --- /dev/null +++ b/XIVChat Desktop/TrustDialog.xaml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + You are attempting to connect to a server you have never connected to before. Please check the server and ensure the two keys below match. + + + + + + + + + + Server: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Client: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Give the key a name to remember it. + No name + + + Do both keys match? + + + + + + + diff --git a/XIVChat Desktop/TrustDialog.xaml.cs b/XIVChat Desktop/TrustDialog.xaml.cs new file mode 100644 index 0000000..a3bbd26 --- /dev/null +++ b/XIVChat Desktop/TrustDialog.xaml.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Channels; +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace XIVChat_Desktop { + /// + /// Interaction logic for TrustDialog.xaml + /// + public partial class TrustDialog { + private readonly ChannelWriter trustChannel; + private readonly byte[] remoteKey; + + private App App => (App)Application.Current; + + public TrustDialog(Window owner, ChannelWriter trustChannel, byte[] remoteKey) { + this.Owner = owner; + this.trustChannel = trustChannel; + this.remoteKey = remoteKey; + + this.InitializeComponent(); + + this.ClientPublicKey.Text = ToHexString(this.App.Config.KeyPair.PublicKey); + var clientColours = BreakIntoColours(this.App.Config.KeyPair.PublicKey); + for (int i = 0; i < this.ClientPublicKeyColours.Children.Count; i++) { + var rect = (Rectangle)this.ClientPublicKeyColours.Children[i]; + rect.Fill = new SolidColorBrush(clientColours[i]); + } + + this.ServerPublicKey.Text = ToHexString(remoteKey); + var serverColours = BreakIntoColours(remoteKey); + for (int i = 0; i < this.ServerPublicKeyColours.Children.Count; i++) { + var rect = (Rectangle)this.ServerPublicKeyColours.Children[i]; + rect.Fill = new SolidColorBrush(serverColours[i]); + } + } + + private static List BreakIntoColours(IEnumerable key) { + var colours = new List(); + + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var chunk in SplitList(key.ToList(), 3)) { + var r = chunk[0]; + var g = chunk.Count > 1 ? chunk[1] : (byte)0; + var b = chunk.Count > 2 ? chunk[2] : (byte)0; + + colours.Add(Color.FromRgb(r, g, b)); + } + + return colours; + } + + private static IEnumerable> SplitList(List locations, int nSize) { + for (int i = 0; i < locations.Count; i += nSize) { + yield return locations.GetRange(i, Math.Min(nSize, locations.Count - i)); + } + } + + private static string ToHexString(IEnumerable bytes) { + return string.Join("", bytes.Select(b => b.ToString("X2"))); + } + + private async void Yes_Click(object sender, RoutedEventArgs e) { + var keyName = this.KeyName.Text; + if (keyName.Length == 0) { + MessageBox.Show("You must give this key a name."); + return; + } + + var trustedKey = new TrustedKey(keyName, this.remoteKey); + this.App.Config.TrustedKeys.Add(trustedKey); + this.App.Config.Save(); + await this.trustChannel.WriteAsync(true); + this.Close(); + } + + private async void No_Click(object sender, RoutedEventArgs e) { + await this.trustChannel.WriteAsync(false); + this.Close(); + } + } +} diff --git a/XIVChat Desktop/XIVChat Desktop.csproj b/XIVChat Desktop/XIVChat Desktop.csproj index 2c2d56c..868bbeb 100644 --- a/XIVChat Desktop/XIVChat Desktop.csproj +++ b/XIVChat Desktop/XIVChat Desktop.csproj @@ -1,10 +1,59 @@  - - WinExe - netcoreapp3.1 - XIVChat_Desktop - true - + + WinExe + netcoreapp3.1 + XIVChat_Desktop + true + XIVChat Desktop + enable + XIVChat + 1.0.0 + 1.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + Designer + true + + \ No newline at end of file diff --git a/XIVChat Desktop/fonts/ffxiv.ttf b/XIVChat Desktop/fonts/ffxiv.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a627f7707b5727e3e1d407f020c62f47addda715 GIT binary patch literal 60613 zcmd?S34B~fnJ;?koZh?D``#_}-mO-*)RJ1Q&Dw13mSoA6Y|C40$4g?zYZ51MfNaEx z<4FPoOjzdvpN?)Hz*URp0*AS9Js?2!b7dG=eZRJ3fjlT>SV6SVR1pw&*R#8_|#R$KkL~wC(!PAacW1$Ic%9>enLA5iz6*Ukbn4`F*Z=xEr$s?{@nu2K{rRbD51s0|ZXMdwJ%Q&RppgqKe01^~ zor8OAJ--q{5`ISz7S2!HM)|W}_|A!?#ie`n?~ty?r9nVT{2#t2omx6C{ZM}i_m}kV z;4LXPdG=p;&Buf%1wFqO1&w$?yb_}jL?^G&Xd2nu1ud_q*_*(9y+m15U>FJJ3Dwr+gu3wWOROvySb1qt`l0w? zJQqWuHB4wj-iTB{qM2jDZ-oeZNysC`aV;Qukb02zU~H{Q z9ev-uqI@sPuO+R+b9X3-`r<@l;!lM(q%R@$BCSJOujKv6HzJj&4$so>n~)5+&Qd~| z`ZoIi!!&_D{lAg;ST+A7)W4R5KD-qvfHaPzrKJ0|a02P0RnolvlF)yd51}HVCZ#zU zSA{UH2y5pAozRKgB>0Fc;h#!p{Nl2IBs_f^?s~ytB(z}?YJ}+Xx#vsI4?Vx>`3uiK z`TVm>OSmuBN1tzbzVrF9=VzXO+w+%}mX?0I^sA+Rj=U%ck?$jYFY@ikH9l2e_gO*B&7uB!!fa7BWJ;ki96*=O-=-#+ms~GIsxti)`uKMPaD+alt4^d#`A` zDDKEdqr*pr9%lP+u``c~h8Rk_^3joprPRol`NWRsh3JLxgBPMB(X00#d{~p>Ic^-f zu%i%tSeTtZg1>X~v4;nCget{DJ9c!Vo!!(9zJkXu>_7`gl@^fms=^{3+LND%J}jkY z<~PqjeA`gy;enwYp;#x;q@Ybuzqwqii3xF}E?n2F(w?6w&+hAa^a zQAHw_h@qc5hVa|_^Aof4!{}*jN27o_&j|~FlqBxVu?3(2FMI)oVPT!n2;K*{iTiS2 z0B@aGQ==0)gcdyE6Kwn$;R|Re2aqmTeF4Zfw&rURkh+C8t$b|(T%Cz~a~B0co-JUB z3Ai{V%nLKZM&Xc*Ho(dYf=jwW`~gaOU{IUVdmVm#S8f46r9sXv3hu&1L5CzfhqC>m zz%C)-TKimcODg7wX&o_#*4N?F`}7%orXwR=v6vU1UOXcXmUE9jT2%kL#HYp=|4Q7j z?vY1|k3Q1&=%a0qJW5^B2;rrNq_0bJ=)*zbgz#qJ0`JL1A&DPpEi7PwTHYg@kVcMr zbfONOiJ~J>Jd?(CGs;w7yo7mQCm8t`1Ys}F<3M`B;p|!vb}FTd!d^VQ|Iz}cL%nxR zrF5fG!uWK0oj#V%q%)58j`oh2*XQ+Vsg$7`i8%hCwiH{g@)YR~ecX7q6_3()I;aVH z2p1l=$Bo-QT$b8OZJD$--OJk7G5StClP)Qb`E=6PSt4Fs+)#`s7?s4oqEh*vMM;$Q zGf@;1wV`lO(y;r>w@4a|?tljWe)%Iw6xpc_>&0kvD4j~DMBLh=*J?GrEiI9*+S*L+ zLi)o!T`jHU1!J+x<*BRtaa~OftFIqn8m+Orwit^u7LO^vM!)+rv$e);ve#HmqNvL^ z?eqr%5o07A4(+U~tF3L?GJEAveLXW7od%Q1mUP+d*5c&Ees?C*uGfz@w%E7($48Fp zw7dFxm$n@=TL#)ne{8izd)wR7{p-3*G2no-0tcc(ldws_#rg{5-5ii!A%+|f_6jCm z5fbt|cL`CRXMpN1K>DJP2Yj1=6itQdP}IjfF`97#Lx&bXsemL?ecnI5ctjj`*M<+y z9&Rfo%Ka>ndC%mZHD+0r8B6Xib$0A&zjA(l^ZtPE1AC8e9XfdXwg27cTQk;>P$%4_ zj5WmRZ2{CzW4$PZFaYl*&|d^79zr7eqe=}%rNMOsWh2T)l)bp8MOjN_)LT)uqD;@* zP_}`L!WI0kt(5A1FQ%swtk@Cr6t z`OjF^V70dOH%B7&@(*0zEgQG_J&R8>f0ePCc8>P+9P8M9{KUTNgZ@X4p54`c^uo=b z_4~`7J$qL1If8p!*jN?v3xb~`a|Hty@Tknsyh4P}fgprf}UBpgKtlUb?4C#!e=@SmJIwfMy)+s5(p{ZC9! zRq1{C!QHIk<=?Qkvi4oK?b|oJ^}rwh(Z}=PJg>!GEv_UD!EsWynDLg$+b6%%lRFe?r-AF3kQRke1L~|WS9zaeS z3jwVj{GkrzR+LGpXaiL>@t^I4%&1d3AWM%0VFMI3k|PV4j7A}e5+n(}Or$`qfak55N&?f*ro;)(x06O3|eLVD87D1|(P+*Bt(RWz@Z~5%bf7$Vw&+Pb@ zouAcT_4A!SxQg}or#8L6>;Adf)Vgikuj{yJ>s-tA_us$sfhwT)PT=3nhsnB!81uQ~{&#pl!fhI7M4QQ60kzoX27ldXG zx#v#2A|q8Eb63 za?9vgv&~}hr`k&!+FbSx$#vb^;=2aJ{y^=99f`TFuEdbt_139bj;G>>m;OTfiZlsS z@+)%&iwDEP__TtVkB@n%*F=f_oYeA`1NPr_rB}TjA^6~^7e$- z(qFJa-d6c zNP>Oo6Vi92>){cI!9;N@H03CuwS-XQtyPdxoq9%qpvKkyyBuUe=yi#Bfl+0#E<= z8|RfTojoobwp8xWvo(;K+LWW{OZXB-mgq=yKyP@eJa_X;_{UmV3*|R&`MUU(mtHFWJ!^gG zrA2sH(L{0Sap_CaI#}WT3LHAor5*@|Ax>}Lb!eW1Gg+ZSC^zHB+t9HgPD2Aqskw!q zK!%`)>^&81Y7Xd;g~ts=*m{6vd$ASI5#*>uS)tMu0!w%7XG;x)xhn=?&KLydrP zb3D;F)YO#TJXNfTwgzf<3~F_*_Q81SRHrVN(_Jw#dWYuCnbHoIYTQ?beKjT!uYjm}$K-xkulLw!w6Tk`oWO-+3vw?5Ri zzQqgwfl#ybkfvU`LO3o=EAZSVjBy$`#%b%KV8Wc}FhArPpt8z`#!%jf`i-ck@+8WW zC?7-lD8R)O^i^hsW&m^NA=;EbWQpOEpjm@G%yFEkfPYH-n(Amy6Y-Ia}dfLi7k-52+BgAT$@TsK_N@LR1a2~cRV5s4Ihukwdp@yTPNhyJih>CJV{zefu zG243h5k!4j65*NY5HHj{=Xv|vJ(QlSyH}cft}b8xM)~g>npg{K$<;mQCdF&%9*tc( zC0q%o(cQv*qCxp(qhfUoVL5>;W@EyAE{5 z0nXq;9kmT(6Sbsc_%OVKDCbblq1=aZ56V4C{m3Q!v<`#tBBhbqkw%cVAst4#0qHKJ z2ap~@dJO4Fq?$b#z%wYkh$NyPvuJrUTHcI)Y*og1QP_dnBdDd(UxU#fQ|>GXrz@rF zD^Jr?H{oge*3EeOO_gVE6N);n(@SL+0-B^jbh+Z5pv|6Z-Zf^iY;LHvCXO~9sd~n=)HBpD_d&=?RtZTNj86z)t1Qh^ zm3Hlv%ej1G(<9|@G#GS)7u>k?5$U9~1&;m&g(A)HFB2s~1|tWxxUhb-E5~up1)OsM z=Ul)!7jVu6oO1!^T);UOaLxssa{=dEz&RIi&ZXj<3pnQj&XI~^P@HL`Tg#$)0eXyF zOF~*LnJZ5k@TBDu67bYoxo=ZS%`HYaTYO1qEp5dZyb>9J(&J9BC)g*-TgH}z$zjGm zU7lc%jxB8*WTLD5!_U3vZQC~)8S{Vcy>H((ZDi;bD5`Dgg7i)4BnUDBBcWfI7WT3$ zm01ylc|I%6LMPAdf*<)sVIEyGK+hn(gIvdcns==&k)LGSFRy8s!(n0<9BBhbq zkw%cVAst4#0qHKJ2ap~@dJO4FB!wTi(TbZ|A?}t3cgxF78c_%!Ws$m&#*ua)9YH#a zbPv*dkv@j>IMUaUh`S{*FsQ@wDxb&FxS!qx(V>#!% zf|nkJT1|qLqn8iMG_p^O&wZuRzF;Ghk)Di==#6T@Em0Dt;mKf(`t66G`(2MO0R9NQ31PkVrwf z34*Dt0MWzZ)sLM$s|Dk?>h;p(#I@H=OiFrPjo4P2o-VbCV2Wmw$@rl|hb8T~b6k5!L~z?A8&{JsKt8Ww1CA^zE~L&QUDjsvKigNCIgQJ?$yk;2VL6-bdx*OC>aCH;W7L$TFEx9PiX4+(JDW`)KsUF|Si zEM`af0f*0rzfQVxvZHjxx*d-4SDZdS{yH42%i;Gqf3>Bj#UwG&)Us}ig*iUx@OT{` zw2R_-lgsTXU+;0dOivh%PtX;+m#)5OG?ssCveXzqV#NO%i|Lal)7MArHH_8RN6a;% zXffbhIBJECBhXXhYhVy3|HX9oB)qvFpD@Mi}2 zGXwmY0shPYe`bI`Gr*r2;Li;3X9oB)1N@l*{>*T`Ys4I@D)T|iwW>>!vzQh#h~)Fn zaT>}4`}4s5Jg`3x?9T)H^T7T*us;v%&jb7O!2Ue2KM(9D#*S`2gTjkQq&qgD)h6mc zTHS*37A%P|88R+ef8$V1XC#Pa0F|5&WVNam8nMs*T&73LeMFfn2##WRB5i1s8YGlEBSY%W6KQ=^44eB&poc*;*hia zm^mHhtx{9rm<8>-KDnk*Y6*49U3yOb26$WqO!gV!tKiy46%4QnB4NM+0Xmh7cCMq#_VE9#~-e?T_>d^1}(r4MXr9OlZ z99H@r7T}A7Ziom1x&Iozs`SS2W#A97Hj>_oL&HJWF*szc;7W%)q#|E6rw1@h zwxg6xF)@`cO`5g4p0m z=J1pKa53;ZkK4vg6q*vc!ocNa0d4_BDlWZgc?QsD#;DJmCmU%38Yef?C@~H`N{oN9 zJ(YrTku0$v(H%Uk-r1s#fAex%0<|?6X>MM(uC=9(^kw)GmL8V=RhlAysNhpD#H{Ea zT0svC0Z65ojpx9nHL50=09;DfB6-BNC4f6u;IZejK1@07m-dE#06 zQ|W1GH+1kYrtHT`t7%0^u?hjSiZvK8oe&V6R7qQ&FT`(F9(vb(<=JbTj`?Z!nSy9p{IPsd9>;|Y%O1$8KZUGn6n3#mW!9&K zZcgn`bNH;2?r244DF$G<;}?Z))T<7PX_RFj4Jaa`fUQl~${Wz(rs~maL)ne_^miCK*Vm3?x(r5{ecNGmuajNT>`XR0a|%0|}LZgvvldWgww4s)WiwLS?vw z%Amy*T1+Xex1-#SGWB8uewl2NA?11$xr7#ij;MH81Q*TBQdaK2uPa^Ke@qjZn z^}9aV5sX_EFA3dZFOT&0HH~RJHy^$>6ngvKMu$mrx1=|Pdv-ePWBrpgQe!xi$rVTX zdN1>oV{~A$M&wU@z??|-%I#2#y~7Q;P}uEoghILWq*ZL}+39qS^ryXEJ?l~*ecRqK zjr-=_k%oqFh#n2*(`YV_pGU*=DCZM_ZA;`IEKOobuLZ%;(=?7%`0hxY4rXP^$2W0vInSMZ~ACTz>WcmS_ zen6(5L#7`sHsa?RiQXa_n>JA}^lN0w17qbMS#A>42yQJTKgl)4`7A+)KJ46{?C=(I zI88a71sN%!m8LdaKnIi6o*-)WUUR(uypSK%|Gw=B%qrMw!-xS}kL- zWF#U@te=fcXVX_reD%OY=+^(1-WfNg)1EDD*)5AZ4Q8`WBZ|>PU7f>jmz!idqmda6 zS{6-3qE?5)9-$`5fx(29_Z13mP+~jadclM)fKSsB78o0*LBKRnu0c+b zKUCJCth(N7P$n0RsyhyZ+VF4yi6~a3W?E{oUgA&+07?NBN&yrDfKmWZ3IIw0 zKq&wy1puW0pcDX<0)SEgPznG_0S+aVy_4T3aS4fj5#?t5N)v3(RGh;IrY1KyS9%Y* zg@oGxCASs`T0Aa?c{LinR&Tyy5A*La8xYi{-I9z+UEQ0ty0$<#?axFao6k*7imj!3 zc-HS>S4~V@z@jU&)@L?pv>V$?TBFG_XE7PIo3|V?7>@K0%Q8^b@yTao=Ce*~QN+yg z!-P(RM&R9i4UWoGM+o%N)A!$})!uji(@)=bzgBzyeNUg8-yumm=Fgp*$GR@kDfuQn zbl=l>_~q}S7I!O8aoVbf4E~}t2Fa0_Cc zk+JV~v8NheKA92!z5H_dWoh@z_ivv(z`kFOqYZ}jgilIOVBF?Pm=Gc^Ic}4uLtXXL z5;tMI!^;TH@PI+~TsRPAi3pgOM?CZE^2gZrU$Y5jDSw#l`VAW{fA%-9;qHZ8+zq+d zfMvQqVY~2e$}G%r8*Tws?oE z^~l#NZ4WDTvi!sMaepJ~DBpy9lY+`hc_J$Z>%W*JA7$my@DXCo(Oqy}hk1D1dHV&P=AaXKrS$x2HCo%lE!$sHrLUx@u|+ zFZSkh>`}i~BQ1Vg(rEqR-roKDdwa#>eS5CB(_rXq?{Dj_vte~iZ>#HW>u>Kg81B4c zPhWpLu0MNLACDht^ar%%my7lFb#?XiMMS0s{EG|eVy<8`wKX-hnT&;8F{C@=Eq{*y>IJMCM=yjlH zR9=TN<-N#zQMUo*0hB2pLO%3|Kv@eY%SQbl2HU#c-T_tv;96b}L<0d``DGCxEo(0W z$XA17Gq%zE-w8(^pObo>G+HhaX|kGu-d^^}*8xeQNRerDfxzWS60&2NACC&Z=Dhf# zP~rg=icf9qwNYb1rpAIwjY=Vkg`btwSOA6mN2sx<3N=z2vd#%!jB*Poj`AY%B6>@9 z3aytAk0u+X2YDxQ9y@|E@olQ>L*Dlvqtpt0Qt)fyKg@qcg6B_We)5Mf+r?W{dSu(m zA78^9aCIL%-aWLV-q&luV^!2-(RYBw@Iz zAl61kGl8I=iRJgyC6i4fky`e~AvF6`+G;Y|#~emuU38p@`R3Nal-uPV8}UaX`N4ED zx(3bvNOV@%=`vqDDD2``dxvnA=ck1eJf8+sPID%=3sBkv#NPvEcM#<(QNB_sA4d5O z)ZJN)AZCT5|CbQN0uWk8VL1Sg_yqBb?XvsqC`jd~N-9S|Dn~&oM?oq_K`KW~KUyJysP&!F8iX!i`-J%e`7 zpxrZQ_YB%SgLcoL-7{$S4B9<|cF&;QH(Uykubb$X2m3)|$#ihX0QYc%7IUmbMOqtP zOOznT*GGYWJ{ga1a(T9uG-kUp9yJ!_A$k>H17C*qTVE5&D;JY?Qi%K z`RbK8sVYP}gkygIZvJOfh?e<3@BK0G$H145Vaktz zFCPP6J_f#g41D<*`0_EX8Xd!ApHKoR-#~?k9Jf5SnlEJV6_=G9niwO?Jjhry5)%!2tDRXpWV%>~g@vC8zm&s*MR~zOF)uA-JX%&*lVPd|uc*jV zMP9D308SbTA<%=P)5$LxqzD{{H`2S2K7#Zaq^}@xJMG&j`~ZpUv@lxZ z0SS6U5&4iZNF7L{NZXOFLb?&@Zlrf3eFW(l4vc7){(k2B~L$P;Wm3k1@s zK!71nr2~Qg&Fqdqz~Nxn(}~=Ewk<&qC)6*w(|&r^pT@KF9V4D$E4~Ay6@@#nY;}!v z3X!FA!T{&w6nRfUnetBa0xeaMmqZQDq>u&ICE9xnCnID%IV7W~+oJFl-`YS;8Ybg` zSy0iiYcLU4V=;Nj_ZmIH5SXp8oS+c4gS>dKO1?aQZH9sW5G?-OQ+~-i~l2p;LdLbgWp7GXGkniH13>dy<$#G24_D?Mm=e&t1oSQe zy-Psv641K@^ezFtOF-`u(7OcmE&;tuK<|=@-X)-S3AotY!Z-3QN5WD(yN5eWD)DhN z`S7nu`uGcs;;rw8YrMC1KUXRI4T}q~Py2ebGNla$@tkyz9&Sk@~K}N61q|@4T zMl@%LnYD&MZz5G&*IVn-T2rZl8G9SiRg*J_NOi3zlW7@iE@<=l^%_kf-Qd-lOy;1| zgk6@{_-HPqQdX_27P_l*>37oiqzovuD6AJU3e5_j*|>6;PXzOn;b=`H7Q|w*L?T+{M#BmHjI^ETs&XoV{?(?PkxLy?Xv3k&-8fJc8 z_(x^jH-Ut1M&d5;>qrB+PIhqLC3MM*YhJh&1=?pphm3GVGz?i7R%KxrvXBBW!;pnx z$igsWVHmP73|Sb4EDS>yh9L{XkcDB$!Z5fPoe>rmG~5qF%1Ir(bp_#8Uc*`AJXqsA zSmQic<2+d7JXqsASmQic<2+d7JXqsASmQic<2+d7yviEq!5Zf|Yn10+&lw*8f&c^p zAwHaD)eVA)c!F>3lcR44a3Fo6M=pzG;sqNZ7%Lw&8eMISXdD0eqx1||3E!X4#^cy& zS>?u_h*xu~(YSkj<2sF~O(v6xfG=-hHlx{sk0e=ZK47=m9R|H#mut{l^hWf?Wn&si z;L_<_Uai*U!xjTqAkeB2b#_~{Yr}>aD5AF3MAqS4{F|PLM>=nB zP2fxu@dc&wM>=ii=;)jdp=4TZJdsESgY7oNKHIGLP0+LD9X1=n#%iqALx&v*Br^p) z0lnUBGWB$KcLH_6IfKDq*yHp0^oU5K0ADM<;qm4rN$0Rf+p`URqee^PyxZQI%-S8| zI=UeJqHSqOTo7-@)`kw@yDGKO*&Q0fHq8nJSVtAQ6%|IRFv|Go5t=yf_W)g-;PI6E zkkc;wsH~0BL3L4Rqfz`)6xwJM+GrHoXcXFL6xwJM+GrHoXcXFL6xt|hWx(feqwoVH z(rx2tk;2GQNKI&Q2;)=gez%@VCZ>7mVf6!unw>#%b83UDbVhno?qLBzyl$FLSc zq}YmeHIf*;jC-lD@J4v%5f@mlVO%4XzoL;gX7h~Yb2tcsbxE4BhCEyRh1XjU#qWty z!Rz&Vo2V!@d3|3#oy%r(r+yT_?%@Vw_GQ0vSw&~M`OcQ>NHiHF_tMS-blrztv3-Y-t(2$+@ zZA1zY5h^pJPY}^6(pn`#IM{om|L_mdRD6e}rd`sE1cRCzq;&euKQo!e`bV|e@v-sw z@o^*j-r^tCVc$<;1T$DuL-;OOUoqkY2C%JBqf zJOOz;TA_op!Ga4rolPo>yGYUz}EYsVC2%MUMW!k zJ8!)ne7Iia!}aJX&fP@nLK;Wffpi4vEYdwl??w6;(&I>9L!!8K2Rcs5yaS!5J$gL& zkTesn7x7&26L6_UcYw&UuT^!sa-Iorf%^^W8}p}rIagm_Q&ZbqV`<18`sKAvjrtq3 z;ZWyylN2tMd~zWPi^b&(i{jTh{C+L_!UV$9Swn6zS6}aNwKOEOS|$#ZIwY~HYj?rt zb2`&Wx97OSnNHiR4o3_;Wj*lkQSnx+z1CEM`eZv2)QOQ$P#;Avnh6|C3F@O@K0R)T z{02n2;&sCs#qZn~j-guVNQL(l&1?sMl(vGe6OU3cA2e@c43j2{n!p1Rp8oBkCh zd>tW7-3ksCql%b}K}O|p1lXdSlTkke2tEr2WapS4qPGD+7*$2Mok^^WHaO}kN#Pt-OM-p=d zKP0@Vut2g$!2Es(@#b_JuT6{1<(3-A9vgFl?G>6u*4(raEb+%W?Py~QyI#x$0~X6g zQB3=wi6$c{y@56AbZ&O!rn{h6<(f|EPB-QKq0J^AV=i}T&|1R?Zlcf+x_%UJOA37~ zv68YK1Q&-4mpUc#8BP{X;R2>xR!wNYBngN(5CudYj{@LPzyk%qqX2jm0FMITQ2;y& zfJXuFC;%P>z@q?o&=NeCF$KVb7V_nwo>sI0_WaQ~I3zGh zauFl;A}4y0&yP^3Kem$UY2#3-4MGb{7$7E*<FN{A*NH`;72X1}sNoyqPi|72gb zKHs>Pohd(<^?0_6Vw8I$eDKu$y(p$&RWQFlg~Le>+iNV0wYTq-L{Zw=T4b!)x=R#a zyrQFn-Q;vO-0N_*mmK(4YIiy~J#GXEJtUq6>8L@$OgU%?ct}Em3ys)DargbRv-Wd_ zsmVLbW8&%CPnT6LRcUd+BgS} zC?#-}4SYM^0@NDPlVx_J!E_Sa(aFbdY=9EG0ZQx!p&e-iX&cgEq#Ka#LV5t{A*9EU zoE}%yj|BU1B4x}SU zXOZqfdN0z)kRC_+8WJhv5#;iDXcI~ayDMZ7GlAA8)Yd1^`UG01rzU`W6KH(`txur! z3A8?e)+f;V1X`a!>k}OLNH!7`CD7*#a^iB$U>)SUps-+WQKOvUIHn*UV7L+uxp09R zCCeV-9}=M=DONE>8YO;mr~!&+bFJHj4S=&a6EvN+J8GL9_H4tU@^2zI2+iY8{tMR3 zz#}5Rg-F3A)1^H4my$G{o&7i@M@%=ixE$H~+;~$Hbg}%nDCX+3kV=hd++_Woof;9m zI+JqYUn=8tl$X4oRLYHisg%b{JZC+4&L_ZgnuU!_x02_);%K@gF`=Z)gnT%iovbX# zdX7#M;V0lADlKUfTsRQj68ELOxo#yVPC@ibti6bw;suG5_bT}Sa;nF%5(H7wx#hEQ z?6^UAq`^%(%9vm>L>x|s^QXK>X{2_f5u|NMhmmeTx(n$6q=%3mLwXWP!P+5U?GR7} z8{yD8ZLg!&JD^l>11m}t#$oWl?eLv;%u^Lz( z<$u>mV}%w`EVgdK3Mu>r8==FBEzRSS=10e~4Vvo=;XspK{~@iW&d1%KU_74#go9!D zLOn69_6WmFlvrO$~c z01=DOy%MgBz{pkz{grkM!xRXl;%d3nC#{3F5omEXYKS3$XjK>*9yVc3c=S3!Pg4G*9yVc3c=S3!Pg4G*9yVc z3c=S3!Pg4G*D9mMw^8^35}^eNyb@9e(jZzMM!pmI7UWyd11i&s;8ybWd4x$$5OE%J zt!i-`Lp{Jy4=~gN4D|p*J-|>8Fw_GK^#DUXz)%k`)B_Cl07E@0hI)XZ9^MtQ9v%28 z%1PsjBabS1Qs%3@;C!%iN>Pac|Y<=gV_QdJ;v{l>7nnt- z5wc6qil3E=uxsh0<(%*sA1|HTA+L3rl+E#OA?D8Al}jxrKh7!i+2?2>KM*nnkqlKapSr3i?_1I?|rZQ)br2( z<3AD)TsfCfY&q^0-lxoETV*cg!;fP^jx&si(8_b1Hp=HVhAE-BB#VXQWGnK5a@|35 zOC}I4JFLSEEpE^m=H&CCxz!@C!+hd+aOFJ6BAzHhNo-eY7QhkYd)4PlQq+?SouN!} z4HF4T2;2G9rtkcgOeRymrTqBb|0Rk+oGc=GyrKWa9{9a8DVpIRZ^se(99G zPqFW~zvaC3Rwt!z;iVFiFuL?D=|fT$IDcHYUAR}Fa5|Na>>66C>BY3YrE;3H47GV4 z7a}iF2QjrlJVzd3q5?vAD#uWc(U$!?P(fIJ2P##uMWo{bq{zS2gj_ad@EoqMxr7AX z%6FK_M<7&?Sq~=@ePq7{*$nCj$6Kpg)$mpXzDVTr)-t|MRCx#w*RrDOQU--oJ|%MS zW%${(@}Y9LQ<6SJqyc1Ch(x@FN@I3gCf(n^&$+%B@OoV?tSg4>c7u*R3`0oobVg%E zXA^exI`w*-sH4?eL!oAnixXSnKE))B#p3hh&{unn2}ek3^yL?jtS&F@ddTmAkTv)SO#8^PL{L1#8O3^+$uqs5UxTAj{f zak*;i>fJW8UI%>8==2ty5nGO}HI7)Ze2;Pvt7nH=kkR3PDCj^euwFs4%e-WOR-EZb z`Rbrr`5J(#^H_fCp7M*wH!;u5nexzymINV zSd>B!*W#(Bom67Nwlyfpw?6_UN#}J?IE}0V`@dV)%XP_0FBBomY zeq$i0**LsYtNrHpMd|vXVYp0E<)?RTN~KtBxbfc8WAzy(HfDRY+A0%Oe#Tp0-+e5d zTuijKUb8iqD||N?XzK`ubmdR~x~^_u%1;{}Ax)R(uvbuzYVWELHyzQAc_}H@ObMeV z+l2DsWkxSyMo71jwHXYZD36-}xS=XT{DmgfiRoLZ#(d_6w$|pBo6_K$&P-!t;ql=O zLt~$AZfQbFC1Pb}nh>y`a0ZAPrMotF~dvHxVZ^M_F0h}XDyh8DRCnSyo z6qUl7IZ+eGo6^&5r;&xvA{0fPwGzTF?y>i%ke`{G;0 z+W!7O62-R<46K1?q%y+g7ZJ$J+Q&yqJ`X>^7sJJ;YR%lPuNQeZNm+On_dWk&e@k?@^MBat5kcCA#H@vHG87bi@a+CWt)7mSU z%*U7htGBK$d~<6{bIVzU+L~l)d#tmwGgD~yd@&Y;mhQsQs+y4w*`TZwzZS?8zdgAz6`@F(7QjD=ZO3zAw1|sUV9nJgBBYj zZ6AM&)gk@i1La2#9%NGostnCrewL*G?zZyR0q#}(_f<|if<~^uoBCf}0Hn|bl`NVf zIIANXX_8gt@*iDwRkdFRDXjXvL`Bl7@ka9Nd@w)i3o2;hVwaf*bUAB=pVud`a8${i-tcte3ri-W!U4fJBjWm!>Ikrl8(l=+}@!emRF;7KI90-n_; z3P-Jzy$sXrGT^?Sp zq-yWszCiEm7r(PwH_gIRBg=C-tBT(4La%y5FZ%#xI2Cz~7yN=$P3tw)G+EMitXtlZ)@4UeRq1azyHmWw56jB z()(Q&;4>ymYT0rkCk&BK2l8&xnFleqbOy;FDpXpdn|+vHKfc~qSp%W%MRX^18SLT# zU!XU2^*}*3OeB5yfitk8mQAXv7UfTzZcnHMq1T$tlSd4OhYz1-Vm25Gxm-@`nLsF9 z{^%@@zD2xFELNy8k>$SnP|)tm<)`0N4@-(Qhr$E-mgaolhV_5@OJ87pUnD~Q%Vj*= zQsM6t75-}nW4lTsg@IA4a`h=OG|UGW!*Y#$=I^4M-toG zs!hso^zbCq)Zh&;9Uga$#kI?U_dYh{j&9CoBXxC=h91ASrpCM5XR$ODj&CazfM6@< zC?xbK*rm#4MYoYDlk|I{JC!^7_38tmtw&-^a_kzdQJMRYv07x%d%r2jTz9 z{xu5sEB(t>V7M%!6T~f33b*vH(c}H-qeotLc-{L9RI8vmpMeyWcTvjPKhzV#4k8y- z2CY^+A3T^yU$HlpJSdKi-u&jVv1)9?Tb|HpMw?qjv9)_pp?&on z1qJn#{ACyhIi7PBfH(mi)Jb}l=90{kLn~lP57|&}qi0Yi?(2cWpI((Fb6bi@spebB zKm)>oVd@}P3)=trH(xo)jN6!@8mt$;1GQmFd~_A8qZL?JvrA~{jO>ySjVFe_lASD~ zl1@@1f3%9Dq)q5iPti3r9$Md}=o(Vy94OOT1(jEhllYdTgJ$@W7;W*6_rb=Q4hi&y;URSWq*YE8ok0 zTV>wyo@V{#U5mfnLi}T;zE(g?$(>5i(?CViElkS$HHe9l(nWbKa#BI-l{`n3NN<56 zDKUu~DtC!vlmZ?SP*1wNs(X<1P$5QTE1u&L1<$&$q~IfYyZlaOd1(xF&}J#d0a8~! zYLdspfxD)l?<)(o%3^{N*#M#a>1A%DmUOJK0dc_PuOc8WheMh34G4&1orrO0$RR4& zN1?#qMJTY(hX7%TiU<%cR9jjT!*Y=D&rjzX8gi$fmV<;pFNX}T!socqs?03CBwU_Z zdI2fqR=DC=BjTo=62EWBD~0G`(&Rm|j$dVXncRJ663e8Wo%@O&x5bKJ`{8)JTwX)VTZEko+&U|8 zo8n+f^O$+EXec0=m?&xEFtITOA($k?u>9yX5`eIchgmF#Do|*`dZ?S#W`}GQ9?cHT zf#NUmqFgdcc#%w7{+cA3KQ%Cc_RDa?CI+<7SDUdP>cZiHABs}UgI6=Ua{u&;20#8a zyu3aV(O)o>O6OSMhSnCVtut=37YbLF|K*Ni(NuNrL~qXRsj;OIWPFX=ox~!z&)2xK zt4q|hxA)<_Y1(+M+3CpCH*9RkGO??3`zroYhhSaR;|Oz&&zec~{*XU__y_R`m46UV zAZ((A_aL+ph(To;9gZ4asy>Gu^paulM=UlrGEbE$X}SJFIm`Z^3*}K(s5;PpJ&j{i zZ$EM8ou?NUSJCk%;S&lzG*<9on2$sbH&_tHlt|Yh9&{!%HVOr$!?j5~*P%=~(Gle- zlL z_aW~=K7xEq$tRFg{VYLY8z4a8ciT{*LZJa=6$-V0Li_K?!qM1%f3Ezi9JU1@ z3KtuxxX>tn6BT}6uIenG(sXw8Qs_ehV!#eDFpTz^Gu49zb>OyH-4toa$AjfOG9c6@ZrCY&Nw7qc!J@ zO@+5Gheorty$dfwCU#JMS`>G7_ka&~Vygga7Dc-a3?UfGK`r16AsT5ECG3Bb8A1r# z(W^rEdm62`Kb2(l+2j9uDxYmEoH`1|taJA_czxd|rhtunXE{EKj;gP!UU?;MfeiDm$)XbOgUK@bf07+g!KaA)Z|5wsG@*vP+pHA zhYIjr9o+~o?N-A}yP=wOBfPX5E4|$aFYQKnX*a@4yAfX6jquWLgqLfe-ps$xg8-Wr&m(S$v5)`h zDE|HF2k7tn%P)w7HmA!xptrcc$P^}On*S};fgF?Z4 zm}%}!Q{O`uQ=%ztLL8u~J}-(*g5d*km*)Z(RYqUdZW|2c8#DH3EaqjKw`~X^w8R>Z z#=KRBd7n7wa=U9bn9UYbIke(}Y}e(n^+4}Z*I5D;KGDFji!^eg@m|6nH8dcg>_4$8 zYbzAWNedIQX)CWO$Tii+Oc6DD97V`C!(iOt)v_^z*8&l}f|%vllI8j6XH}--NA80uv|d zvnI21+-Wj_ha(;-)!bTsei=VNnycn+1~1-K?f%gUWy(=0t;#fIl=D`}!BU60RP`j{ zkHfg8!*%H%@y=OTj=QlRDp^e0Lqb+uwD2~HWT zi}I{>3>4n(Z zIx4dMRI2>cYPhWAnGtLOZmb;Up;D|#kg=MSPSvg;8zD(PaFuG~6{=k}+_TV0^2(fZ zF8X^csAY=oXDJCWEk%kga!3hR6%=0l-fW*!Wm|9k>F;JXZ=QMd(Wmo5zz86MN)KE@R8Wh0@ygcuZEPXA)X_iN{Di@ZNVHAFH!vPvUI25 z*df$!SUEQ~1*ON7xu{Oxt7ZT%)s$4slNEAUO!7L z=G}onZJvTh6N$FEXe4Z^O{Hpg!-@tf4v8WT0&u_Db5|0W9N!}!UU71lxV?J~8Mru-8#`$%J5@|=veo+DfB(%r>!fwvn?-R7%1rrP6?(83%D=1ixM7Xe zwe`j0VoScC-rBTM#zjEB8x$z6rhrfd2#r+2Mpl(oPGVAvrQ7Y_j!52?6#2)K9%xOfP-cnG+7h@J#4 z9s({N0xli`E*=6d9s({N0xli`E|O0c&tdk+wpU9tO6iS!cj(wRt}I9wGu#NmlV zJQknohW6)5rUrJ#BGK56p#+T5SggA|UZDiVcGo4lkm|7F{0!k|50svZ91b=$_JAqeSL;Dq2*5eECl@ z_4DET^>>H5>_6CFhTP z6XY;9K@KB6YZ&A{4DK|HO_0Ob1UZaNki*ynIgCw^!`K8lj7^Zk*aSHYU_tCXgTjkQ z5PS4`fmy(RR)zm8`Z5dn&jS9lfd4GuKMVNJ0{*jr|197?3;53h{_;q{9VN4MB%5U@k5G9OBfzz~@{<}ev? znqo3tEJ`a4+htp}3#ad_l7~H*#l+tUR&uW!aEwsS?K5l4znq9iZ5)7nJ~I3U4m zJ<98q@*v8CkZ5Dd(+lu+%Pq(-K8*W^$uFY9cdNoFZOTE=K^&s6mH$i-&8QT!Y(LWe z3Du522w&(Z8XV<6D1VQBP%ga=9H27}6klnD(XDX;087?50IIV#!V0jqueg0+Z9eZR z*5-8L{E90Zah_`?4cBQR9{1S%&}kA*S8cd0ljERCUg+ z^2t;|f}+!$OtihiP2lx5b;C)ZHB69`px8FPE{T0}D|F>fyuxHexal(3vW{=`lOZc> z#_}>l|4Is0x!;Ha=@8&E!XX?2+(-_}UKv`apk**x4kLHZhRf+8uZ2L_t2z;W79ny~ z0pm-O%t;2Vfp91g=YZL<@Rbeg)@``+&OdO*S#*WR!TAbaxL^3dnprg|$A)ZBPD|)g zj$IGq{kihFF^%A$^nxNx4kl;_4wf0^o(u!o8@7VFHY?oIChVx_A2i!5=be@yWZ*5} zm0U73UJvcSaicgdmREgOx-V!)Em760@K8{TFPRvM5D)bOC^8TAJ^V9Y%HeheL>8w* zhidB@0)c?n9Ee8!Q!OoGJTZ*-o4mRNtI8>hPs7dOw(AXEUuR!!SgSQ{!~vT&=Y-~niW2cQ8SfChK~8sGtFfCr!f9)JdT zfLj0uD%ij^(-CN8y8KV4(qlIvaYG2K>qH33yXTkfE!I0I|RM=`g+dsfqL&M}mIu(nrKW6g# zV_ByIKGgyOS^hA_JBjnW;g0EC;f`51x2dVX?po!3DGcI(&DHwS|LN_@gX1i&{MU1M zba`})MkC1@X>^Wmjifm(S<;LSTe4;AvMd{0wvLf4-^j8#f@cW?6IjBPn2mSC9SGDC zHcJuWm?TR=Hk)I2$+|XyP>Cz#s!)|w<&SKNGHATN*Wdd_BQug5%&tZAdcLDyf3IJ^ z?tcB=>-E^dTC}j$cDKp;L+$Of@@S9emW@2$G%Ko!h&ESRI^R^?A==T?m)FmHc+M|P zeqw_m(_FU9N~#;4*h|v8t=1)@pPbmAYx4UFx7)p*rsz*sCGeK!Mvq?i;R*bI#3_0} z!+2efAG43+%3VSWQLXjTlsFTqrK`C(>DkF6mbQAEQz9U~>=)O4;2JsJmMNV}#zATi z*^eXW#}Ta`N6?QW=*JQC;|Th31pPRIejGtRj-Ve$(2pbN#}V}72>NjZU69UFk0^7p z^v&2QzJY&TkngG2XVsu!v%od0;i40|XMt-LxMqQC7Pw~R-!1t6JxJ$}o=5sR(hra* z?&QQWTBHwiQG)q4#nk!h% zS!uJa%D;&RozHK=ffvi^q;ygbRcNtPT$>)s58I^uqF&A{u*-^B`4A>qi(Ic-CrwH_ z6Lq?!az9N7b}5sRVJ0E4)%kr~U+Ck$Fr$pi5y-5BVk40%Q8bN)dh$yE!YR-o`$`|i zkUoqdeHcUfFevmvyU~X+qz_|AAI6YAj3Ip(L;5g=^kEF?)5efKj3I~)2oCE*mA0Tn zf?EM+07q4=*EslhT;tzyeCjy(cO3jX4*nen|Bi!8$I(~D!N23+-*NEoIQVxQ{5uX* z*D=>I61S2X#D_@@S(VZjtkjZ`Nd?^m`^o?$QeY;U@sNa!TQz=o#`C3==BClKlRZ7y zGu*np3>(*5it^W4lXG)RYAqIPcAY=d{1G`X{eaJB>|8T8_VTUk1BUhu*q7Al+L~}Q zW5#p=LhQk1xz1Bs25nirXU$R_SuLgu^FkY)mfBa)!EwWqIaE8Q^HOuZn!N?Nx_iK{ zq-FOZ?*re`E_(-fl4euG%4W%(WTUYQ_jlo0%I`*gH}W0G&muodBVr$GQ60)YS&<`B zJ5PEza^jI(=>S(cz?BYgr2|~)09QJ|l@4&F16=6} z#_?29gVM(pRdZ5~U-wQxV&1eYr)>L(>sH$}=;)G`)4)Yb>mVH~D*{3X`!8EYUVdJF zF+$APn+vM^xKmVEjSa|5EMNMFY>rY1SpymB=!Ro=z#Wes5E^dPUdHnP1& zvUG2~fPi*tXi&}Opb(ryJCd5s0l^OZ?f{p?+aJ(#n*G6^Mf-zB=Dk6-J4k0cgln@u zkeh{zLm~+m&A2CKo2_lE&vrL{3MVz(Xg~YsITo-Ef6f&4lb-=3xw{% zLRcUm24h}Pk!@{ZX<6P0vOrLG-%6A73JNk4%bAaCfdCa@ye(&0%$Cy@v#6n6cv!=c z!7|x$mV@M)9VD6BX;!e-V=e`N?aua|(P!?lmN4D2xUB;gG)+#Vv8f)t}f$tG|7j`j=C96*a_4 zBF~a&hv&ot^?dF39%>o^+7j$K_F_XKQx$aLRkF{dOx)aQlT~?YBd=qf7BiSjcfUOXc}Qu&@K2 z%AqaV26Hq-AR`}<94VdTDD^Rf1tyCs(!>%Z$L5t8={kK*R%(}M??-VyS+#|9j-?v~gd_=33+kCbMe-meNkLIGOn_xr!Brz(d5)Z6)NpP! zDqF~cN_9(d7MHI?w8DAzPq{bxU!q}EVu(ieR{0#Tj#(63<(E?=8khLxtjZJTKOPsIB{FLO=bXRCoKzL# zB#)F;u+EimFCU6c!zh_a5v1lOw@)(etVNzg4`~_*t_SP_3^CjYNE>4#fO`Q+%Rq1f zkgm^?JUfKKXcPPpE@<+s5qV9XwW++*2Ht4{@3et;;2(%IiZp|C3h4~eBS_C8y@K>T zB;G@51Mjp+O~dAEl!^krBACMeRX#tYe8oF*p}EBAXC=0Y{^BDz{}_M0ocGVFd?ZS) z-4iH&eDlD#_pJ}jmesIUw1-;%V|5lG;}&g2TlOJsjkTwgI(?8jq=tmrC>fbNq$;EU zQZLd@q*lrwlt{6oNTPgPT(O#z*VAr|)H|wBihC^ZXx1Y= z;7OElSXxp5t<>B$e}k*EFaxL76_&ah{0BGo==D7tqd&)w0RK1`phq(CZ`H=s{n3`= zEUvC`xvFc5ojEoXbr7WtE@t)gKuxvFRa1=)uH1R$W&KU!Z3IiHQPlMyS>~t7o^F*r zUJWy%pf%G#;v}*^D3@mtBN#u6OjC)|8W*J3>eWNYYKMJNOR{#Fp!iqFwmNo z8l7_H=L?aa?-Y-wuC?2Ln`KT;f>P9ylx)rtZ>LU&J6gjtIPnjURET5ruwEbiwk0pu z@<56qwIuqpl2k*ATnFS`c}o9=eh2*0nv}uVDA>;dge-g-o@Rhvk1s$lbMme*%TH`& z7w;ZkI2absB(sAz?HX^|!JBpv#tz=JgE#HqO*?qg4&JnbH|^j}J9yI$-n4@^?UFa` zKvx99IRUHG!){t3v05RqS|PDoA+cH^v05RqS|PDoA+cH^v05RqS|PDoA+cIDiPZ{; z)hZ=cJ&u=9qa?SYTr%zI0o;T|Xg~0h{Rg*Ai3(eIlHPLE)VlP#N@rzJU1xhQ4o|4ADy4GNdo_(q?p0OGd&I-N z2&w*r-U%JX24$Khl2smbr1CB`K`KZjj|iZMg$+n;xR8Mxbi4~GJ7^_n#W~hxpr3ls z3q{?@168YHS5>8mN?oQ6c^#+$PMV2Sl610LNa9PEva6{re%4c7s&90S4a{%t*X#SY z&Ks+2Hl5oi*3EB4w7#ve3;X#&(Oue5*Pzq;n{eDhfB(E_qBpBt4Q``hxVK-tNQFfI zbG{!J8~CDId!>m^Q&5aoKBNDQ{xJH<`k4J~iIOQhfk~ke9-}UW_;(_;fF3%~MyKv$ zI?)rHT2FAICpbY9CwhVtJ;8~d;6zVwq9-`f6P)M?PV@vPdV&)@!6|zJ^?)?cQwL~Q z$8I}%f*n1X;Oq0}1uj`Wfjl2~#<) zlU`Hpcj$Xqk@{%2h)&G5(J9&{oOW2^v|uVH1KVR`iO$i+eYqJ=qxJ?06{kkM@9pXm zhTiex@;_PnLUf_KTbGUp zQuT(Ul;}J{y4{xy%dixIlKwR-4gX-s6yHuoigsw1Nw!nsPbhc#oF=EqX0rKg3F;}{ zy?F8X#UCC25&pZFph|DW-G)5XDMzoox+bJ=1Iri1V(q1|G*zU~MDQPwn7p$Uz(P#9 z7x}%|XVhDaPW@V8S(H1)#)-Wh9Xee{$KHj1dPy9A@x|zaFWnP;No=i-KBnCfllnG# zBHFgMy+ij(huADOcYH1S>t~*UKWs<)Uhy4z5>-SUpFp%_i;1pSJXWsTWvHl?Yc6a5iblfsFK9WD|K^*b zA8E^*Z!Swz`U3SUmbRxowhl-5vbgqCy_#(*RcYj2u5LK+KGO!j^+a^l|Mc?l8>4MM zdw03Uj!ug&L~r`MxU}%(ReYAO6!EyCh0j*`Osm)O)j?Rfgmsy@eB<%UmshF@wXsea zi?y*m#(9Zpql&>iqLE>xPEagk-C4kUi>-H1Ydyo-Xxs4{QL$y&kBEfE+be0L$I+3` znI6^;qA#vPOdvl~N!v|GmeVS=W+X94t0-I1#z#k(r4=R5x37v523-mwx?xiRv7tT1 zqG~D>((6N^DPv>`lLbr+rWT4sN<^f-|A&a~WHfn%ZZBXo8j`oZid;;iD>k+2memLZ8UZx901+?C2p>r&unn*6-B4LZnyWg6-rXnJ`>cmKkl#5>UyoJwK{|^h(N`u54qxaYd$dI+ zi&wJg!C=x(hy0nO4O${PM7N4BE<7zBh)(Ekdw=SQ?#|BcC%*c4PghsZ{c7XxFK`b#=xA;rrp?Xt;keY%t7Ben#fQ2aF5n*RMZvWc_*} z2nN=RrSiaz1DU?a6x+yJRyGk6Som^EM2))*l0%49nqCoIo)wV@gupH;M)%>lKc?im5 z_iFVN6t%sm{fa<#>JYTJpbkNP*-mOVEi!{M7MX!|2Gr@49S_p9sti^KiFyo0Ow|73 zdQXC}?ebmmB0>M%MT#fv0Jq)!xjW9DS$IPu-csJqgUUvm9cp8uRVdUxFQKHQOmZm=Cs6?19PZd?SQ=9Q1 zZL5;`-3@?y817xB4}aWac=>9raQ3R6drNi&4dmC*a~Isvk<+Ke3z0~|w$o)?$56f| zuEpc$Yempd-p=iv8yn%&Lu@K}Xm-d=I&~jHlh2hPJ*{?V+C>(SWPfG@r1=`bVnDhs zVwm7#ZbO9vPmZ_Ate@2b~{& zTDeQPUpXhV*;trcC0&`6q%3EPjZY}I%HMs;b_s9b*msj4r^dV0$k&0yb>Ll^22g9# zwix9}ynF(%7qA!Cwj;j{c?vE{`9b6dIX{g2Fz0t5zXN#^Njs4zcqibUxE6sD^j;Xm z(3z;t3mxQSsu7;iH;V+VAC{Zg+= zcf+{b3rND1N{RqRP!i>D=WBNWQpumf+xPO@7l^E~oWy(uc?b2Yx+5bQ2GM}=3mRJD zB2w?s)-M_Iwo6k`cs-d)J$T5PEjQ{)oF#Zt`qq2g8!(17OZFqyQ#n+!puig@6tqD! zTcwdYv1O@TL6HJDGckvL;EukVl3HuE=43cB(%064q%bJI*FU8cFyQ7&dn^YSz3 z++ZwdTGB87&>+N`y!7mBd)}I~)Zy}~=&j=>{Ht&jt+SVwRHbHQX4jXMEqrs|q;8-1 z7pNdJveGlwhOF7Sxdl0Sx%mmW`;qAP&E}DP$M+^d*BI>U>wA?v{e{TIu8K&{+8}g2 z?$!-m0uwNUJ}F0kX+OdM<9>bB+H?dodVL|OBq=#1)tZ-YFU-r&M#PRxxX+50W@rDc z*q)MHaK!Ae7v|^N?Zrl4U43P~)tVtJ=pXSiyajV?EqW2o9F}~qo=!nVhJ%v1h>>Xi zMbHH}2uSm1iU8$CK7hOqaGW8rk@S0ZD8;gXV%*fYlkydKfGi#;Ux_@0m!Lc?)G7We zavm&%0gqR9gea}x7N2}is}KQx}xAZ{k+t%YB_CL4198wA4N_{PK=ldnbp-B(kM zkZ1<{=?~{uI=!As=elu6b+xa3aPUBe*_?D_=gywa9ivCVOgV=J2NSSNOFv0fgaJ)9 z9onjwNWdOA?DWu{Vf>38cL#$*I>Mn(Di~>NYiMX|+m^R;V~?*KXTap-=2~(b#q|x2 z;!LY8*Os22Px4a1pu&HmM|?xKUUyz!sK4E?#+YIJ;hO0+_pLc^sxU>8%t;rM(^B3_ z9Z!34ZSLCl%xBW?P5)iS@r=J@Ix>H5NwIt@`{wNP*}u1DS>4tHIWxAtTvuLpzIWZh zf=h)j6kRGlSn}P{$qUg{_K3wd8x9es<3Lbx~ZnHrla<7-B{hj_3t$tb!EH0 z<@$Z&pPJg5e%Uead6@9Q8cw`HOdx_hIj^d~Lqh{B8c<1nyX$y?%QAUs`sx zTx#9jdSOG(hVQoJwms3_)Baq?T+knUFto3;t7}cye0Ns&rJh|IbsK-TX{h(MzRAr; zx0ttF?En4N&uu$3@a*8Gq1T50ynV}P;pnN+i#sZJyfk*-Saj!GyI#B@ZFke2P2;Zd z2lqa_uWa9Y6aI;NC%!Xjo}8Qf)|4_eF!kK@BVlv+D>IMnFWmp>1Gmqn%w9Y+dH5h` znx$M)u;`KhC@g`R7trIoIggXVL3j1KUKxWYiJCWH@AqlW8?lS}D(BZ=JM52~Hz`Fp z-due?Nl6pM=+(l2;|-JpqK@-Iafq{=*C}T4RnF^`uZY(3>m9+vhaE$7 z|Ab>Gd?Gw|Dm-03);sD5j`a>ZhUcdp!I8~F;r%BMPt1*m=T6KXJL+hxZ)|LA85njn zwsanw3*)z|zS+}qWa3cx*!)cW;n~T?dQZLE=W(@+>>L?8)uN`ea^lD3!Qc1=ysx@P|nsltm}tk$w!hVdbQ9E7Bo6Uyt87dJ}&R zIACkVYMz59PX7{W_x0|M&M@`!@xZ zn?WIJH}(G$$jyWP6KJ2Xq~;vx=>R>+DNCagam7(MpN)Wfj>}dUz&poa5*8+xG!iXx z15Qz{dKaii>O?&bS9gg<(S$RK+&CKED}2H) z0%E;r!Raj?~q7z=F-56gsicO+d^kF-3i|7|y#Wpb@2E~vV79(Q27!^Cj znAjS7oq z*zIa^F>GYm#ITv6o1urHm!XfLpJ9NZR&GfB-OS}SbGgl2ZZntL%;h$7In7*7Gndn> zmD9=P$@+J@n!EXTj|$!V9d5?wW_)hO=Vp9v#^Yu@ZpPzgJZ{G0W;|}jT)v0P z_i*_hF5ko7<6$~^m`)zX>tVbe#_M6c9>(ioydDiNf1j7}dl|o%@p~D+m+^ZUznAfQ z8NZkDdl|o%@p~D+m+^ZUznAfQ8NZM5`xw8E@%tFRkMa8$zmM_z7{8D4`xw8E@%tFR zkMa8$zmM_z7{8zK`x(EV@%tISpYi({zn}5@8NZ+L`x(EV@%y>_0GA)&@&jCcfXfeX z`2j9Jz~u+H`~a69;PL}pet^F(z~2|(?+ftv1sH#j@dp`yknsl@e~|G98Gn%R2N{2m z@dp`yknsl@e~|G98Gn%R2N{2e@rM|Hi1CLQe~9si7=MWIhZui|@rM|Hi1CLQe~9si z7=MWIhZuh+Y=kL%sV_3q+& hcX7SDLYSqBD_5YT78r{telGo^xhw2Op3ZkNb^xi~z`O`v?P*gyQ z^bXQ{l@dDSPJ;h?);sIm_s)9jT-P#WzL{^&-ZOi4`R&O&WyL2aFQ0@U=!}f?BNYgO zgRgLif(-oGbnV`QAQ1tXM-pnTgUeXA;g3B>`08zidfV8FkAh!PgWI3AYMCRXrDUXT z_=Tvb*sFDp@g;>M%Q*eK8WN^`UEya~=1sQvt|ZmOHd8Nb-r>;Uf~~`-M=o}|WEwGb-VqlyA;6DLxV^i;GmI|*d#y7{5KD8-$d)}nu5j5jrsa#ZM>?-Ql%1a1+dbI_k5 zUvcL&N|z7~n*l2QTD?ROiZl#dQr!+1t(PsKf#9JeK5w1sT>Qrb?5KG;0nQ8X0= zCr{+AQ^;r?VD-dVvx?Z{HELA`%<{k?O4Df<)`O<&v@7mKl<~^neNWPVJV4-#reS2z znq*rPoKzu0`YF=n?(paQMpp;YHe(WKqSYVIH{K|BgBDen#j2L^r}os(R4PaY(Uu@7 zCC~mfhSG3o)Hgk6;?hS5p%+!&>Wi#s@Xd;MZ?6jac&lxV8jem4Ks+{HEO^X7yCT?P z+;aG8mLCq?rl_v|?dvaMEs47vtGpI+YvTl3sg!&@R{13dSc^_yChrd4$lx1j2(c83 zztXI*Q)s}dR%LG-l<0pN+F_WcFw;%i&t(;{=5Mnnf!0NK#n)me`9)X9bWyCA4xnK7 zA$ar``7$SRT5p>RZH^?6J^vb4$Lc3aR<&xon~g#Klu#-*aKWb~coBTLe zA;b0@;{_<#`>O7+Z`-QXH{`A9=pd+uDHQ*qgw@lg;peV5<9R4J?|d8IxLiK(xQpP% z(i4dJpjMG~WrWS%JA#}9O7%$e$vMe7?2ysQYu#gRGNGT+y`D=x5kN|3SSMKqJ?vA@rC^aTKLt zSDU3%>o8zqss(RF%c&kwih?Ly+t8k@j-F1m2~`p0Fc`(pq|&b;paX3dUUT$4`U>Ex z@~~~xOmdpjVTlStOtr1@i#KZ>aXXN}gJ#Un$1d=-&J{^!le@36cuV7vTBA`y!UDO0T>j(y2&3l4q zyk~$sKXa0VS(G#h|3~`UDREZ_U2Xqonq6d^f$&^`d_N>xf!$Eddk%v94aia}&)Rkj^9lT{&CGygFW!T&sf9OI+3qwHE z5CVFu_HLlaG`7NNF=lCLDe97ted<6pO8oUZ;`_|dqpTm|M{86L!*&hYznQj2Jn~CJ z5QHg!XM=noFb|j-f`dmSL>>S?gy)12gh&5xL&2;6`?Y_+6C@_QOQcMkhB$;U-(Sjq zMJ2q7CJsIBAdV$GAR#bD=p_*O`_^&uUlu|4zcc)Ic+X^kst>nf2e2Nc0~qUA{-IK& zpvU&gwD}&s4u3d`k3K|)G8x>Dkb zu~oKO<_iP&d6o@v{DwFs9lCVXd#F3#aCC1Yl2g6u1H*%e*z5H>hufnHZ3&_Z9Vt?| z`BKwO;lK89i{~H`s2_gkWlPlEG>e`r)~jcA@$v8JxMC=2SzVFU5~%&rGP}=0%f($e z4ScAhgP8=6mAf8WOENnrYpRcMJYD4urlFrcJ;&kcZ)vsbId(|RpzzzM=wC)}d8&54 z@mcn^>=|$>C!x)IvgmZdioQlek0$yIQT&>Ec7EGr$io|A_n&s>=?{4x;`)5NpILG} z3e8$}m2Dam(>v;FDDxzh;(NW951i7x4hsj7MHB=^M^?dV>w6 zDpy4%x~cq(v0K)5f9y4tyt6YV^qmUT;xL8Ub&|2~RN{QI2_AX4?GU}AWhBZ75Z$T{ zVG9zPxwxnoBJ8x_`h@ZvIPuiztK0cZZd2#jLZcXk-52`{P2`vvdxcdpzu40w@rV1{ z+Y97VtTfjk+4JfK{>x&;^=TTpIxS1(AEuilJ!ngp55uCP&21{?)2?cU0?~o^JYlt{ z>2OV98R5=YxrRW7t|N)u<8YZ~`hnbeOlO*L@r0tpL#!$9&+-vuNLK8*B zr)KuukM>tl_y>Kc2T$RD=F<~nww8w;WLqMxdhsg`-NrQIWk;-r%L)j#jA#&SH+R6O zmiFX_@s(cEa79s^+6Gv%)alnca<~&@r)>*g*iU{Gbk}8d<46r-hqVjT)cBmp&F+}@ z&M?3VOZf!StXAWzGM|2agA-LxlfUFJ8}GunmO#)@1aw3r+?u8eb2oBW>vieVcX5+Z zm_IMu>9JhWSE)xnbhmS)EaE80aYj5Ep5zKn!!N+gim|0x-FrIensbtUk3vhAwdAU1 z+Y(Vfs{Mc!Lqk_Xd_3as9l2*{**IFN}Mok8Z zkGOv*@k`IG{7g!;o3{NsJCi{Qk+=2Zt|C=KVl6(UyEh=PB{`I05-Vxt&Fu>Z`E&>NkOvy3ABjk@m&YmimDFOeSC=pKfa?6H>Jzm{y|RB-!{ zO%B?u7qjSQU##Zn_K=LHeLW(!*9Hus729um^%l#ipP$P2Vu}1A0+Y#$X+}05CsPhf z>2`9crlk%KCDuz^>QRUlC}BJyIt45x^+qSEkOYz3_Pf%}fo=J->8}_QQL_2-S^cvq zNnx!=`TAck5O?GsDurGi>}H5d&d)kii-E!~F5P#+17FQsW0excdQHdq*UnrKXvtcHZrsqBq;SygddeGS7z*QR4nE-mpr>c6u5j4-{W><6eQZa z$7>#>a%tKv+BM{o@J9Y;ceZ%P5Zc|Ruxs?|p{HTWM{nxC^{C)5)Z*0mT^LpI9+HkYE?e=YFyP0ao`OXos{OqM| zV+yon?)xE(_2QJ6D>y>%KH)vvVigIWbYG|Cb z?9TX9`eUUbCF3qD!}Hnxh3_*m*uGj9F*SbY z@6)4=oU~~?5B1o$@088?f-QKm>L@||a8x&&8S})7CE8`w%OxSe>KbvA-OiTesXt|k zEuCbKG48Q$*6H0}j4+R|-cPa8-5wLpOJS(3Dv8O4-&P*Bm?kRv6P&hY6 zYFdC`f8!6>7W(p6!!uefk59^7YtjtAQk zKPu7=Mcp-;*b)lmhBz08u^ujLefVh8gaXchxcA8(hbaNK3vVBCb#!a=09$k8PV!;AXG~`Fk+V+jMe&*=(5k6^s3>hoNinHu=`(oJuV%XYEo|<4c31wAi87_A?=IqBwG-LnZN!tSYTQ+6m zWJPw9^{L%t?KGR*rNb{uQj$xB6q(nKu9+9-wp7sY;^-F9+@PJz@ERts^c_!I zb)@)dvflrsJudtvV;v(PSLl8vTe~PtyV&fhq*xJPFqw`SS5S&wS>R1xOjsF$D)JLS z;K5dn3_nuWKTB}YB+TaUKSRjsXcoDMsGOWzrX{ejo8cL4-lJaIZb(&uMBUJ}vD(+P z0yEy{Ad|#Vy9Nful|FJt*Ba0L4Ds zwU@Dw$lh|C8O4-hg-Np{R8qbx!IJ`=L7Y|{>Y?$QKKy26U@}dYX}nVe?xH!V1j8lN z?lk?sw5YwZd%ri<&1$k$WvZsvPdnHQVSYWxM~0SVkQWs8@x#cpq2l7)qB-Ezi>hX1 z*}I#Rgjw;KwP69HQXen#$MHq|T{Zu$Z`MKGQu7_3{rfy>ob;RrOf;swKXYqMvan~~ zgieN&^%fQf+*I}wWHC)s&FJ$BQP4kN%`2NYiL4~5FEmPtDAq&=7_)H~Wd9S`>@_Yp z>e*Ehn{07m*n59j#`49aAgmp~@q0kL0 zBaI)+Wuc5ym{AG2O<{pAg++OaT;184ZCTU-VxbF+HyuLOVJbX<1iy$wmEQv4Z`EoU}0c>5F4ig1?JQm5_0N9=1K0_L9C;QMpDo*-?LVbT;oiPm`J z=#{0BYvHw;VBf^?vi21zhxo1~msM+=kfH@%(4zb2fW}3lt_BCU$Hryt4>*gfri+Eg zhpSETJI)v)R|SxG;ejNR8612pMNFy*w$y~LpvtmV3fH5w#RqDm;k28t3A%NxvlONV&@2Da`ZG$6Z#VA9fNDf9gWpE=Yrf}Q+oZtGXL^7lBz z`rG1qGa z)db-R^YdiN?kxqq4A0fqRAR2(gHX%|J|E+kFLe&F3q9Gf1x5{foLs;2;Ys~Pra|Q( z0?b`CoVLs$dvrbaR{3dMec938V*Eiwk9nt4`lEkIPB;5AdG~Xz7{yX&__E`I#5-fF z8KW@Rg@E%M6U*Zk$RVY1Q9f3^t$4N)Ko>Hi8;KV1q($6K<^3m9G4;0;Dm_swhg>XESG z-$|k57|}j;kD8$9IqwT-vsV9rG-aByYY*SJ4s2$gH}t}V&yQfghRQ~99>#{wT_*-V z94yeFTs6?RoaXv0$)u3aV!KnJ)s_xP)77WYfwEQjqx}&( zv7!AkLOcO-dJf)Q%M!DmEOmnpj-l1t@6;LPMBEjR-J%R-4_Uhgx^U0B$|(Ati9G)3 z5HH=XI#bfRFyy=!u*D>;QIx1g57n*w{;Ker>`cz;q-;2+Tl2j4(3?gM@11dS9`kNz zKe>$JLPhl-H;$vLRKGy!c+d4l4bysDaL=crOS~^#QU(wWuvG_fSd4zidAVv(I+)<>>kqW2ht z9skH~W=Z9LRGv%r`0dmeO+Xd;j3Q6MnXjp?>lD1G%ULyHG0GZB8hyz1IN$>Jx(2sZ zb*$ggxsO?KMa+8X#aZPWN^hUX62cH9|oY6UVY z5MAT2IqK11^;=7I@1c&Wl*D#ZLuKCzPt&x$kv@*@JBcIo zs&~txK-=?n``5as{QUXn{i)2dz{H1N0ssnxJoIAu(PZ*&mhwDNp=;=o3Qc7^>RD=? z^|B4WR5G_b!Mh{de^@@Wa z0t6DC=j0-9^XKZ8V!Q)_!Usz%J>>A-bDNQh@FYSCTWN3-(1uC*wNF=Ho&?^$3Jk{ zTaO_`dCjH;hk?SEvpcH`im{J@@jyT-iqQGNQSA7eD>*}Cv-~ayHVT#8Wh+ifbVPwV zZOvA(zgho3$I4;_n1tB-JcjT>egaYPJ;T;W2&T8|^Uomdm#@FFQ z^ZCc5B|&tE&%E=8j3_SdUXzpYA^-z8gv2u0p_6o_1;0n0nn>`6t~U$R1Ts8mLBmeO z4M%?Wr&7{lAV9PbNlj$3sGG^mkj7SX=5YR&_V&u}*uu%sEI9MLO1jjLWj_cYe6bu% z+-fOWg%OMV2nV!98m; zQb`b|-1CIKgi%djx$kcDUDT0hzE4=dG|DUTQJ(Cv`>#6F6TAx4Rj+<#CKkEnWfbj5 z2PxR=d@EHT%K@;7+qfTnNW}pj1jah~FW-X5pY6c=(Xq5}0y^Szv4B5UWELpKYNI339ls<3ZlSgm>J1yOcg&7%_{csD#X3WgAv|>oq)S zL^m2pp3QEbAPv$t)vy8pYToJUfA|RvfZAbN2koSe+QxfPG!lFLJoIlP8 zkdg{+dF3;gS3kI1Z;%O}XG9&?6(L8(NSJ++r?4RH($ysEzPb1FM$pYA2LeK5z)eX{ znq`B1VSGLFe0Vl=eFT6{BycdzkcHgMh^*;I{9>{KZLnG0mFJQU?SXXrlvW#mWP zTdks(A6=~4>b%nLeQRPP1QrMg&IL6`@(64L#RL;lX3RN{x}$ z=3~6A_rKrLE4yGwC9NJf4Qx4JgKR&0@?(zUj zhy!7o2GU3Nvg6ZFxe~9*UIG}L0s((GonRdbD>-A^Aw?ezP|XHhQJz-8xBUmbw4=5A zPQNRhWVoMvBreBIel-OJ{@>O#wcSirPK&(;@Z%OK|a5CD~A?5 z>dn9TfZwW8PK2=fdIB545*4 zguoKWM^o#;8B_2-%OL;hx>QcYA_$H48|<+(O~qf@0XDT*ph|n4#be&#I>gbtypSU54%aHDd;59++PlkI=QTI~q_+m5s++1!XQQi(kT?B1Qv{ zp>ITQ_@>Fm9d7lkumzbd)%%%E?VXmzfVl-b;fe z&GV+Z|2*?C@6CyJ8Zf5}lw~F$UqGiw6d}Ix47Y$^r6Yn zFJgG8P{_Hz2O{rl5M`O5RWxSC8$AVxrh@8ObCKhT@dqoK|azb7&^iK?i{B>M9;472?=BzuYxs*l&v9w!~hx1E~AH4ahDDC!?$$WU1I3o7hsJ<#FJfhjar!@~d4z%zn4# zEyoF@->YrRI|EX!r1l?Poic3NUe(|HPI)wM7s!4Nksd0sG<8~i4ea@WMU8ORcD5+)y&|$Z2MvZm5d@nwdPOPs{|F&q!?Ya(~u|@$CGn zm|`I9ZRoOQ?p}Y~<#!q%Jdbu~jdYGQ(`B757VqwZ($XK^6ZO5W3S4a(tvHhZh`i{fwK<2Ut{BNM6T90jnrntbQ?F0qfJA`v_>* z|7NH2DH&QRhB*WSTTQsZ^{(!C$eGo`r`8M!gTzV%Wx78~0) zuLg;3MddfpfrW=)wlu<)xo{y&qV{Yatu~*O*GTxym;2@6MMz-ESmy)?nt$O; zZ!dUApDkgU=;hop?SG8uhEYS@hlJWfqnmqtqz10UxDEvVl@WEA>$Kb=w5uX-k|@zD zC1PHCTF4XZN|Vwu$)HM>rJdA@@@_qPLNqZsl1Mq=FxHzE2E7#(D!DXrq!%8la#1OX z;=(kq)NJi%z=r|kJ+Gicf&RL$fLsL*q$q?pp7kfk(~U2Y2KPYnS71Y*%Xlu6a5Dnv zPm3JBbi_T|$wQwzI(udxTnmWXB%c6EJ!@^6Q5Lk^zj%KN;*V*Xc zC3JV(H_b?Do@VeD2L=qAts-lF`8P%(D?%1`XWKFZb?{Me#Oyr)n;ya4aSPLq**7wP(Z_yf zJS#A&zwG^ado;iL$fEWBgOtZoqsT^L zYyEM| zdUjeg8vKfN3{y|po(SO4`DjA8`vE+LipB165c5-=;1>+sbcZH2v+D9L&s)@hT-z6c z{XM`+@_6(-sA+q%{R(^q@}Uu=x~OW@Vs9?i6CU6Bwj|9@mR5YDg_lF15Ic2`3TI2Tce3%GXuQ z<<|fOsIM&i{7RCD(subAgNKiyP3InrweI4Sps1dDRgcoNx6v8(Ua($W&OYB%jCA9w$*NyBlfdVeW4JSJvt+-(06C_}D?>FR;}l1P2Zi(i_z7TnLV4a!N1Jdc} zP9uM!IXF1SX_BRg!i2AG3iKvPoXGdI6@C-3)bR0&Y037%X!ul`V*CP&!F8x$`XvZa zS(2OaAUWd&#-30vA%Rp3p-onRC?nnYJFS@lW1_nG@5CE{Ghra=u1V>`8Iqdyy8QQT z3FwG3PdNFxZsxtx45s>rRH9SiKM93hK*0cVlviRws^|61AhYhp+?!TC9FK}U?MNph z3_#d;J+6;KKR?~Tis2{_Bc>9ta^3&UHE$`8iXsE$3IuDeFMcz9+BSbbc#D&c;W z0`&xtaxI3m-0afT+%tcGHJHuPxei>XvXnS3`|ZAO_^0Q2)kp4x0RDI>OGpt&C4h<* z+T^5s!FPUljDbLbGOy<_NxsK04!hsYu z$puye_e^!~2_@UNFPMKiOTZzhpTXM2FczKJ$?uj6Gqwz8TcV9#U0mSGH+imU+7ZiV zKCx`h#1RCC-9K=e16Kovy_G7eI1jUPnU3Z7LW*ce=Nssc*ynzl;#)qS7Avp(kQTQ- z-K-GKsUF9``*kw#L1jcK1!83q)G=_1g+DB>ApCNRe+Azr&aVt>|E|)|uh!ABl`H+r zyYm`H6D!{*v7?>I*1(j^3$F)%F9=_Q+?pFR2*{4N5~t9uj)%uvsR4!6@VTuUv&A2& z{dnxr>w7m=5Q$MtJKzQJOtPlaA6rUOikk87QvNk@nDRHLFHJc45prvw1safAa=v+2 zTCbFqqcax7fI?MY$hi5=W}Qj0vr}i)3co}pD02f6nk~7d^-qnqPZ~S$U6p-qcj5_f z12Y<)q=;I0G@F$!x`y|U>0)R4*2Ay%DovQ|I#f=jZXPyPaloC~{U?Usk{BhUyXhd4 z$VKb=ExZPUCpMnO-zGofaV1T?o_+(!%wtO|`@oB*8LZWxb47wkq%$hg;0!TVWQM9} zAkmu5m&UOfX+NqR+C;(aAC|KpLD{@f1i(72d7nAYjG*v`sAx1bI_}}&d5t{1`5Z~3 zUorgHz}NE^2(}tV1BK_RmxvAFHK)D9A4Q$*X`d)LnIsYb5U2-VBXA2n8?0U=%q|4H z)S;G?{{E$X%Y3W=ce5@bp(c3*Z7mHL5iUM-mDw5bsETUvWu9RW?!J2879D-W-e?A_ zXW`{V@z3E`!nJ$X1>m-u%FQpQ75f7{K0p3gDH}BJTK$)hTFzs+t*jR>Uq8AYA1GPJ z3o4X?i)%-`m$%KF5F}%B*>?u-+1*{)Lv`AfoB}J2wn0BSNd`wS6J0lPMgG0dN(jpT zA2&Sju-&CNzHoY6KL#(IW+i%6^nd>*>VG-(cq%~e@0O4d82b0l?Z1~x3EY6kgt7mJ zORL8ngbaX4>F;0yvwvkG9EiWXr`iy3=k8qHJ^=GJv;dRz>*#R*l05O$K`>?&md%+e zcbr9$=k9vcK)FNuF1Y#mAKerLH6?&66t4(>L8c46VkW90fT)-${!-boWG}b+8GNnk zBDns1t>OGLa21&v?MRCT_gGy+!9~zrx1X?@=M>XO^xa@G@KdKzaTX)??dSCFT|U6sKj~0 z-3Up~0Via*GH87}{RNvztkVE8Q8-M)Ho&Te@!DfCz-JtBErq z4_5i5WW#5`sDeg`@8i}r86{z1IsEMMJLh~svg@lV{!!w@#jI~JXNjMWJYY<*()*R};O;k9Zv7+`2MYBc`f z=&QL`gD5Mi_LWFGG^YG*oI+=?&o@!Lnvy}ahHLUl)=;}|{=Dh8$=T@BTRZ0ORA z_r56WLvb@kNAP2Z^S7)dwdv$D+^uOXhUDf58# zxA8@Cdme~ju@J$nWFDdTChYJjh+qk+!*s?*rj+nV2-p`G1x3S!0QsW;7dxus(ah`# zm!c2ot!MX65Z`A0d5ZzGZKenRw%OGM?(OpQ|8OD)c8Zxg6cwHGb4na_qwgvRZqNvd z8oTKmq4;5(9&rNrHAtHdwBy>!dkNE%NJ35O@8yq*#HWP`1gR-t?1n=3cH8!REl&e0 zj9?~7l5{p+Pb?2yJoRro98552l*bK%a>$1o{l23S#ob4DJRPDg>r*327$`aDtffYa zv*P*nlnAncsXI(i;J6UJU;leR!0DoR3q1&PKc}kA9`up@702fl5NGXn9@s3Ru%5eM^$L+?tVBknpvp$n zv_T#@=&fM}iz{AxKzsIIsaaNSK-7)S|OW7_dIf5bPY<%ul>^ z_XK#Ini}@DcPsrH)4v^>;uiaVJJ>^|G{FmiDht#Q9wdL{(8mk54^7yN=#Ochk=5URwG0~2_G~HpxBp`NLEpW! zztlK2KFS|;MIVdt20FOAoB#aQ-!~sc9{%kB^3fh?ZT)Zg7M}cv7@wGcP5Ucf#P*k; zM7<^mZp75IPn;77j@kTD@NdUG?cz;R!b&N_m9wmX>U~YH1LH`Tso}xe!ay}ge_kXK z1gc6r8&)qoA;K#ALRWQffwM5&)DPMOS=seKeUfLewd~)v@*U6~sf!}E-`2K9ii7t4 zn>ElTVPpx`#+CFSRIIqzOC+J_au=Zuqh*@_$W{E*{lBrV@^So literal 0 HcmV?d00001 diff --git a/XIVChat.sln b/XIVChat.sln index 50a3c2b..c6279dc 100644 --- a/XIVChat.sln +++ b/XIVChat.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XIVChatCommon", "XIVChatCommon\XIVChatCommon.csproj", "{6F426EAB-15D4-48AC-80D2-2D2DF2CE5EE0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XIVChat Desktop", "XIVChat Desktop\XIVChat Desktop.csproj", "{D2773EAE-6B9E-4017-9681-585AAB5A99F4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XIVChat Desktop", "XIVChat Desktop\XIVChat Desktop.csproj", "{D2773EAE-6B9E-4017-9681-585AAB5A99F4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution