From 20eca5e8497d8489a6ccc8fd88e7ba20d9d00b31 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Wed, 20 Jul 2016 08:19:28 +0200 Subject: [PATCH 1/6] Profile import/clone fixes Fixed importing a default profile not setting the IsDefault flag to false Fixed duplicating a profile not cloning the layers --- Artemis/Artemis/Utilities/GeneralHelpers.cs | 8 +------- .../ViewModels/Profiles/LayerEditorViewModel.cs | 15 ++++++--------- .../ViewModels/Profiles/ProfileEditorViewModel.cs | 5 ++--- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Artemis/Artemis/Utilities/GeneralHelpers.cs b/Artemis/Artemis/Utilities/GeneralHelpers.cs index 58fb17d1d..29cba4b2a 100644 --- a/Artemis/Artemis/Utilities/GeneralHelpers.cs +++ b/Artemis/Artemis/Utilities/GeneralHelpers.cs @@ -50,13 +50,7 @@ namespace Artemis.Utilities if (ReferenceEquals(source, null)) return default(T); - var deserializeSettings = new JsonSerializerSettings - { - ObjectCreationHandling = ObjectCreationHandling.Replace, - TypeNameHandling = TypeNameHandling.Auto - }; - return (T) JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType(), - deserializeSettings); + return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), source.GetType()); } public static object GetPropertyValue(object o, string path) diff --git a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs index 0dfea8c36..b2218c502 100644 --- a/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/LayerEditorViewModel.cs @@ -217,20 +217,17 @@ namespace Artemis.ViewModels.Profiles foreach (var conditionViewModel in LayerConditionVms) ProposedLayer.Properties.Conditions.Add(conditionViewModel.LayerConditionModel); - // Ignore children on the comparison - var currentNoChildren = GeneralHelpers.Clone(Layer); - currentNoChildren.Children.Clear(); // If not a keyboard, ignore size and position if (ProposedLayer.LayerType.DrawType != DrawType.Keyboard) { - ProposedLayer.Properties.Width = currentNoChildren.Properties.Width; - ProposedLayer.Properties.Height = currentNoChildren.Properties.Height; - ProposedLayer.Properties.X = currentNoChildren.Properties.X; - ProposedLayer.Properties.Y = currentNoChildren.Properties.Y; - ProposedLayer.Properties.Contain = currentNoChildren.Properties.Contain; + ProposedLayer.Properties.Width = Layer.Properties.Width; + ProposedLayer.Properties.Height = Layer.Properties.Height; + ProposedLayer.Properties.X = Layer.Properties.X; + ProposedLayer.Properties.Y = Layer.Properties.Y; + ProposedLayer.Properties.Contain = Layer.Properties.Contain; } - var current = JsonConvert.SerializeObject(currentNoChildren, Formatting.Indented); + var current = JsonConvert.SerializeObject(Layer, Formatting.Indented); var proposed = JsonConvert.SerializeObject(ProposedLayer, Formatting.Indented); if (current.Equals(proposed)) diff --git a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs index 3e2929dd5..34a272f6e 100644 --- a/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs +++ b/Artemis/Artemis/ViewModels/Profiles/ProfileEditorViewModel.cs @@ -414,8 +414,6 @@ namespace Artemis.ViewModels.Profiles { var clone = GeneralHelpers.Clone(layer); layer.InsertAfter(clone); - foreach (var layerModel in layer.Children) - clone.Children.Add(GeneralHelpers.Clone(layerModel)); UpdateLayerList(clone); } @@ -642,9 +640,10 @@ namespace Artemis.ViewModels.Profiles profile.KeyboardSlug = deviceManager.ActiveKeyboard.Slug; profile.Width = deviceManager.ActiveKeyboard.Width; profile.Height = deviceManager.ActiveKeyboard.Height; - profile.IsDefault = false; } + profile.IsDefault = false; + // Verify the name while (ProfileProvider.GetAll().Contains(profile)) { From f8c35df098e890ca2b147378f57d523272e61bf1 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Thu, 21 Jul 2016 11:38:46 +0200 Subject: [PATCH 2/6] Rewrote Overwatch datareader to use a named pipe --- Artemis/Artemis/Artemis.csproj | 10 +- .../Logitech/LogitechKeyboard.cs | 4 +- Artemis/Artemis/Managers/MainManager.cs | 2 +- .../Modules/Games/Overwatch/OverwatchModel.cs | 62 ++++- .../Games/Overwatch/OverwatchViewModel.cs | 14 +- .../Games/TheDivision/TheDivisionModel.cs | 8 +- Artemis/Artemis/Resources/RzChromaSDK64.dll | Bin 100352 -> 18944 bytes .../DllManager.cs | 205 ++++++++------ .../Utilities/DataReaders/MmfReader.cs | 95 ------- .../PipeServer.cs | 260 +++++++++--------- .../Utilities/LogitechDll/NamedPipeServer.cs | 186 ------------- Artemis/Artemis/Utilities/Updater.cs | 4 +- .../Artemis/ViewModels/SystemTrayViewModel.cs | 2 +- .../Views/Flyouts/FlyoutSettingsView.xaml | 2 +- Artemis/Razer2Artemis/main.cpp | 44 +-- 15 files changed, 335 insertions(+), 563 deletions(-) rename Artemis/Artemis/Utilities/{LogitechDll => DataReaders}/DllManager.cs (78%) delete mode 100644 Artemis/Artemis/Utilities/DataReaders/MmfReader.cs rename Artemis/Artemis/Utilities/{LogitechDll => DataReaders}/PipeServer.cs (93%) delete mode 100644 Artemis/Artemis/Utilities/LogitechDll/NamedPipeServer.cs diff --git a/Artemis/Artemis/Artemis.csproj b/Artemis/Artemis/Artemis.csproj index 3a0358555..96489d3c6 100644 --- a/Artemis/Artemis/Artemis.csproj +++ b/Artemis/Artemis/Artemis.csproj @@ -38,8 +38,8 @@ https://github.com/SpoinkyNL/Artemis/wiki/Frequently-Asked-Questions-%28FAQ%29 Artemis Artemis - 0 - 1.2.0.0 + 1 + 1.2.0.1 false true true @@ -471,7 +471,7 @@ - + @@ -480,9 +480,7 @@ - - - + diff --git a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs index 32d9cc77d..8ad21c937 100644 --- a/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs +++ b/Artemis/Artemis/DeviceProviders/Logitech/LogitechKeyboard.cs @@ -3,7 +3,7 @@ using System.Threading; using System.Windows; using Artemis.DeviceProviders.Logitech.Utilities; using Artemis.Utilities; -using Artemis.Utilities.LogitechDll; +using Artemis.Utilities.DataReaders; using Microsoft.Win32; namespace Artemis.DeviceProviders.Logitech @@ -24,7 +24,7 @@ namespace Artemis.DeviceProviders.Logitech return false; } - if (DllManager.RestoreDll()) + if (DllManager.RestoreLogitechDll()) RestoreDll(); int majorNum = 0, minorNum = 0, buildNum = 0; diff --git a/Artemis/Artemis/Managers/MainManager.cs b/Artemis/Artemis/Managers/MainManager.cs index 1d2693896..8b732b637 100644 --- a/Artemis/Artemis/Managers/MainManager.cs +++ b/Artemis/Artemis/Managers/MainManager.cs @@ -5,9 +5,9 @@ using System.Timers; using Artemis.Events; using Artemis.Models; using Artemis.Modules.Effects.ProfilePreview; +using Artemis.Utilities.DataReaders; using Artemis.Utilities.GameState; using Artemis.Utilities.Keyboard; -using Artemis.Utilities.LogitechDll; using Artemis.ViewModels; using Caliburn.Micro; using Ninject; diff --git a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs index 3fa5f39fc..618d90564 100644 --- a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs +++ b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Windows.Media; using Artemis.Events; using Artemis.Managers; @@ -8,7 +9,6 @@ using Artemis.Models; using Artemis.Models.Interfaces; using Artemis.Profiles.Layers.Models; using Artemis.Utilities; -using Artemis.Utilities.DataReaders; using Caliburn.Micro; namespace Artemis.Modules.Games.Overwatch @@ -17,6 +17,7 @@ namespace Artemis.Modules.Games.Overwatch { private readonly IEventAggregator _events; private DateTime _characterChange; + private string _lastMessage; // Using sticky values on these since they can cause flickering private StickyValue _stickyStatus; private StickyValue _stickyUltimateReady; @@ -34,7 +35,6 @@ namespace Artemis.Modules.Games.Overwatch Enabled = Settings.Enabled; Initialized = false; - MmfReader = new MmfReader("overwatchMmf", MainManager.Logger); LoadOverwatchCharacters(); } @@ -45,8 +45,6 @@ namespace Artemis.Modules.Games.Overwatch public List OverwatchCharacters { get; set; } - public MmfReader MmfReader { get; set; } - public int Scale { get; set; } private void LoadOverwatchCharacters() @@ -82,15 +80,27 @@ namespace Artemis.Modules.Games.Overwatch _stickyStatus = new StickyValue(300); _stickyUltimateReady = new StickyValue(350); _stickyUltimateUsed = new StickyValue(350); + MainManager.PipeServer.PipeMessage += PipeServerOnPipeMessage; + Initialized = true; } public override void Dispose() { + Initialized = false; + _stickyStatus.Dispose(); _stickyUltimateReady.Dispose(); _stickyUltimateUsed.Dispose(); - Initialized = false; + + MainManager.PipeServer.PipeMessage -= PipeServerOnPipeMessage; + } + + private void PipeServerOnPipeMessage(string message) + { + + _lastMessage = message; + } public override void Update() @@ -110,7 +120,12 @@ namespace Artemis.Modules.Games.Overwatch public void UpdateOverwatch() { var gameDataModel = (OverwatchDataModel) DataModel; - var colors = MmfReader.GetColorArray(); + + if (_lastMessage == null) + return; + + var colors = ParseColorArray(_lastMessage); + if (colors == null) return; @@ -135,6 +150,41 @@ namespace Artemis.Modules.Games.Overwatch ParseAbilities(gameDataModel, colors); } + public Color[,] ParseColorArray(string arrayString) + { + if (string.IsNullOrEmpty(arrayString)) + return null; + var intermediateArray = arrayString.Split('|'); + if (intermediateArray[0] == "1" || intermediateArray.Length < 2) + return null; + var array = intermediateArray[1].Substring(1).Split(' '); + if (!array.Any()) + return null; + + try + { + var colors = new Color[6, 22]; + foreach (var intermediate in array) + { + if (intermediate.Length > 16) + continue; + + // Can't parse to a byte directly since it may contain values >254 + var parts = intermediate.Split(',').Select(int.Parse).ToArray(); + if (parts[0] >= 5 && parts[1] >= 21) + continue; + + colors[parts[0], parts[1]] = Color.FromRgb((byte) parts[2], (byte) parts[3], (byte) parts[4]); + } + return colors; + } + catch (FormatException e) + { + MainManager.Logger.Trace(e, "Failed to parse to color array"); + return null; + } + } + private void ParseGameSate(OverwatchDataModel gameDataModel, Color[,] colors) { if (_ultimateUsed.AddSeconds(5) >= DateTime.Now) diff --git a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchViewModel.cs b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchViewModel.cs index 494e60558..c02fd90a6 100644 --- a/Artemis/Artemis/Modules/Games/Overwatch/OverwatchViewModel.cs +++ b/Artemis/Artemis/Modules/Games/Overwatch/OverwatchViewModel.cs @@ -1,9 +1,8 @@ -using System; -using System.IO; +using System.IO; using System.Windows.Forms; using Artemis.InjectionFactories; using Artemis.Managers; -using Artemis.Properties; +using Artemis.Utilities.DataReaders; using Artemis.ViewModels.Abstract; using Caliburn.Micro; using Microsoft.Win32; @@ -72,14 +71,7 @@ namespace Artemis.Modules.Games.Overwatch return; } - try - { - File.WriteAllBytes(path + @"\RzChromaSDK64.dll", Resources.RzChromaSDK64); - } - catch (Exception e) - { - Logger?.Error(e, "Couldn't place Overwatch DLL, Overwatch support won't work."); - } + DllManager.PlaceRazerDll(path); } } } \ No newline at end of file diff --git a/Artemis/Artemis/Modules/Games/TheDivision/TheDivisionModel.cs b/Artemis/Artemis/Modules/Games/TheDivision/TheDivisionModel.cs index a817bd567..8f7cdb600 100644 --- a/Artemis/Artemis/Modules/Games/TheDivision/TheDivisionModel.cs +++ b/Artemis/Artemis/Modules/Games/TheDivision/TheDivisionModel.cs @@ -5,7 +5,7 @@ using Artemis.Managers; using Artemis.Models; using Artemis.Profiles.Layers.Models; using Artemis.Utilities; -using Artemis.Utilities.LogitechDll; +using Artemis.Utilities.DataReaders; namespace Artemis.Modules.Games.TheDivision { @@ -34,11 +34,13 @@ namespace Artemis.Modules.Games.TheDivision Task.Factory.StartNew(() => { Thread.Sleep(2000); - DllManager.RestoreDll(); + DllManager.RestoreLogitechDll(); }); _stickyAmmo.Dispose(); _stickyHp.Dispose(); + + MainManager.PipeServer.PipeMessage -= PipeServerOnPipeMessage; } public override void Enable() @@ -48,7 +50,7 @@ namespace Artemis.Modules.Games.TheDivision _stickyAmmo = new StickyValue(200); _stickyHp = new StickyValue(200); - DllManager.PlaceDll(); + DllManager.PlaceLogitechDll(); MainManager.PipeServer.PipeMessage += PipeServerOnPipeMessage; Initialized = true; diff --git a/Artemis/Artemis/Resources/RzChromaSDK64.dll b/Artemis/Artemis/Resources/RzChromaSDK64.dll index 12fb419548ca9b6cec61c8a299ed38dd59026fb7..4ffde6f3ef023071068d2f4c40b8009e600772a0 100644 GIT binary patch literal 18944 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 T-PO5k|E|lKeU175O#}Z4dLH2R literal 100352 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

`TrhNXY2;`q`?@yqux3YuH(O5 zeq#L=ZPES~*Irn^Ik|qrLrJSoKFqTHjgq>sl4?M6F48E4-|i!u=pn1;bEjh= z7;MoC!HCQ6Pe-nP`@4cy%0)DwpaFKm<Y$vVpl%Y_7 zuf^6R5`yM0f5)ng#Bphdk=5+-r=IXOYV2U^+J4MATT$4HHACba_)T7i#*3Kv&0GJT zwkE_@p=G=6mbb+Ygk0LDymQps5!eqf*YiD3U1F3`2XjOgDnPg)^X#MfgBmTdu@Ur-ay9y=AVq`xrxa}=uFg?2VUc0x;NcdpP{U)0s7{z-p1_Kog37t&83`h)TE zQ#@@H4;%D#zZRX67u&?R=|hi>s(rKRPdGHXH}_+rf1S`*x4nrN-#r?+w92lxdL!ud zY{X&rpC>{W-5c_;(N8|qGktS>q#3TR%MD83%-i@9zjuhK&Hh_iGf@orUt(l%S~AC%GJtiMtt{K0 zmxx>i4;o4+EH566<|Vd&h%}zL?r+AOO~CcVla)lB-bj@Ceu31XLgnJp<#=4&e8J2z zu(EIiqp{hsejqEXyC#E{>A3zYwxH!@xO~m01JNbYQ}bM_Kx^Mcw8a9Ke(|p;#Joxv zYo+37^s$cLeoiX@9)Kq*V-qT5CaG0xmpXJJ&ShysXFQSlL!2Y^)%5;r%Nn0e@&oPvD??^x9XE z->ZfVK}6fn70$!F^Nh9#HoW*E$%H`13r?r(p{;T#6~*<%C-DDiY)q5Y z4!sfx5J8Snmdi(puIYPL+1npr9Ru@`BQV1${Z6V@5T{<{t4K!W6{rkCDqEhKxau5} z=)xX|PLcHl(Rdh&u$K;WQF32p`YKuw_O`+~+GycO`xSSTCJNtea3%rfgO1>|vU0RZ z`J)$kD=DWAzK{mTxE=$=TqX;VUjiHx|FoBp4sSjg&FA|un-~(6EZj*%gZ;%K4OfhL zUE9K0%PeUVgHp`I$+&;gv!sz_1EmM=5OqlWM>C`Oht=#G?7OS^-8zZ8g^`Ha+Hr$1 zy>56B^{vy-U*ST0&2gA=?q-oOy$(4b)1m3dfgEG{@tkj+ejYOjgik+h@V}UT?jucq z@*}7mNIezxFs3TW7FH30J$kKmSgfXS*>Ea`77&E~U`}Gy;A(`xRUow*^(0R^EKRIx zT;fq}UZuGpfJGL#EDM6E!)!s;{gJGDM9V`#|22!~Z-beJv%VQQ2J2tWv9g|J5C|82 z1qt|5Pn4{mE?9p8^#tov^C}(J8?qev5)iGn@P~dB1(#Z5SY+6O{RoEQLkv}hF?7E{ zAe^B?`vgNb#ki0)1!Um1}M=a@`0!Q9s*X<4A+=Kj_U7&&<%F?U%g-RP(3qym& zelfj&{45$=KmvVV=}JQh^^Z~s+DFv4VuJglcTu{%TlF6m{crC%3i|N0klL}l%SnlZ z_>IvsPqqll=;aRoHz&V7*yrDm#(w%he~27$w2+>hU9|nG-g@E|4zuav6;g4>Exhqw z_RVm^3uIc*!(I7FW_~6r=Hm`o+aih#>{ej6Uk{$Kg+)&VA76|uF`}f@FEs0Z{ygd9 zMoYHK8$v-uP5>Rpn|KEeJeiNXoo3JxPXPp*-+2~0u4x8ý{iE=ub#a_u~m6b{R zMLl}Fj)5|bBspvbfRA|#peGqeOnH5}jF^8!SG$`e4aPll&F(1Zs3EMnDQ0HJGsp~| zGVhYQ65WzFqf)c0`b}!?Na%>$Zab>phQ?dZ_UmthF!ccTJG*w?eW&+0>0xsH9Xr_L zq|kTzKM{g!(T6_QH~KxQ(s=jYw^~i98v!$?-MTbaqTZh^8TLNZEa2^usnKIF&QSl4 zp1Vr4m_%fmT)cK1vee&iJkl4mcapYT+Wzbb_^LI# zg<)~&eUy`c*iQ;S^wZNq9+MAI@Fd3Gen|KPxv>8enek<(IIe%rl6w_%(QUC6EfFtg zR`~P9vpFHi=Zh}6mG}5gQhPQ9Eh#H8aTFx#Hy>H=IAjG6W{Ag=M)k?mwsB`sV6Y(e zFganG1uYVZS(Z2BE`B;xbttRu$KzC1+CpZ`y5E=P#8G;bqVLr_9J0qA{eeHZ_!({p ztUHrN(wm1Jee(zYG+H>+gE$sRgi#w`cog+DY{FO(f3mV#sC<(oqBABcCFU*Eq1lracyxSVaaIE zV>mkoR}N7&>C9=(GhkmMCs12Qf0HjI^LmDFY`dw@} zUdq7<0@oJ6NemfAr&M{$f9JzdnD_qpA3wr4AMP3(8vf3nt(%nxwyY5_bb|4x|Ks!7{z_ zQT8HihwDLPNgZdSe?pU`?1{8NIpXu>;FkV^C?;kuq|$M*G#s*H=C1(-Ni?{gCtBUKk>7B#dKbAb18?`u`2 zMHzJ9LK;@s9DE$_`QvHuOh~?3yH>5j}#o`sT>5Z8nw<;B`$XaO$#L;mbRzfUQ zR(eeWzxpAFS(d>D-D@RXB6Eo%UTwoRim~brzAhkN#T7@~;xQk+D7+XF*gP2X5V>54 zN|MvJ1x2}F?%OAw`m{ob@IO5b4M<6#7Tb zn=pW$i9-^7p4J;-kLm41(2PkI%{bKIFq#=)Rwh=_k3$C*=AJT3Jo9i5@|@U61m z%5)=T+N%zS)A`lfmQ-#2fyvnqGt#l@CNsq zMB6IriHfBY=opM`P`qi_0u>2m8&B5yG&-aCvmPzSSYI76UyhABUj|a|9fuvZW3waE zi9~z;nMf~Z`rl|P3F8LAFF#dtweY{KFmhJZS3dy#88-&qE-eHL$L60_{y?QJ%G{cd$tg{agN5-!jMuu z4;qCIF|_jvc>r5`vIt1KQ{wPpp$GOidI0&?F<2AnKr;@_BhU9N<9-hRNuagA5x?#c zL}Qw0guZ7PAMf!06UDAFQ56Ryig1G5Ug#?Gvs6pi;2Ix5`G&?Fxa#Dh6f1zy9Qx2Y{qE70}GbmWK1TKq=zIAs_c+Sp5@<+ zBtA#w0l=YHRt1H_q%iMZ+Bn}=O4NTxTQu^udqi5aavjp`AO0KBbBs1ZR#FLxj>hYx z68a0KK;uS1qfv!kEqYLKA4feb=pPa=(-bhF?^(vs1D8Nhmj#V!p)P^OE9fDCt(J+Z zQV;9xqQx>l5iF|MjLWVOkI=@BZqXwZa){Xe)GzKQ@Mq(L_j)|KPc5LtPzXsH3fU}^ zREP&jC}aTu6vC>Yu$mP1+#OO#{{2D;BL5$EiL_|tW2D>Pqb{CAFGkE-@bz3a4be-wzWs=L6DS z%`(lN_Zt8)A*3P0_pA%C&AZm+$AZeAy!c)s!RLRndmyTR_CU@q;UD}9ommG0sjYZ| z;PPWo_zRr+!*>P(c(5?o<+o?DT9f_2TL6Chtpxa*V}^Jo8F!pGWd}N-$MXev2?c+t zd>DZ$ALF|oyWoKoF?8TNQy;huL+3@%7DI52JgMcHQ05EJ9x#YQd#*l&z=mo9RI6-NN*{ zxzi2#Ga-4gycC~6?CJJLzwiz*@nmx%#=(_*y!V0I3Auw2wiQx*dmzx1mw;2a8Q@;g z@)E?TX~U>F;bzWJD95Pj(`}IHPOx6>;=CR@E+m!N#rH7vFVQUb0emfSnCt{PXk`S? z#9*U11vJnnalc|dv}g&W9v*H}&>`P73CcBifss$I=o~r*pleOyQWG({M16I!^BL?y zSRycf;4qwOAD<-R!6Hr(@lX+0r?T`W5m$=%3K7fv8l=O>$^8qL?;~u8=chaMJa!sR zPIdX#rmKplze~eKN4hLs50zqbVHgXY0dIf(cU#ddyS`)#z;iC&-s~>ES|4gtSP|OG zz)L2SFTO6NZ`_QOZ*NAl%0YaIIyDkCqFgzm-1ku;Kh@ND07otxU-OJFfOA(}O*7KB zA9QJ7Vvp>zN9I7w+Klu$O*sj3nl4Hzz=1j6-o$9!aKC2>Kloprp0)_RP_y24X`i~Z z_Nm{xc9E-TEk2d?PseDdvTT`S^m-aI2ro)>l_gDEw|2r;lmfn9P?kF_!4?O;fKF`d)3RMb45bY|D5v|Q`+R% znH9(fi|QQhEtmFTJ~|J1EO9k?pxrN~PP;ZgAIZX8?F;T7&MixtG;7`3sgmiTbIWok zk?C3MW@NJ=$#wQzZNa2O{momz^}ad2eJRtu%37Ra1sNA2S3WwRy0jN0`;BjVR&2tn zUkQQzI6vb3n+q9B)=eil@3+@|XP8I~UH6?dkr=Swl$&q=ONqjNwz$P-=0w%NDe*?WU5hP=hKggje$*?k)6rdyOH?jT^ zb)xw@VE`D%(1#PIv>@FjM5=c}Z@Wm6f;zHk!9#@q2dN^u1h=O6+71gxfvXa_3{V#N zd-OP8n+>B3UvWi-tIUbB*7?ARZxN4Y^HF(D;upVhl^t-6!;xFoEThMvnq69gjae}J z-1JHqOA22VL`kJ9J_qj%=JNY@MuQnlIUPHl@#zlv0Pe(KPP3jMGOuh-acyP&45N8a z5cLqj^|-QH#Iaw|AYIWC=|76PkVa4~eN0&V!Sv)iUQv`!y+d7l_M|NP0_(<4y>Xwu z;M#K_rTwj&1Az$2;p<^q13FDPq{9cNE1nDjn@tGQ;ALn%0$yGCFyQsy z6J#;q`)4(`W8+ZKr^4EC!>fXaF*e`KhBv{WXW(w!=_|oem2Q+2w`kGD9&=5bs zPAl@wtUzPRb?i~OX1|ymv^3c-0@Uw-D5xLb%s>7b;xJX?0sHz6jbeHc`TWyIBTrh< zfQ>xs&$Lq?g0$2c$@j^Oj<9#yZIR+l@y*@6d0e8r?q{3pGr$0fQ_gsp% z1f~xs1?lQFz@Yq5nyKJ*T|Rb=K>7YxSbbVhUN&iZ2TB@0gia&s#KYcUp>S<=9c&|kL8H?!szwC?RMOuGZ4#(p_6I533MSM}pzFXX<^ zoSU2&7g&Nun-GJZri=D)lYv^GSG+o8v%~o*@(7>ETTjLZZCvY6cFcN3U-5Ygto>QgLr6NqGg=%LZ%y z)M>0NM78q95Xq!olI)Vd$1Oem;^toTG=e|4=eJl*aQ4FuA#~49BT8~ceFF_mc3%x_&B09ahd#1-#i_6bt zQ5}WiPyGW9bI6SEO4)|H&k+998wCFJUzqrdqvG>17b>OEP~hTLCn~&`60Q&{-KKq;yu;DM1nrG{d~X6%i+&rnW!pyy z60?#p8gCNw4d!Jae26qhE!3QRgOLx;?I+&~Qa&Av>$}q5VpZT|yZo1^P{(Zo@0s*g zBOe{#n7*BM@3-G5%9f;O`~qLokh7ZhrhbNxvqu3MUy#0fj;|fxGQ&MkzV?Ft5AOXM z)%BP86wIV`%3=Lk>$UbKZdxeQ5;XGz(F|k`R_|pRqF+rOGlD$80`%S2qg{ib+@03m zn4`U-uP0&6=z(VRP}?Y^>dHE(m(kkS?WNdrF7NfPFN6*4qq^|@BVpAkuCnK$m@}^W zcOXyOysi@a{RP^_J#S%`JjeHsfF~nYdp<{dt-TqN!-0qaH!8~0b_hR59PKa)e}KX+ zM@fh^?%%-edeT)sO38$sz{>jX0gzV4=aAaWb3h;^M4+>yKO`G~&(GNf zo)wrBjA0cG70PF4sr+S)G@858DULbXrPv{=&zPhAq031rB+fpkoC|Xn z^Zf~)+4~YEDmnWS`az|8)tus-%X2QvS<>4dAc>Fwwi6sXQ}DU4^{;vV&iC{JEn!ya zoWF2!Py*{d+CG??u;0K*MjGHIKEcVU+c`pzFtO-??9kH+nLL17%H+3e1}otdRFl75QV&JcloD$7b9*roxkq?Qrt|Bf$zNW29Cy8v|$hD zYx}jA_18p^qw!K07muqKH2xhQ(rkR&vEGH8^?0NfNIes@@!_gQoS;J+>(fiX5+<$; z`84jre+oW0keucdI_zTT6%FV+HVdb^w3qY`q10TB`#^sCb>NHE0iU>0(4sWxaSL-a zti;i$!0YY5q+Or@zI=VU8y3zB)WGcqn;s}U?x^)3CB(v7S+5zgsDTKLy3dHZjm@Gq zNi|@J!@M`gPQGLPLrCbi;Eozjc$(>izb703*E@8=)7R4p)1Q8G{`b$m zUix47Y3eH*)|ayj1_wW^e~SBes}p{Y-U>6b3rLuW>5IPs2TXZmnOV(;7@qVrdg07D z+HdCIk(+psp~qcA$FhK)_SJkW^l(4D&!zpE^;DOZs88l#gzI$Dhv2aUJQCe$p~JNY z?lZF|mc{;n*rXN@CU7&0S@uhPtZ0>sNsP-%JXsf`7d!s+bzjo=VKX4wUOt@XeK?gH z^s%_U!+P!eGGUd&RkjBXYxf3fAz{x8S*lZm`LIKl`QG|4GnfuNK3P?zYM9M%I4&xyT_)`|VDo;K-3T*GQS4 z`~$({S**D~!Nz4rC|^sy^C=R}Pf*QFdlK83XUHF# z+qG6ujMxY=ZpSjDxF)U<;!4E)lBu-_dHwTgaMKT+a3M1TQh1;Jg1j;edDZb+v%f^p zWS;@_{r0hlrHn=*C1rG?kutkY8J&PUjI~S`K9Y>f(ceW|iBMTjTJE~*!TmR*V}>0p zTpjF~)94s^)KB9?jE>O|1{fVP4dD(TpktpBG}+5`vtx4*%Z|-PiXFptnM+xTJq|uz z@N%!wg$ikY6SQNUS&Il5u z4Dvz2$-cV8V;#DRXFf+g-`>@I?P3)E*OAnb{Bw2WBzr;|PN5AaS&zGBxzRPOq4^9W z^(<0gm)SL!;+Y-RwUS`hDw#U6Yj`dRHL`0(?Am*uaRG^c8;+ubX8$s{5pSRPGpxSY z3GwaW=iz8ZB8M&E(H{@UV59FUZ+GJ@oZh=X4WlPXi-W>Eb;|ALaio@Ie-emM2XSye z4P8!g>}=t{2wX2Xpzzm`uHklzn4%0fB$$cYDE;?XRw#rO2FEtTZb60jEqlqNuA zXSk*&q7i~J;%YLbvM#$Y&!{nu#IQq$5B{K72q%ps z2Vn{jB>5#SU-y%+#cx!Af$^EJKx3dnYcBms=ohzEWH4WErNxL3;)t=q2o1v(B)j2x zAuw8Nr_1rg5az4wx2kxCl1-J;&G05{w?VdhCsDTHyOS11jHIxyDWZK(e|?o3FYXN0 zr+LKeKz{n`Z=NcQ;@q`bRx)F(J@8fza_HcbSnvk^z=^64=y# z2E-%z3cc?T99gK6tsNsc)NI!`NqP}W51|p(hde(+4gG<-BgebB zK9Sbmf1mwUG+@)8s{#JGs1#Dd2z^xWdJsr%s_j;=hCPrzUb6LlROx#ry#aQrc!n7% z8y@`Hk)eMF?CUlVM;~)dG@QQoC%=JZo4y@vqon_&X8B2XfAX8mz9W>4Eg5CItsNT% zO8YuQ#_O_rGkPJ7S{O6TTlgE{t}>;8BM&$3_7z+oVqXB|hxrw z*d*o-iU1sP5roVPJ&lhnk%#9qK_3UliVV$}J{~=9fVeqW6FKk!Io0&IlP|g7K-bSX z<_u|^cy7#R0hyo}*O9~(OY)abLe}5y!pUvG&###@96RlHd}ahylSw0+T!EwG`4T{; zo{T{&Q~g#%8@ZAn)1O0c$s5qHejkuSoey|VrA!KSr(_qC5SI!Nj@mJRs|*$5P?)K9 zoY8SQg$TVTpG`_*!2pI|s7i*cl3`ZK=!L(G)VaxTHu&w_fmS}LHf@-WIpD=1KKK*; zV`O)X>cGzp88b8#j+<%l_B3nVfWg@-4`XSMWM91!^&sbgX4l2jeKmkT%HT+n(6x(l zlAYc)>HgoJcE>4@*i`$_M=;j$xla24BwSy#VvXM)srEN1P!6>~s^dc`TQMW4fzSHAWdXbsKklti=7hF50mAd87PqSP$wC zRtlwCix0wCe1fvzG9LKZ`nA+@s4wCrV z?>=jL^#A+qE0M*&AREDMvz*&=otO#aJ@P;_4+|WGI|ij6REyhW@$(=0XUoBtf4z81 zQ9fU9#U2)(ulqNOyg=%3$_Mqt@dxy;FycPquYX24@?FM#MEEU|Te<~U%W9?|cB3sy zinmA+_YrrH8d^IH*7*QYw2C_7TI_YKPv(3kOWRHTu83s zJ&pu&;JOA%!QQzHf?cQU zycJSIi+(v?EKB`dVrJmZCoQzP7t4+e-!7_r@G4HOCz!hx!n0@ip0RB}nY{Cm3*Wx% zGZ_BhXXWs1sek?eo$`gP4z84oZ z@xCLL2ad*1Q3M7jf9 zu`mPY>{13G_X!xTXhHhFm*1oRTkK8cJ0{L21g7qnI9@laCf9lR&_jVI&^<=M;y~(V zqvBk%X~`7sTW*C=(C!&#ft9D{uy2hrv?f}@OCO2)XoF}R>{;8GPTBC8*saoOQg~MV zSVa4^B!z$EV@znMd0S9JJhCWy|It;V_lfBJN}3h!L*UrY6J;57!Lmz2Wv2vESG7s0 zju7)>vI4xU*Yq<(ID85AGw2=Lpz$c=D=Tin!>fVR-=ea7QtVj2U*h}~Oz^RAgNd_K z;B-=v>%T8?{vdE3H4apQ&aDDRr{!R=`;vauQm~Inz&MUT*68QK!Y)?bzX!w{0CZZQeVvu+IGZ^EU4-zxvSkkGQ%UbMUA|K17ahU6wO- zCJlSaJvavjWR}Yjd4TJcD*EQ80r)zbvQ8mV3(cIljYbj_dP*;_}ZL?&5n( zc&}pyt{o}%r)n|B*?)tWX6JOIBtMC?91)HoxeJ`10_ms7atz5|Q1~gcN}6&PR63rr zKZM+6NLNFFUeOKl4F3ek_g$&v<2ZC?!<}$_KhHqnnHf_Trrt~)Z%n6k(Z(O6MXcLP z^XqY9{aUR3*olbeh+I>+v3A6m*?2d*BA)k_Fb8(xnFUpt3Vi(I8>&ccpLT z^}4<6_1CJZ(EFzg=i|Ma#XS7^D~R_kbTlsu#(Z%guZb^CEX!@0R-m=xob#)B{_`*s ze)={zLiY!QEn?%%fy*wv?1fw{?>d)OfKES?-a=_<*Q|mJ^nVJWc*f^zd2jCG{whY6 z;yr#2wwxQAuN|DTZid1=)H(hnyvdGn{|a1nuH&gW!*Wh}%kk7j{qp1SOm|0mD_hdO z$VYM)S=7G3A+*yh@AGK;Tz)(=)p8G{Pk*a!koMUrojKE&98l{La{~Kvr+-@aJ4EvL z!{EC zwGe;7PvHMfh}TPbW3*a-qqJaW;vanD+zr zEbm?+s$jD-LBjD=-r}6;`@Dm-f_taGvZ{Z4!4IZ?Qa2_0u5gHM) zW0WCUriStydH)Wi7GYQxXnT$B%)?bsN}>n9zr;{az9v=2=4ERQMr+TQC@3;a%1zBa zV99?<3Z;no8_oP2Z9m^Mq6Osxa;)<8QQ;B7{Kc_P3IC@kIOpRIun?~3d-x!PnD{W^ zi5U$N2Am8)m@T0FL11B6nqSPC3WxsWgcglq+gHRYy=R%PGhWQcem|#&>8WIW>UvK3)}$yH2bc5vASwWXtF~4*KS^ChOP_y#VZ~)bX~&hl+d*s*ItKapCg*HHQAj=P)a-iJ6hl=j!wr+#ZjWH z1>P8wtu!P|DaM<^Gc=c!X_my9B|?Qwxk)_9mePOil3o4L;hv;qGi%35iwMybKLN?H{xLMT-#%osv`5YEKuQR>9}JbDt4n*HGX%UDBVSuT{NQCj@45T!4}VWluzi!v7ubL>`Mn6vM}I!wYYd z=9}rg72@#>*q@LtMqPg-kfCUJHtq{QTq5>2BI?6iZL#q+_2f@4f*hys=@li}i@^97 zU&ojnUoGMq5xYfPCt{C?SBiMGh_4p$S`l9>;sz0q74cUhUUrByH;MSSBIk1v-ym|_ zB3>%u1tLCI#MvVLslY53>ElJ5DB^#J{EtNZu83Pj{Je;Fi1>#h-YVi7MJ)Nh{8!|s zP{gxDJXypiia1Tg14aB5X4Js=k44-j;@3pHSHwF-{E&$66!At8Un^p_h?k0Zfr!r) zakhw07V$_C4;FDN;_WtzGNF>5uK8@wiW7Mqxj%a>wi>G&lR!)YF$u&Z5R*Vm0x=21 zBoLE8Oad_p#3b-PErHPUm_XbAG%vCCV-ko-ASQvB1Y#11NgyVHm;_=Hh)EzOftUnh z5{OCQKP>@y{(+|)@GBtV?AsM8ejY=`djU}x>^50@9_ow^LvNy z#P9fI=nE$u$qap$L{{PT>e1-Tw>otC35%672d5JFZi;#HJzA=qC zn!k{QQb@@7O%hG~?)Xoc(4d8&mDmk zYFXfCf}X3XARE#_P&EA7%4DXsg300?R2*7V@!7-XZQ@nS5^W*gZ7e9%pURT0X9imj z(KS;+eq5R@>%h8fel%6S)NSS3#OC)(jsEo+zp20lo)HB9=~`a;OuWR@>>aQ_kJhMk z^hZLxrIV+Ii7eE3EESVLOad_p#3T@tKuiKL3B)82lR!)YF$u&Z5R*Vm0{_<}z`cUl zUrYiq3B)82lR!)YF$u&Z5R*Vm0{>A7)K%0gS5~a4XQL*xGEjn3{VKjfaaUqo1D|qj zLP)`P2sRtnE?j3Y-?&Edzv_=4CE(xoqvWqRr*3(ERlP@2!6RKH_-T#p1Ef66{N6&! zkbliR)aO?qC1xmQ-OQ{wMK`ke4TZ?R;{4jOD=nG)ZUEG$lngvJe%(FPC%@z`X$n7o zBL67+G+c|1k|1sNouys%uLStr_AjT}U01t$4QcR?{UcrHpkwP_VK3_!chP=xFYA+M z>z^r;)*kAc@_p;faQU|3I>R7h(jt8mKjW-${7hV%@{Po|^2IdEH(Btl_STjDHf4wC zTk%cW#f2Vx05SCELNbw;$;YO@6r(=*qP=&55B7Z6)Hr_1QgH)DlM{-%{i|JBQManp zQ&xFyRZTfC>#r^>^;CwcxRK>Bm4 zY4EY>W1CUmw8IaknViZG#dPBOA;SQzb{NKt<-(*>GCiD5C9b>jFQogTtGtU#Wsj8SGH&Q!)CJ`w+zw*HM*sueNW8<}OwG-Zk6M%`LvIpR`f z1?nsV#cEKjM><1Utz;`_DieUW0{LYEw;uS*(6*|qL|(n(Ma^nl)g#ZVlq0VeS7#`b zm5JbB39!l#lg~2X5u*avt3j~>p%ygPL~v^5I+P~;a_~G2{m%iVYSb-5+aA!d{*4uF ztrT2Up@xc79eU^$EmCS@t>_Bn9g;HqXHd@F%d~iQt+&Rb&X}oY)K^_yQS6zJaoQOZ z)YH}C`B#TEU|f*yZ1C(Anp2e-LSGq>Ta|J(`cMqcCP3z=L3R_s_2~%3%6yb{rBO-P zDwkZ)p*Et)CPNsOw;nciqMA`WYtDJaIr;e)&dymd=fVq+oO9j<1$h@NP)}3Om@q+| zX_{TID`UZ1$O66KTiP8hjU}^`Im&r}IZ8hMU5LKsC<~Bxp>lzd%km490woXe0@OMU zfqi9fXZB#dK{kSkx{4}%0IkklSK%oxE3Nm;@Z#EyvExxsK3jGcR}(Hh?TpLVo5&8& zM_=b$ke45%7nv9ftEmvSL<_4EBc%d5;^-?C);DZ zW-kl&wI_+qH{>)2(#nHGySIW!?(y-`a6d9b4eDRwhU`4LKbZkekJNY1Tuf3=RyiPQ zmu0N1ttwYfp1?5@Y#jC#&e@TioBT}@(xhjRBZPh@1OB8I*BsX;3(Jw?=txebeTDLm z^qD>Pg`9NFx#-sg7(5H-=gljgKj)G>YhSGL4UHDse0MyVexVpzmm_B(d_bNu54GlF zG+lyp@A&4|_4sG^qo~qiKAT5q)>+Dg3QBIGlVwBVJINs?G zBL5=mRT+4kp*YK_2&JgK9JRaqYaK7wU+&{<4K^SS8YZDxXJO5iHMOg1)QZ(*6>baX zMJLL7bB0E*x3sieEv>GuEi1KD0#V(lH!iiCdUbWBYt)*GRcdub&2sB180+5=vfb8i zNtiKG!a@UXGGL1V*(d%HTEl!#Y1x%(X&q+Oa@A8=p{^{gt14YuU7?;N@J~`#mDa0e zwRLq~x2K|fqR2$FAOiIywW?lSipgQQipj2AT~+0&to3?~tg0Gl8~VT6LcyfeJ(TTK zdn?r1Woq91LcvZ=tw+UOsk@@CdW~9Nx)SY`metkP*Q=%KGH*>8MXi=W>=RXWK_wW& ze{a1P8Eb%nEf{aLhxCX-jxDvyE6S=%>x>SlwKWw%BrFbB^0S>F&CXdXxogFwsD@WA zRm-cEEvu+Qr6@!u4ovR8=g~`ZK&sw@@rCy5SAc&kEXqqgrK)F*yF#s`u1bXhN-_Q* z5cHD85X78i&@Ti*eUe?#mD1`{)it$VwI0f~BS)?X>CpuW~yS0-dmn-EQkPV=hN zP1(arJQeEFDi7;`{JFJt7#yW5+|?D+RCQqTfU?TcI<-v2Laho*pUlBQL&=74s2LWK3q>Z5G%|kG)#yq1irLlS#2G<<1r*0wD>RrVeJ^ht{7?r(e9d6 zO#d?vtKX^>73%ED3XI#ly1LrBF#7Y1m2Q<-cdJ+rR>Jd@DOV~g=CC^aqgR}Wd*)>1 zEQDt)RhGifR^W;rQ^g$5`wKVnN^y^%D)X>voDEn2oO=8#6}~j+2uuNX0VO}Dfkrfr z&IL8z`FOz-IT)vm7i$d8*W=OFk$SlgHZMhfD>jJX~@;YrnNMPrd zR#khUrE%lSu_PKFYC-BH=;z>dVZC0%>6<>$!p``KlZsSaoBp$+)O4bzHVs>Xy;juG zLoHn+?L6q9FcyRDaSJ2tZ(uG)?KE|UnK>{gIEkn=-W5wR>^UIK!DAAgrkWKDIB-5j zh$VMmPI)<4s-K1tNtCaN|AzbuvZM6jBa0N!*nb<#A zf{{KSdmg#Cx8#08CcH(ixVt2+Meqgl;5T?LIR}_|22L)p$}y6w%?7)GrSL3OxF=hI z(S4e-O0<3&{1d%W1#%cKN3L9Vu0c*UTHxLJX@Zlk+9*ZqTtAX}mNMxGZst`iztCM# zbAH8|yw!`r8?G<%R@ALoREo8Q%n}?Ue>T)x0Yq^(89!P+ z451Qbl|~uMH#7PM{t64j?LF8;v&>I1U@+fINVd(Z82m4nfK^>>7h!Mr#5e`_Zas3% zdTGEl|1vCT<_TIxp?UxAp9v7c{fFgv8ip!P|v&BrmK?nr2_E z{IH#9e8^M(aR}LlSyMTu| zDH>{69_zJQ@Y*bCB9fN*XXS(RV}D&sVBY-P`9DAS%jbvRvFzNP)xWuK@BGE@QP#7k zEtyw=-I*mhb)Je9RUx?g%*jh~E0%hfFPT?*bw%AIqkN*fe5u)riUb4yMoSQjV-o0A z0_%Y91g|ftUn%#bU{Bg)j zat|`|M%wMk@dJ?;?~dy(?@YUWbo@x*rMib1cq4NA<&B!1W_!#w#GO*&C_HbNO{p|? zvu&An`?unU13$&>FzFskUbO!7HZNKqk3|+!?Ni2Pjk1kXj(4Y;eVb|@KX%+G+jwi< z>GttyVnA2)8WZM0QalkMY2jY~rwWNWq&EiW3qUdm;}6xr?=quqOP zf1WW=u9sJ**u*b{*$m#shx0aNR92ep^l;ve%!|fHG=7bKjk3~TFKRQ`n?*`;GJ%%kMUP+l1@)gnSC2^3nm$Ku-?d2q^J`A2sI}@~f z*$>nHtbO~h%8O=`$C4LCE<>hCKQh7aBh+Pfl9W4jRRUeUnJVRf*;!JL8v(o0qe*X! zgpD3ml@Y06VV8ZPqOy6t9;YGOj{?N?({s` zI#*}e$B!I0+&0F#hKZ)HX!Z~dFUq=ML|#8JZ_cF-jWv`@id=ymOgS!VFxDBNHQbO~ zd*0aDqioX+yQK^|2g?eSuVTn|modJcoFwJ>K$euh=)XmVR(XWYWl?;X={Lq({6@AT z)OPE;Hsuz>4~-JPpr|M(0}8$<_sDjm$t~I(8V%2oZ`c^gwvQh(ZY1X4&>Gd$by(kw zu@sgUt&M1Ty{+3@ylCs8DE<_B7wgNDEPAiB$bK7S&%dXQan~|NUN0Wojr^n8Xtca2 zwh-<9BAUP3YVcTR`uk|~jXdjmXoNB6nd6pr-eS;VKW{bq8I2E{F>Xg$$8EGeMAPr7 zc6)04P~5jz)^O44_Ex{Wts4z5nr#?7n{uHnqS<(~v3l5IFK)wLwixsLWvaB7D*(G3 zuhDpn#!oc94S9vFzoX$z={Np_aU*Q1GSoe!MBe%ITF24!9!-W_{8vA0YB_kiXWN|G_|odHD!i&3w>{mPeXl^??`JmMy`iB!KNP zh!SwJPDMY?L`cX9^(8V-)L9A{u`VIjEyvvti0`bX%>qO_Lk8QF!PhIwVAv9`_grsN zmiTPSwl--~$WmfHz~=s8HU?V<(e{d@{P(+ln;t5Kf$gf*=8vAw)hh6 z(Xei_A=)t@qI#w&iIb-*ts8C1JikqOW>%b_I|gxNo^4p7GOT={GHk&BW!Pk-=TR!) zlXLS@-0NkEs>%TJe}70%qe1&DghD(WA{2EDokrsNQT`x818@ngxFfC@q+C({Ynag+ zwxrI1ZHcY@niJgdg&ZsF*QUV&U9k=BrwpDnursl(Uu#0M+?g7a+D}RKBq_<|gB1JZ zfy(wxHsx)E?XyGujm)#r-e#eHm_LRrHT1!2s}FeJxHe&A`Sl_E0jzg_NG|vO0PEKW zzytb9Xzi=1`ydY{FG)5fiERvwR|Y=c&uuR;`aImG3}1?T%AVzYg;rZ69rN0Rq<4K6 zbOnCAGQc)Wi4zuK;4$CiBPkws_?%)l%So5z0~3^i@Rb86^%FfYePzj`HsuI&Y;T&#o4XToIyb~K@gnmg>yP+0WtT5xM>{T*)s-=%JNW{o z;c=T{-;Vn;lP>ei3q{@p48`Si=?wjrolbEF~%bQ%y*Eicx9NsBLD8eI*5Or5p6M#dnYO9$3^9Z z<^1EjJN&b@^Dl#@D2PRUl7Q2AA97%Ud}oV~!WcH>$0^SsbRs+eJ&s7RDHYIrC-zDd zgcEQ*31JUpufw*Q5N0FXjSxWjNYI;rFcV=OLIuJqgpCMyAl!rS2*O^3R}g-UKo1=U zE5Kcc(ogA+v*iQuuKyq#r@XpanO#?bS3L8?Q48b3RZ&`AU*QSM!>h1MYfJ0O zBQxEl;aLT>c$+y4N#umkax1DUg6+-0+s=x31vj^1WmQ=P-^j&xKPu|-c*^F2T2Ixo zD&x@9{2)UMYecn$HFaGg_epaxLutI!zp#WzeN|#rt%j#-ZsLQJAaFz5(G?YnVQkWgMNStB5Rr z%#ZzrB#<&D{5|f8=j1HH8A^HL!CCJqcREAJNJYaBp3&?jzM#*umc=-0SuD?5Mplj1 z4$p)XSJf=5jo>Ma!PrGi0x=21BoLE8Oad_p#3T@tKuiKL3B)82lR!)YF$u&Z5R*Vm z0x=21BoLE8Oad_p#3T@tz}G5)hEX=+yZ^PQc4Y z+mu%1HvqnkaG2i>S8d975IWC}Q?>!Vi?9RfPQZdOHl>Jk04wpXeKY862Xu_XJAwEn zc_!dJ2p=Kc3^+aArsRT$62PA$Y(}~bFl#*CZwJjnKtDnT+X9R~5zqgDqXPO6-eErA z(38MF@-qS7M|c$NrexTZn-JFH8}Hiz|BbK}`RW9laxp?K`2@TTA$t<&0R9=F6?Bx7 z@opNzVWb-Xhn<4=H-Ya2+=ftycAEj!Q*DYHG@XFIMRvfcOztipH{#_S&%d2Q!?46yok^_8T0`cPC;Fy z-GDD4WH2A_tTVwg(uIIGA#A|!fNTT&4T1;hPQbfy*6tbn9z-i(0nW)K;kO&yfKMSP zz-a|MaVl&8GIs*{5FP^lHo&hCa`D?6>NLm@VF!5td&Nr+<<>USOS_Uxp>F;T*wjmoq#8u557UinQv2eBHTq?0A5-E zTf*6~2Ea2efbP+53E&S9+K_GrjK9#Pe9pcDUW`x#9^8NrBUB^Z4EQC&4A4v|v?+5C z8jxQCcn88}q_+dMBNPHhxd`vgAnamWfImPu$hH9c&xc;9cfh|Ps7R{|z&pYw;J5*I zA+$jLt$=9@VWUWA0zQDSk@FJ)xdDFn66k=G@BfTB)^$NUekF>hXrd)yW7VNMA@F0Q*`E7tD%fSQl0pCI> z1fQLNu1W;(Qv&!D!d>9A74W1g$OZXMzzeQGebCtk*zZc%D%%1qL)ZYBHvqnXFbwH7 zz&X{RgSOm&I}pmLFTepSY|38HR{={9-a^_9_z}XRpqWyGGiwNYQMVcJq*{zw)O7-W z58)%=ZwLGgVJq?7uzv(60&8euEa z4S+iknvre=e0de}q3e{@Hsu_IG~^coHX)>dw{3uj5Om~s0;aFQHPTMNN`zC9ZUB4; z;VYz@0sn%Kj=IX#@Jk3Dq)PxlLnuL7{fLJ{d)3z;MEJdE`Wi}j3) zIR65i?ZDXzcsD`|(uCU)-a(ph+;tcuNN)ms6~PG}+5qDlV7Ex80M11yM7jj9XdUbj z>Ft2OLMS3W;PUIC8>BY@zU_nGLppQ4O?eDq1JbR4Q*MBci38Y%uor3NyEf%yglCX; z0Ft0W2$jUwuns{`kuC)EAh?lk0DJ&pHSqy; zgf!xB#MvT*Oq{bQ0el=`3DVZHB|LXB<0gzR;1d=he2#SH%~(@y!q_BDK=Zr?;W@WJ zZlFVWDZ&RxZwDN?8FEIt8SwX8;HOCEHjFQXMB)Jc6+tD=?eOOa8M%M7j;|ZwQBw zR_=qW5FS9f0q`+|XOM0N{58T};sZ{&A3g_ZC*aH+ns;GhSfYos#)ixJi$?FM`t zVI$JbfU_Qi%`hLZ8NrEk8{lz2gnp4$0Vn+k^9#;JGyvX)kc9L$z$X!gA>9g?`VjPl zbP3=m4`c3Q`VpJ*1VTE}ZGboY81oU*+X43>%s{#oaMq*nlf(x!&xa7+k5G+#!k-|l zN7{PkgXclwegYqde8OaeW~5cXsR)OVb^>08@D9?O0AE7*0O>Zs@sC5-=b>MK=2;Bu z`3dX!3ZA`q?+LVp^B08e2+c?neu8iaX~Ka&#W+EFJK&Q)gPkC4JsV;@JHj(0$-8Wd z3LL^K5wejcT!)ZPI=i8VXW`Gmr}~^t$wP1=T>^MBf{W=_Y|2uELZo?)^LB*ESP%0I z@P32@u1NsDKv>VU{qLa@ge0W--H*ixletC${4T;*T&n;+g^-VVpJ()WmcIq-H^OR! zHl%r;?`ebv_%xo^YeRSxX`Z{weh`2n`&wfPX=#hK z0dF(lE(4xzz;D_vlD;qb`P69dD+3O_Q|4zFaIOKX4Y=NbcNuV}0pBp-rv}vjL;W!2 zAmI2Q+;751K{#*_D&kGhh~e3gw0Pf>QUQs)|(? zF5?ehDO>CdYgUBiRK}fG;gLUnMGXG_RgR+k+&I?DEKqRc$BU4Ae%?I%1Z&o$i97_1 zU*b9EoJ{dE_EjEv>g}SuoE)did5NMth_W+|EGxQ1dC+!FvG_TF;uZ2lrL1~!PEldr ztm4I)b5Q+zapx2lEe$`s2L#l&V4G_4V%4s^@7T}+Esa>pKTnh6f^tWI*3S$ zw=PTJZ^ssUa6WlOg{N*!@e2G3G*0%dti2MyFkDx)e0fD3e)aSS4EzYLx2`6Lkx0tL z_3nx?o?R}+FWo_R`0<=E$j8aW;^4VN{LnNx#c!2H0fQ%Cb67@exX2j9)O0ER(%ljNcqGO7d$<%k!(2)|J+YUqf0nd)`9)EN?+x zP?{@k>^*-2sMuXfLE&7sqGZ~^9kiz47hwhGRki2|WL;Cd^8dAWJ-=-fQTWx4r2s-$ zqK9&`dRY!loeBw}%BCu5BV47(O5HRUw5-=-dx`f)c4i&t#-CA%%eu{BlaK@n0X=Y_ zNJu>bLL!$+y&(CRV~%`pz3V2FD3>Bpu`5kx=FQBTd2i=UW|E!vNdN*mstvdl-EK`W{2@4dY<^|ll(jM>^g0$1MD~5F)-uSb;IxUiPpwk$KaG{ zk#PE!?>aUvq67WtL=7N94lJcBhRDPp{BnAF6J1YAQY1F|lkmcY87Pq!jT`-hvEa?& z<~YF0t;_Bs7_zz^>y1=6`rBG5R<+~UU~pWObBLS-ITBa zE(_2{(*P1@Y#pEHYcjT0OyoKv(m}8rQ%i=03;WM41CKv_;ol4ZMW}h4ZEZb0YeIOO`EP0n72Ovu-&p zc=E#2oJFsY23mi^OZhFq+kS;a$cFmTG=TpDgBQXV zCt0%3B#ZAQkVh5^fjI;P&3dxE)$bY}tL_jVF0fe6bdw z7tyNwRv-Jjj*yeW#cmGVS5G_RCXy&-ePyUB_(4gCK!fNx(Q^rPrWbPx@;o@`BT54Im|uk764 zIoP@I)rH-a-GklH?#f=YH`+tRVva5$dN-Pl=AzG{ZsbMxqcDol`dKuJ4x^)pwnqDh z`$zj!!nZo8OW|~Q7#@Yw!{TuCkT$0`!<}E47o5SKM>HFLJ$yDSKKS?nGC$_viw{p& Q3^~f4Oaqw){yh!+1 - /// A helper class for reading memory managed files - /// - public class MmfReader - { - private readonly ILogger _logger; - - public MmfReader(string mmfName, ILogger logger) - { - _logger = logger; - MmfName = mmfName; - } - - public string MmfName { get; set; } - - ///

- /// Turns the MMF into an color array - /// - /// - public Color[,] GetColorArray() - { - var mffString = ReadMmf(MmfName); - if (string.IsNullOrEmpty(mffString)) - return null; - var intermediateArray = mffString.Split('|'); - if (intermediateArray[0] == "1" || intermediateArray.Length < 2) - return null; - var array = intermediateArray[1].Substring(1).Split(' '); - if (!array.Any()) - return null; - - try - { - var colors = new Color[6, 22]; - foreach (var intermediate in array) - { - if (intermediate.Length > 16) - continue; - - // Can't parse to a byte directly since it may contain values >254 - var parts = intermediate.Split(',').Select(int.Parse).ToArray(); - if (parts[0] >= 5 && parts[1] >= 21) - continue; - - colors[parts[0], parts[1]] = Color.FromRgb((byte) parts[2], (byte) parts[3], (byte) parts[4]); - } - return colors; - } - catch (FormatException e) - { - _logger.Trace(e, "Failed to parse to color array"); - return null; - } - } - - /// - /// Reads the contents of the given MFF into a string - /// - /// - /// - private string ReadMmf(string fileName) - { - try - { - using (var mmf = MemoryMappedFile.OpenExisting(fileName)) - { - using (var stream = mmf.CreateViewStream()) - { - using (var binReader = new BinaryReader(stream)) - { - var allBytes = binReader.ReadBytes((int) stream.Length); - return Encoding.UTF8.GetString(allBytes, 0, allBytes.Length); - } - } - } - } - catch (FileNotFoundException e) - { - _logger.Trace(e, "Failed to read mff"); - return null; - //ignored - } - } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/LogitechDll/PipeServer.cs b/Artemis/Artemis/Utilities/DataReaders/PipeServer.cs similarity index 93% rename from Artemis/Artemis/Utilities/LogitechDll/PipeServer.cs rename to Artemis/Artemis/Utilities/DataReaders/PipeServer.cs index 3958c5040..6cd907c3d 100644 --- a/Artemis/Artemis/Utilities/LogitechDll/PipeServer.cs +++ b/Artemis/Artemis/Utilities/DataReaders/PipeServer.cs @@ -1,131 +1,131 @@ -using System; -using System.Diagnostics; -using System.IO.Pipes; -using System.Security.AccessControl; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; - -namespace Artemis.Utilities.LogitechDll -{ - // Delegate for passing received message back to caller - public delegate void DelegateMessage(string reply); - - public class PipeServer - { - private string _pipeName; - - public bool Running { get; set; } - public event DelegateMessage PipeMessage; - - public void Start(string pipeName) - { - Running = true; - _pipeName = pipeName; - var task = new Task(PipeLoop); - task.Start(); - } - - public void Stop() - { - Running = false; - } - - private void PipeLoop() - { - try - { - var security = new PipeSecurity(); - var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); - security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, - AccessControlType.Allow)); - - while (Running) - { - var namedPipeServerStream = new NamedPipeServerStream(_pipeName, PipeDirection.In, 254, - PipeTransmissionMode.Byte, PipeOptions.None, 254, 254, security); - - namedPipeServerStream.WaitForConnection(); - var buffer = new byte[254]; - namedPipeServerStream.Read(buffer, 0, 254); - namedPipeServerStream.Close(); - - var task = new Task(() => HandleMessage(buffer)); - task.Start(); - } - } - catch - { - // ignored - } - } - - private void HandleMessage(byte[] buffer) - { - var request = Encoding.ASCII.GetString(buffer); - PipeMessage?.Invoke(request); - } - - public void Listen(string pipeName) - { - try - { - var security = new PipeSecurity(); - var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); - security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, AccessControlType.Allow)); - - // Set to class level var so we can re-use in the async callback method - _pipeName = pipeName; - // Create the new async pipe - var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In, 254, PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, 254, 254, security); - - // Wait for a connection - pipeServer.BeginWaitForConnection(WaitForConnectionCallBack, pipeServer); - } - catch (Exception oEx) - { - Debug.WriteLine(oEx.Message); - } - } - - private void WaitForConnectionCallBack(IAsyncResult iar) - { - try - { - // Get the pipe - var pipeServer = (NamedPipeServerStream) iar.AsyncState; - // End waiting for the connection - pipeServer.EndWaitForConnection(iar); - - var buffer = new byte[255]; - - // Read the incoming message - pipeServer.Read(buffer, 0, 255); - - // Convert byte buffer to string - var stringData = Encoding.UTF8.GetString(buffer, 0, buffer.Length); - - // Pass message back to calling form - PipeMessage?.Invoke(stringData); - - // Kill original sever and create new wait server - pipeServer.Close(); - - var security = new PipeSecurity(); - var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); - security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, AccessControlType.Allow)); - - pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In, 254, PipeTransmissionMode.Byte, - PipeOptions.Asynchronous, 254, 254, security); - - // Recursively wait for the connection again and again.... - pipeServer.BeginWaitForConnection(WaitForConnectionCallBack, pipeServer); - } - catch - { - // ignored - } - } - } +using System; +using System.Diagnostics; +using System.IO.Pipes; +using System.Security.AccessControl; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace Artemis.Utilities.DataReaders +{ + // Delegate for passing received message back to caller + public delegate void DelegateMessage(string reply); + + public class PipeServer + { + private string _pipeName; + + public bool Running { get; set; } + public event DelegateMessage PipeMessage; + + public void Start(string pipeName) + { + Running = true; + _pipeName = pipeName; + var task = new Task(PipeLoop); + task.Start(); + } + + public void Stop() + { + Running = false; + } + + private void PipeLoop() + { + try + { + var security = new PipeSecurity(); + var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, + AccessControlType.Allow)); + + while (Running) + { + var namedPipeServerStream = new NamedPipeServerStream(_pipeName, PipeDirection.In, 254, + PipeTransmissionMode.Byte, PipeOptions.None, 4096, 4096, security); + + namedPipeServerStream.WaitForConnection(); + var buffer = new byte[4096]; + namedPipeServerStream.Read(buffer, 0, 4096); + namedPipeServerStream.Close(); + + var task = new Task(() => HandleMessage(buffer)); + task.Start(); + } + } + catch + { + // ignored + } + } + + private void HandleMessage(byte[] buffer) + { + var request = Encoding.ASCII.GetString(buffer); + PipeMessage?.Invoke(request); + } + + public void Listen(string pipeName) + { + try + { + var security = new PipeSecurity(); + var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, AccessControlType.Allow)); + + // Set to class level var so we can re-use in the async callback method + _pipeName = pipeName; + // Create the new async pipe + var pipeServer = new NamedPipeServerStream(pipeName, PipeDirection.In, 254, PipeTransmissionMode.Byte, + PipeOptions.Asynchronous, 254, 254, security); + + // Wait for a connection + pipeServer.BeginWaitForConnection(WaitForConnectionCallBack, pipeServer); + } + catch (Exception oEx) + { + Debug.WriteLine(oEx.Message); + } + } + + private void WaitForConnectionCallBack(IAsyncResult iar) + { + try + { + // Get the pipe + var pipeServer = (NamedPipeServerStream) iar.AsyncState; + // End waiting for the connection + pipeServer.EndWaitForConnection(iar); + + var buffer = new byte[255]; + + // Read the incoming message + pipeServer.Read(buffer, 0, 255); + + // Convert byte buffer to string + var stringData = Encoding.UTF8.GetString(buffer, 0, buffer.Length); + + // Pass message back to calling form + PipeMessage?.Invoke(stringData); + + // Kill original sever and create new wait server + pipeServer.Close(); + + var security = new PipeSecurity(); + var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + security.AddAccessRule(new PipeAccessRule(sid, PipeAccessRights.FullControl, AccessControlType.Allow)); + + pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In, 254, PipeTransmissionMode.Byte, + PipeOptions.Asynchronous, 254, 254, security); + + // Recursively wait for the connection again and again.... + pipeServer.BeginWaitForConnection(WaitForConnectionCallBack, pipeServer); + } + catch + { + // ignored + } + } + } } \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/LogitechDll/NamedPipeServer.cs b/Artemis/Artemis/Utilities/LogitechDll/NamedPipeServer.cs deleted file mode 100644 index b11bfab08..000000000 --- a/Artemis/Artemis/Utilities/LogitechDll/NamedPipeServer.cs +++ /dev/null @@ -1,186 +0,0 @@ -using System; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using Microsoft.Win32.SafeHandles; - -namespace Artemis.Utilities.LogitechDll -{ - public class NamedPipeServer - { - public const uint DUPLEX = 0x00000003; - public const uint FILE_FLAG_OVERLAPPED = 0x40000000; - - public const int BUFFER_SIZE = 100; - private SafeFileHandle clientHandle; - public Client clientse; - public int ClientType; - private Thread listenThread; - - public string pipeName; - - public NamedPipeServer(string PName, int Mode) - { - pipeName = PName; - ClientType = Mode; //0 Reading Pipe, 1 Writing Pipe - } - - public event PipeDataReceivedEventHandler PipeDataReceived; - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern SafeFileHandle CreateNamedPipe( - string pipeName, - uint dwOpenMode, - uint dwPipeMode, - uint nMaxInstances, - uint nOutBufferSize, - uint nInBufferSize, - uint nDefaultTimeOut, - IntPtr lpSecurityAttributes); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern int ConnectNamedPipe( - SafeFileHandle hNamedPipe, - IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern int DisconnectNamedPipe( - SafeFileHandle hNamedPipe); - - public void Start() - { - listenThread = new Thread(ListenForClients); - listenThread.Start(); - } - - private void ListenForClients() - { - while (true) - { - clientHandle = CreateNamedPipe(pipeName, DUPLEX | FILE_FLAG_OVERLAPPED, 0, 255, BUFFER_SIZE, BUFFER_SIZE, - 0, IntPtr.Zero); - //could not create named pipe - if (clientHandle.IsInvalid) - return; - - var success = ConnectNamedPipe(clientHandle, IntPtr.Zero); - - //could not connect client - if (success == 0) - return; - - clientse = new Client(); - clientse.handle = clientHandle; - clientse.stream = new FileStream(clientse.handle, FileAccess.ReadWrite, BUFFER_SIZE, true); - - if (ClientType == 0) - { - var readThread = new Thread(Read); - readThread.Start(); - } - } - } - - private void Read() - { - //Client client = (Client)clientObj; - //clientse.stream = new FileStream(clientse.handle, FileAccess.ReadWrite, BUFFER_SIZE, true); - byte[] buffer = null; - var encoder = new ASCIIEncoding(); - - while (true) - { - var bytesRead = 0; - - try - { - buffer = new byte[BUFFER_SIZE]; - bytesRead = clientse.stream.Read(buffer, 0, BUFFER_SIZE); - } - catch - { - //read error has occurred - break; - } - - //client has disconnected - if (bytesRead == 0) - break; - - //fire message received event - //if (this.MessageReceived != null) - // this.MessageReceived(clientse, encoder.GetString(buffer, 0, bytesRead)); - - var ReadLength = 0; - for (var i = 0; i < BUFFER_SIZE; i++) - { - if (buffer[i].ToString("x2") != "cc") - { - ReadLength++; - } - else - break; - } - if (ReadLength > 0) - { - var Rc = new byte[ReadLength]; - Buffer.BlockCopy(buffer, 0, Rc, 0, ReadLength); - OnPipeDataReceived(new PipeDataReceivedEventArgs(encoder.GetString(Rc, 0, ReadLength))); - - buffer.Initialize(); - } - } - - //clean up resources - clientse.stream.Close(); - clientse.handle.Close(); - } - - public void SendMessage(string message, Client client) - { - var encoder = new ASCIIEncoding(); - var messageBuffer = encoder.GetBytes(message); - - if (client.stream.CanWrite) - { - client.stream.Write(messageBuffer, 0, messageBuffer.Length); - client.stream.Flush(); - } - } - - public void StopServer() - { - //clean up resources - - DisconnectNamedPipe(clientHandle); - - - listenThread.Abort(); - } - - private void OnPipeDataReceived(PipeDataReceivedEventArgs e) - { - PipeDataReceived?.Invoke(this, e); - } - - public class Client - { - public SafeFileHandle handle; - public FileStream stream; - } - } - - public delegate void PipeDataReceivedEventHandler( - object sender, PipeDataReceivedEventArgs pipeDataReceivedEventArgs); - - public class PipeDataReceivedEventArgs - { - public PipeDataReceivedEventArgs(string data) - { - Data = data; - } - - public string Data { get; set; } - } -} \ No newline at end of file diff --git a/Artemis/Artemis/Utilities/Updater.cs b/Artemis/Artemis/Utilities/Updater.cs index 587cc74c8..da3bda3bb 100644 --- a/Artemis/Artemis/Utilities/Updater.cs +++ b/Artemis/Artemis/Utilities/Updater.cs @@ -15,12 +15,12 @@ namespace Artemis.Utilities { public static class Updater { - public static int CurrentVersion = 120; + public static int CurrentVersion = 1211; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); public static async Task CheckForUpdate(MetroDialogService dialogService) { - Logger.Info("Checking for updates - Current version: 1.2.0"); + Logger.Info("Checking for updates - Current version: 1.2.1.1 beta"); if (!General.Default.CheckForUpdates) return null; diff --git a/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs b/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs index c4b4223f5..2d1ff3388 100644 --- a/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs +++ b/Artemis/Artemis/ViewModels/SystemTrayViewModel.cs @@ -132,7 +132,7 @@ namespace Artemis.ViewModels var dialog = await DialogService.ShowProgressDialog("Enabling keyboard", "Artemis is still busy trying to enable your last used keyboard. " + - "Please wait while the progress completes"); + "Please wait while the process completes"); dialog.SetIndeterminate(); while (MainManager.DeviceManager.ChangingKeyboard) diff --git a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml index 8a03e5159..8e9f297a4 100644 --- a/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml +++ b/Artemis/Artemis/Views/Flyouts/FlyoutSettingsView.xaml @@ -113,7 +113,7 @@ -