From 7309201b11ba49b57997c5714c2fd997248b34e4 Mon Sep 17 00:00:00 2001 From: Regalis Date: Sat, 27 Feb 2016 00:25:13 +0200 Subject: [PATCH] File sharing fixes: canceling transfers, displaying transfers & progress server-side, invalid sub files aren't selected or sent --- Subsurface/Source/DebugConsole.cs | 14 +++ Subsurface/Source/GUI/GUIMessageBox.cs | 2 +- Subsurface/Source/Map/Submarine.cs | 12 +- .../Source/Networking/FileStreamReceiver.cs | 102 ++++++++++++----- .../Source/Networking/FileStreamSender.cs | 28 ++++- Subsurface/Source/Networking/GameClient.cs | 7 +- Subsurface/Source/Networking/GameServer.cs | 108 ++++++++++++------ Subsurface/Source/Screens/NetLobbyScreen.cs | 27 +++-- Subsurface_Solution.v12.suo | Bin 907264 -> 875520 bytes 9 files changed, 217 insertions(+), 83 deletions(-) diff --git a/Subsurface/Source/DebugConsole.cs b/Subsurface/Source/DebugConsole.cs index 881ba1590..d5d4b87db 100644 --- a/Subsurface/Source/DebugConsole.cs +++ b/Subsurface/Source/DebugConsole.cs @@ -417,6 +417,20 @@ namespace Barotrauma //Hull.DebugDraw = !Hull.DebugDraw; //Ragdoll.DebugDraw = !Ragdoll.DebugDraw; GameMain.DebugDraw = !GameMain.DebugDraw; + break; + case "sendrandomdata": + int messageCount = 1; + + if (commands.Length>1) int.TryParse(commands[1], out messageCount); + + for (int i = 0; i < messageCount; i++ ) + { + if (GameMain.Server!=null) + { + GameMain.Server.SendRandomData(); + } + } + break; case "netstats": if (GameMain.Server == null) return; diff --git a/Subsurface/Source/GUI/GUIMessageBox.cs b/Subsurface/Source/GUI/GUIMessageBox.cs index 5d25a0f2c..3546b5766 100644 --- a/Subsurface/Source/GUI/GUIMessageBox.cs +++ b/Subsurface/Source/GUI/GUIMessageBox.cs @@ -7,7 +7,7 @@ namespace Barotrauma { public static Queue MessageBoxes = new Queue(); - const int DefaultWidth=400, DefaultHeight=200; + const int DefaultWidth=400, DefaultHeight=250; //public delegate bool OnClickedHandler(GUIButton button, object obj); //public OnClickedHandler OnClicked; diff --git a/Subsurface/Source/Map/Submarine.cs b/Subsurface/Source/Map/Submarine.cs index 8c2879ee4..c1cc4d92d 100644 --- a/Subsurface/Source/Map/Submarine.cs +++ b/Subsurface/Source/Map/Submarine.cs @@ -631,12 +631,16 @@ namespace Barotrauma if (extension == ".sub") { - Stream stream = SaveUtil.DecompressFiletoStream(file); - if (stream == null) + Stream stream = null; + try { - DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!"); - return null; + stream = SaveUtil.DecompressFiletoStream(file); } + catch (Exception e) + { + DebugConsole.ThrowError("Loading submarine ''" + file + "'' failed!", e); + return null; + } try { diff --git a/Subsurface/Source/Networking/FileStreamReceiver.cs b/Subsurface/Source/Networking/FileStreamReceiver.cs index 8030dfff3..89f8c32fd 100644 --- a/Subsurface/Source/Networking/FileStreamReceiver.cs +++ b/Subsurface/Source/Networking/FileStreamReceiver.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Text.RegularExpressions; +using System.Xml.Linq; namespace Barotrauma.Networking { @@ -18,7 +19,7 @@ namespace Barotrauma.Networking private FileStream writeStream; private int timeStarted; - private string filePath; + private string downloadFolder; private FileTransferType fileType; @@ -28,11 +29,6 @@ namespace Barotrauma.Networking private set; } - public string FilePath - { - get { return filePath; } - } - public ulong FileSize { get { return length; } @@ -69,14 +65,13 @@ namespace Barotrauma.Networking public float Progress { get { return (float)received / (float)length; } - } public FileStreamReceiver(NetClient client, string filePath, FileTransferType fileType, OnFinished onFinished) { this.client = client; - this.filePath = filePath; + this.downloadFolder = filePath; this.fileType = fileType; this.onFinished = onFinished; @@ -92,8 +87,10 @@ namespace Barotrauma.Networking } catch (Exception e) { - DebugConsole.ThrowError("Error while receiving file ''"+FileName+"''", e); - Status = FileTransferStatus.Error; + ErrorMessage = "Error while receiving file ''"+FileName+"'' {"+e.Message+"}"; + DeleteFile(); + + if (onFinished != null) onFinished(this); } } @@ -102,21 +99,18 @@ namespace Barotrauma.Networking if (fileSize > MaxFileSize) { ErrorMessage = "File too large (" + MathUtils.GetBytesReadable((long)fileSize) + ")"; - Status = FileTransferStatus.Error; return false; } if (type != (byte)fileType) { - ErrorMessage = "Unexpected file type ''"+type+"'' (expected "+fileType+")"; - Status = FileTransferStatus.Error; + ErrorMessage = "Unexpected file type ''" + type + "'' (expected " + fileType + ")"; return false; } if (!Regex.Match(fileName, @"^[\w\- ]+[\w\-. ]*$").Success) { - ErrorMessage = "Illegal characters in file name ''"+fileName+"''"; - Status = FileTransferStatus.Error; + ErrorMessage = "Illegal characters in file name ''" + fileName + "''"; return false; } @@ -125,9 +119,7 @@ namespace Barotrauma.Networking case (byte)FileTransferType.Submarine: if (Path.GetExtension(fileName) != ".sub") { - ErrorMessage = "Wrong file extension ''" + Path.GetExtension(fileName)+"''! (Expected .sub)"; - - Status = FileTransferStatus.Error; + ErrorMessage = "Wrong file extension ''" + Path.GetExtension(fileName) + "''! (Expected .sub)"; return false; } break; @@ -138,12 +130,17 @@ namespace Barotrauma.Networking public void DeleteFile() { - string file = Path.Combine(filePath, FileName); + if (FileName == null) return; - writeStream.Flush(); - writeStream.Close(); - writeStream.Dispose(); - writeStream = null; + string file = Path.Combine(downloadFolder, FileName); + + if (writeStream!=null) + { + writeStream.Flush(); + writeStream.Close(); + writeStream.Dispose(); + writeStream = null; + } Status = FileTransferStatus.Canceled; @@ -170,9 +167,9 @@ namespace Barotrauma.Networking if (length == 0) { - if (!Directory.Exists(filePath)) + if (!string.IsNullOrWhiteSpace(downloadFolder) && !Directory.Exists(downloadFolder)) { - Directory.CreateDirectory(filePath); + Directory.CreateDirectory(downloadFolder); } byte fileTypeByte = inc.ReadByte(); @@ -183,11 +180,12 @@ namespace Barotrauma.Networking if (!ValidateInitialData(fileTypeByte, FileName, length)) { Status = FileTransferStatus.Error; + DeleteFile(); if (onFinished != null) onFinished(this); return; } - writeStream = new FileStream(Path.Combine(filePath, FileName), FileMode.Create, FileAccess.Write, FileShare.None); + writeStream = new FileStream(Path.Combine(downloadFolder, FileName), FileMode.Create, FileAccess.Write, FileShare.None); timeStarted = Environment.TickCount; Status = FileTransferStatus.NotStarted; @@ -218,10 +216,60 @@ namespace Barotrauma.Networking if (received >= length) { - Status = FileTransferStatus.Finished; + writeStream.Flush(); + writeStream.Close(); + writeStream.Dispose(); + writeStream = null; + + Status = IsReceivedFileValid() ? FileTransferStatus.Finished : FileTransferStatus.Error; if (onFinished!=null) onFinished(this); + + if (Status == FileTransferStatus.Error) DeleteFile(); + Dispose(); } } + + private bool IsReceivedFileValid() + { + switch (fileType) + { + case FileTransferType.Submarine: + string file = Path.Combine(downloadFolder, FileName); + Stream stream = null; + + try + { + stream = SaveUtil.DecompressFiletoStream(file); + } + catch (Exception e) + { + ErrorMessage = "Loading submarine ''" + file + "'' failed! {"+ e.Message + "}"; + return false; + } + + if (stream == null) + { + ErrorMessage = "Decompressing submarine file''" + file + "'' failed!"; + return false; + } + + try + { + stream.Position = 0; + var doc = XDocument.Load(stream); //ToolBox.TryLoadXml(file); + stream.Close(); + stream.Dispose(); + } + catch + { + ErrorMessage = "Failed to parse submarine file ''"+file+"''!"; + return false; + } + break; + } + + return true; + } public void Dispose() { diff --git a/Subsurface/Source/Networking/FileStreamSender.cs b/Subsurface/Source/Networking/FileStreamSender.cs index 86c2caf4b..8832ba541 100644 --- a/Subsurface/Source/Networking/FileStreamSender.cs +++ b/Subsurface/Source/Networking/FileStreamSender.cs @@ -11,7 +11,7 @@ namespace Barotrauma.Networking enum FileTransferType { - Unknown, Submarine + Unknown, Submarine, Cancel } class FileStreamSender : IDisposable @@ -39,6 +39,20 @@ namespace Barotrauma.Networking private set; } + public float Progress + { + get { return inputStream == null ? 0.0f : (float)sentOffset / (float)inputStream.Length; } + } + + public int Sent + { + get { return sentOffset; } + } + + public long FileSize + { + get { return inputStream == null ? 0 : inputStream.Length; } + } public static FileStreamSender Create(NetConnection conn, string fileName, FileTransferType fileType) { @@ -68,7 +82,10 @@ namespace Barotrauma.Networking public void Update(float deltaTime) { - if (inputStream == null) return; + if (inputStream == null || + Status == FileTransferStatus.Canceled || + Status == FileTransferStatus.Error || + Status == FileTransferStatus.Finished) return; waitTimer -= deltaTime; if (waitTimer > 0.0f) return; @@ -112,7 +129,12 @@ namespace Barotrauma.Networking //Dispose(); Status = FileTransferStatus.Finished; - } + } + } + + public void CancelTransfer() + { + Status = FileTransferStatus.Canceled; } diff --git a/Subsurface/Source/Networking/GameClient.cs b/Subsurface/Source/Networking/GameClient.cs index 7b938b5d6..486858c4d 100644 --- a/Subsurface/Source/Networking/GameClient.cs +++ b/Subsurface/Source/Networking/GameClient.cs @@ -713,6 +713,11 @@ namespace Barotrauma.Networking fileStreamReceiver.DeleteFile(); fileStreamReceiver.Dispose(); fileStreamReceiver = null; + + NetOutgoingMessage msg = client.CreateMessage(); + msg.Write((byte)PacketTypes.RequestFile); + msg.Write((byte)FileTransferType.Cancel); + client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); } } @@ -736,7 +741,7 @@ namespace Barotrauma.Networking { if (receiver.Status == FileTransferStatus.Error) { - new GUIMessageBox("Error while receiving file from server", receiver.ErrorMessage); + new GUIMessageBox("Error while receiving file from server", receiver.ErrorMessage, 400, 350); receiver.DeleteFile(); } diff --git a/Subsurface/Source/Networking/GameServer.cs b/Subsurface/Source/Networking/GameServer.cs index b970c6c9d..b408f8d6f 100644 --- a/Subsurface/Source/Networking/GameServer.cs +++ b/Subsurface/Source/Networking/GameServer.cs @@ -312,13 +312,36 @@ namespace Barotrauma.Networking foreach (Client c in ConnectedClients) { - if (c.FileStreamSender!=null && Rand.Range(0.0f, 1.0f)<0.01f) + if (c.FileStreamSender != null) { + var clientNameBox = GameMain.NetLobbyScreen.PlayerList.FindChild(c.name); + var clientInfo = clientNameBox.FindChild(c.FileStreamSender); + + if (clientInfo==null) + { + clientInfo = new GUIFrame(new Rectangle(0,0,180,0), Color.Transparent, Alignment.TopRight, null, clientNameBox); + clientInfo.UserData = c.FileStreamSender; + new GUIProgressBar(new Rectangle(0, 4, 0, clientInfo.Rect.Height-8), Color.Green, GUI.Style, 0.0f, Alignment.Left, clientInfo).IsHorizontal = true; + new GUITextBlock(new Rectangle(0,2,0,0), "", GUI.Style, Alignment.TopLeft, Alignment.Left | Alignment.CenterY, clientInfo, true, GUI.SmallFont); + } + else + { + var progressBar = clientInfo.GetChild(); + progressBar.BarSize = c.FileStreamSender.Progress; + + var progressText = clientInfo.GetChild(); + progressText.Text = c.FileStreamSender.FileName + " " + + MathUtils.GetBytesReadable(c.FileStreamSender.Sent) + " / " + MathUtils.GetBytesReadable(c.FileStreamSender.FileSize); + } + c.FileStreamSender.Update(deltaTime); if (c.FileStreamSender.Status == FileTransferStatus.Finished || - c.FileStreamSender.Status == FileTransferStatus.Error) + c.FileStreamSender.Status == FileTransferStatus.Error || + c.FileStreamSender.Status == FileTransferStatus.Canceled) { + clientNameBox.RemoveChild(clientInfo); + c.FileStreamSender.Dispose(); c.FileStreamSender = null; } @@ -522,11 +545,13 @@ namespace Barotrauma.Networking var outmsg = server.CreateMessage(); outmsg.Write((byte)PacketTypes.RequestFile); outmsg.Write(false); + outmsg.Write("File downloads disabled by the server"); + server.SendMessage(outmsg, dataSender.Connection, NetDeliveryMethod.ReliableUnordered); break; } byte fileType = inc.ReadByte(); - string fileName = inc.ReadString(); + string fileName = fileType == (byte)FileTransferType.Cancel ? "" : inc.ReadString(); switch (fileType) { @@ -544,6 +569,12 @@ namespace Barotrauma.Networking if (fileStreamSender != null) dataSender.FileStreamSender = fileStreamSender; } break; + case (byte)FileTransferType.Cancel: + if (dataSender.FileStreamSender != null) + { + dataSender.FileStreamSender.CancelTransfer(); + } + break; default: DebugConsole.ThrowError("Unknown file type was requested ("+fileType+")"); break; @@ -1079,6 +1110,12 @@ namespace Barotrauma.Networking server.SendMessage(outmsg, server.Connections, NetDeliveryMethod.ReliableUnordered, 0); } + if (client.FileStreamSender != null) + { + client.FileStreamSender.Dispose(); + client.FileStreamSender = null; + } + AddChatMessage(msg, ChatMessageType.Server); UpdateCrewFrame(); @@ -1516,38 +1553,38 @@ namespace Barotrauma.Networking /// sends some random data to the clients /// use for debugging purposes /// - //public void SendRandomData() - //{ - // NetOutgoingMessage msg = server.CreateMessage(); - // switch (Rand.Int(5)) - // { - // case 0: - // msg.WriteEnum(PacketTypes.NetworkEvent); - // msg.Write(Rand.Int(Enum.GetNames(typeof(NetworkEventType)).Length)); - // msg.Write(Rand.Int(MapEntity.mapEntityList.Count)); - // break; - // case 1: - // msg.WriteEnum(PacketTypes.NetworkEvent); - // msg.WriteEnum(NetworkEventType.ComponentUpdate); - // msg.Write((int)Item.ItemList[Rand.Int(Item.ItemList.Count)].ID); - // msg.Write(Rand.Int(8)); - // break; - // case 2: - // msg.Write((byte)Enum.GetNames(typeof(PacketTypes)).Length); - // break; - // case 3: - // msg.Write((byte)PacketTypes.UpdateNetLobby); - // break; - // } + public void SendRandomData() + { + NetOutgoingMessage msg = server.CreateMessage(); + switch (Rand.Int(5)) + { + case 0: + msg.Write((byte)PacketTypes.NetworkEvent); + msg.Write((byte)Rand.Int(Enum.GetNames(typeof(NetworkEventType)).Length)); + msg.Write((ushort)Rand.Int(MapEntity.mapEntityList.Count)); + break; + case 1: + msg.Write((byte)PacketTypes.NetworkEvent); + msg.Write((byte)NetworkEventType.ComponentUpdate); + msg.Write((int)Item.ItemList[Rand.Int(Item.ItemList.Count)].ID); + msg.Write(Rand.Int(8)); + break; + case 2: + msg.Write((byte)Enum.GetNames(typeof(PacketTypes)).Length); + break; + case 3: + msg.Write((byte)PacketTypes.UpdateNetLobby); + break; + } - // int bitCount = Rand.Int(100); - // for (int i = 0; i < bitCount; i++) - // { - // msg.Write(Rand.Int(2) == 0); - // } - // SendMessage(msg, (Rand.Int(2) == 0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable, null); + int bitCount = Rand.Int(100); + for (int i = 0; i < bitCount; i++) + { + msg.Write(Rand.Int(2) == 0); + } + SendMessage(msg, (Rand.Int(2) == 0) ? NetDeliveryMethod.ReliableOrdered : NetDeliveryMethod.Unreliable, null); - //} + } public override void Disconnect() { @@ -1559,6 +1596,11 @@ namespace Barotrauma.Networking log.Save(); } + foreach (Client client in ConnectedClients) + { + if (client.FileStreamSender != null) client.FileStreamSender.Dispose(); + } + server.Shutdown("The server has shut down"); } } diff --git a/Subsurface/Source/Screens/NetLobbyScreen.cs b/Subsurface/Source/Screens/NetLobbyScreen.cs index 215131455..bcad8ce5e 100644 --- a/Subsurface/Source/Screens/NetLobbyScreen.cs +++ b/Subsurface/Source/Screens/NetLobbyScreen.cs @@ -55,6 +55,11 @@ namespace Barotrauma get { return modeList; } } + public GUIListBox PlayerList + { + get { return playerList; } + } + public GUIFrame InfoFrame { get { return infoFrame; } @@ -505,14 +510,11 @@ namespace Barotrauma { valueChanged = true; - //Submarine sub = (Submarine)obj; + + var hash = (obj as Submarine).MD5Hash; - //submarine already loaded - //if (Submarine.Loaded != null && sub.FilePath == Submarine.Loaded.FilePath) return true; - - //sub.Load(); - - return true; + //hash will be null if opening the sub file failed -> don't select the sub + return hash.Hash != null; } public void UpdateSubList() @@ -657,10 +659,7 @@ namespace Barotrauma public void ClearPlayers() { - for (int i = 1; iM$0Hgc(2<*Hh=6#>6pmFJwsQYleC3^>Z4p zz%f=+j-lq!%nG&Gy=DexWtK)}W@?0vIZgFCWR8|&`2L@RqS@t}Z|42J-}gTL{;Yj_ z_GRt0)?Rx(M^8;J&nqeA=|ZVckr5W3z-+ef0NrM@-3B^Wu-R4tmB3P98L$F)6Ll-M zE&7TD+Rm1zoG$)=rQnUsVDEfkJ{!E_Z^B)SjrC?pU2mC!mcK#W6TlBwu3YhY=SZ8_ z%id0Mf1v~O7Rsp-O0Rg=$kEz#^qhhAgT3#_J}Q7XkNlG%Bx?xociLokxM{O>03v{nKqSx!hypqTPM{0m0%8I0ay@ck z4sJ$_(Hz(j}nnfFnr1gsQuceh4%ZGyxcj^6vrg z%oYLK8D)P1y%RK3vDwN{t|A=)oIv^*>ZSwlFq?N-pgnB~(wBizz#oCVz(L?J+6IG8 z#d`t3TBI{TKLfQk#@i4UGH?kBE}`L@0LrcmK>l+ys6u`z=pev}vU`DENGAbL1E)}b zA82=!U$%K;+ef5Zum283lTp+F+5>b4=&hiSfDQw4fnLam;q^YCU&^h+dOblQ!Bz)9 zpn-(S{#f_}Hm|RJRQg%8aiQ&3sG1L&2x@7=f1-T7HBBH9cm#F+F7Rh7+E{6?XO?S+ z%aJb#15NMT;9(kj5iK16)%O=dy-mUGgaM}aLU6v+;l@I5X}gg$B1!7%bqkuZ2$T6^ zq<8+T81FMrMefK5ImrIU?!2EX*!hJw?tC6t_s{LT&9(=uz76kJf=&c|4)hDa60u(q zhfe>vJYb*yyX2wjb{6gRU5MC`5uO_PugC;jwBv6nK~h)+<~9eb?GD5P=aBEqyb*n3 zLkA-JG_n@QW5AfZk(Rvo_8G)>dDrzx()|oV*$~0o*k=?g@uu97B=i%!GwzrgXT9Ub zI|b-t9@;DeVv!yIY)0A=)_l}Ek*8aJFNExHq&HVY+=*f@@C!4tdbMU0g}46CX7p%? zHL40+8`aW4%6!TgzhivEo>1;AVy74-uT?U3WPVZCuIg?UP0{%*TAGDfw&@y`=Ck1< zn=a7sB&nOFTXY^J-ZzAvRC+hty206^aAzQEVzml$soWLA8d z7*2AR$YK1M?(7l9T?wp(ac3+OBwFuc-6czs+lJ(&qov`@Kf?Kc6BD@0&04zbaGpVD zo*hCoj~>Y`x984iHbkJh0yUWGJX#1ZpT)`td7T;3j_89YnAg+6#a*wkmVV@vSl8C+ zN^H2sERL?eaX30Jv60f_KPoDf+49O5mpA%PkvyZ8)iPrcY`w)yJT!5L+>u9IW@kj2 zzfaIBst3xUus0nk>!Q@2iY`b+LPvpS?-SZpRJW_TD5*Kq(0dM=3T2hTn4E4TD>g z`hj}7#dehE7A4Abi>CYn)ckQ8(Zag&(q?f5<7||0Mf8PB0(i$=V=+SL_h8sPKpzP3 zW1ypfK@_@D7#`jaMIImzMV8FX7F(G@Wh;g7n9ZmOK+UVbaln$^GoXJ5x}k2x)iT<( zQb^NQB6l}Rm(UL@g^tQGWS?OawMrPzzMuuGg!Yj#Mp%y)UjvhXJfIU`t&D0`36VmG zKnGU|QNeLnOGkqK1xuXDye&^gb60`T*pWO@3=)_joM6631fi;sIU&bV6Z1VPn3k#H z(PL1@@~M7}(2r+UNp;LuDfFo-63+|$O}bDdKBA1o9%l5V`S*)MRBL!AX4|%JdUl(n z@sx3Nqd+_42LZFJ4uK6o z6w3b$x(DUKNS{Y~A}|(s6(~Si66$sXzd`zblxd)QAqZVTzXnYeY!oz1xJ{VLsLwFL zm3t>Pv^OfhLZt&V5Ez0NF9BzOnaGa@b|PH_dKR<}XaW{tpevvs0Q-?%ZKEv5Xp_08q zDwVmFFdCXGrBMBoLO-g@mA-K-tt+c;-)9>?y*Ogvs-<_2q^uP}2D`h}7$u60?W+z- zNkP-6JbdTH35}^cSjPi@auX#72!i3p3++{AKY&#)>R8 z%%n^c!#Y0H6*_oDcJTYBiC&S`Jz<2>o!x|J>b+Wen-vm!T)IL%=NX-Nr&;2k4Dvi; zsdKrd>SdyXPTk6)DykFzZ<+cL*i&ndtmo%Uy>F%HU^6HyRvbl*Q}sZqj!+anbDH!x z4!h?t(Q5HT5N$uH=91^{s!HnDY8VxrkSZK$AD3*HbGzAV)L(Ybql&LiP|r7n4%Gcq z>B*`=_DtV)F`P1!#V+(nkP=KuM};~^U01tp=U>kc){1LR*%oXHq}fjj?dkcsf=)&E z8IhFviBRWzPZX-EMc8?d)X01dqGEXvJbH*&&D8s`m^nb3RWnL)cs>Qu#pAtYMSC80 zR7|);&P>Gys*b{$LT#m`({4y*_hh73muWElWE;N7D^7E7({so72P*M zVD=x`6tb5Iw`%Da(6U8Ws4pzU|JTN2!T$r}VX@-ZjK>;canej`xTGiYwdcjcfLc>3 zWo$Lgu}jNn+g7b3t(Y%H@?I_CJNByaQmL@Vpt<9v7NHJwg7k>-x^dknn%%*?lNz&_ zi<)m`x;L+{$vuIRM`Q)W#!5q|;kX&*SoTVvmkz9%+x&-ZU(Qg*c?nzE7d@9DnGlKC2fjQ>Ws9kPv^b8MH|Q~r&mR2t zq;g>MeO=OK_f4Q%zom&|SqLTcVJ5BFrs%wGznICYN+c|53>H<@Am#Xv`Cgb)n?mr? z25AMOB?|=ywI3<<;OPa@-T=oJ!+Qk}yF7E%J%cyB%N26~cSN&h2IG`f3nrR^8qO!p zkbFU0n=f8uRjHzDldDXUxJwt-F_8_r?xveH;Lh*-jKHv26Vy;1`l$33qu?xKC=IQX zo^`x;`Ok$9J@(>-@$~80KXnOwt92nA>ZLzZmx}?t2`+U!?HejZaL*FSqxpTwFC`z7 zZohFQat<687yrIO3J|;_R)kSgfh_TeROwC@bZJ3{g}0oD&&z$bzV4oni(s|G=&@eX zJapxKCOp!cAu6gHok4SNWX%`Ka#s6<#B|!TL5lSsHbEWxROR|I`~KU!K7Vj;Uh+Jh zJT8V-RI7YkFb;vA@U5aE^Pe@;)?xl@J~^S@vNG25vxeFyg^=gCgcVN0?cqCV52HmJ zm0rBpF6kYys!4>$_=VWyKb-c%n0xwZVf_9*l2`L9pfFFQxprwO3E@h}%OP?TGqT|r zxA=KvqbnPvk0k3JN=KiOq8(PM9%^XOH$chkET>D2R%+-*DK7jJQcb`~z-sd+(Dv9b zD&Hv0=IoGkh4}(yfxa=6ZdBY&3E{nuNN>xvf%0G`b-i&{s5D4k?rW4xpGOdUUrR#m zB_=V`>a;)RmjjUF?`(6}gGiz9%ENfO_-cBQc=bBend*)UDp#AO@8l{;j`J_&akr2s ztwVFZI9M(dtIDPRn(bEPV}Cq>aw(7YHc07J70{^LP?!4S`U+{eU!k^D7qE)z^4b{j zG>gd4T323%184zYO?V-wN2Qi=(p;h5Z`2O7UJMg%>`-{!Z|8E=IFyFpSR=m-Hmct5 zpS+E`zLeH8zj|9mZQd_zWa@RC>BdhzkRz?V=iR6 z-B?ZM4@>@gxZqM~?nlxv&DII^KMIN&%TRLF9@3Tfr2#)RL|eXNSdOUk##XmjF=TMo|5y_6v~@?FR8bxB&wHD-Ez)@$ zCx|b$DgbZkC<{VGwZtW-d>H|OHa)u}y~Rk1k!pHcJG>npvo(cl&kp3*z+HF@SKuWLk-rrEi{!2xxlvp# zEar z(EHb7lU5$n5bKcw5Qu3yszHlq1>~|Smt5n15$nCi>KdfC0WaTBX4Ubl`By-BlS_8C z?jCoYQW`~Hj4W&#^+14Nx!I9CGDAMis*WgWA*(Rwa-agJ1gu@)LyssgF<+A+_~PVX zuv4JYlWYjDo*hujXwL(3I#0i>><#ec$pv&OPA{j-oklSK?!kb$0oU0wptr3TEXV|0 z_j=lrZEpNcwIv`LQGzgbv2Bj6$o8OZI?}UHT4ZwaV|A|2d0(z0v0pcwOdc8f(u$UJyH{p;H!}X z>Ctbo1!rplc4J46%T?4wl7m{-$qCf>n*0hcJ|;&Y$bj?og?c$Zm@4m4UHstofJE>v zPU}wHYXg>SO6;vL-$!x)39swP)KnWV{`w+2U0Y--rk#dG_5y8NB=!4L>IM`2Ce4n~ zx{~K&Kmx6ZlEP>`M<L{ua^nrZMk$}mJHB;6pbtV;8$`UnAlo8SM$ttfpAr~<& zH3mQ%*WWJ&b4R1x+-Z|=4trTCX<<}-j~dKlF3U&dO~QE;249q;c$A==x`hiD0$MuM z2CIVP3sD42q@L{E^qIUkOf8q`)P;b2D(I;X;cKGQsiH4T5ja9uEnTQQ6n7iv-8$Ti zJoO8aPTi&qred!a%*TW)K7$J*)s^yfCJemJ1UL3C*}vayOt@*MlTn|7f0IEird`|V zeeh0Y-APTOv;xW-DaY|XW7Jyl`Y3Izwpg6xm#37QSPhTt9jwwrBkoX=sHUTuLB45v z2%kG%U20HuId-(#qet^0OV!B&ZNDgolkg8-ISSGmmRDN7X0j6qiR)@mkqIXE(}2culU7CsObrDTkjsss=K;{I(uLF(=d|zI^EEse6@Z za=r}}Ie$Xk31@VSlFZBIC<7U-St~DQ2{f^z+Me%iRMTPiaF6A<$9~7Cp~UD;jut(R z@|)GMlvpqKu?CH!+}_G*I{T4)x;9H$&v?VVid)(ALbEFP3NTz^7o(6I->S`2{;b)D z_qn8&3bZ~_-OO7?D0Pf7K9n2%1CzU6PQR%~%6k-+G5a29!3#&g`iLLYGXfRAqejrz zAL@}jRnqn|Dww8Dr8Q%4t^E&N7laZlDY;gA58K(%68fE53!}vMm2f_5nf3*v%Kgef`ZP>%@UgozL~CdD zM%3o4$543G9@K9dwL-%|JvShPN9@(k1X1yCv8<+7G#6DrWk@_%(s3R&->WFrmx-3Q zl_NZ1yLJep)nZ?ruV^^Z-H$`snz-hqyiuCQd+yfW7peJztWf3wJ&q?H){f!;y5>~; zH=Ul??p{>A8tUAvT_2>)r2g{h1()DWSZ2~wsUTW)@ak{0LX|3)X~~oq zuO{-M2z@nHJ`&4#=|VspPq?HV(#TPw=(G|l$K8sK{G%I!Vp^50pltlnRc+pfz+?6ZohYeRSOQ*|xDH6~_OcK4vTp=wg|J7%IM3 zb@Gtk=s6OVELWhW?~HltIu)UolJjYB;h7?REThO!b0DcN81wk&HYYS*Jo~=II9ms5#k^u_ZcWY;74N_Ju+{ zAL``MJlr!?_rM|jK#!%|94&-0X6eQ3BRctKtqT==Y8FsIC5+M&OZ7*nv`TGtX9r!g zIF={Y6Rf`Bl)dRJ|0S>OEwEq-eRGdCi1KswBz|(1-kFi(vf+Sa=xa&n2d{bUGRu7D zifNR$(@U62Uh z=*dlD?56ROfzPiU&C%p2(?-(j66l1p2>e|8Fj%esO}|X%Ho~4dccHA)G!{X-WFv

8jykRPrM0PR?nN$DtQ=oZFQybtrAojM0>t4%^o~#7M)Eztb-T zQ06R_$MXcEhEercNg-Dt!pg;=Mmbe~sq0j|#t7ria3ceU|4KE2Ben;@h30 zn^$)*3WCYK+L%v`tJFxUN;LLSNsu~{8a~rhDtgXz^Ge+)g}d_zY*o{DMmVoZGG@5Q zQ={ke{IP}!^;n`s^H_&*M5G#Bana=-N*GTIGxp&u`ACykFB&mQya;z}Auet<`sS#X zMDB4$gKw=Ua3#*TB;%sH)HcONv*Q&6&UPYj=9_9r^yG80UiGYI@;;-Dw?rx^hh&_dYJ||tS%%J|W*M(A_7Z)ZV%}Co zN)8_~*}%2Mxj`Gl#}pYpn4p)?e{zZO9;=;eC??h4p@hn~W!j2q#O)?NXG}N3?Dydo zazAD`3HOxgv^murt-GNNRy56Kgz;o`E6Vsp9gq+}Mi=J#XMV=nlq3l^feT`%n8eQT`WnkLvg19QOM= zO-uDuKBUG7VZNn?;9dVo2G#sdSNQsE#xA*bpqUUv3GW++5DWHy#<6^1e+-lm1C6G~ z8;zN|h+hpluwvOpbq9@9>kCPsm_?6%X6&YKMw_XW_o1P0@-!JDH6AhG^vGV8m4wn;u#>5$r|}nQWT>gYKrx*(TGH+$qVa zy^a7tJp%P?Fh*KS2&GjhWaqv0 zY5izCx6DPN5=Tvmi231q3=8Hd!EU5}VZ_`~>oM!Fj$=jyy?5A{9%|imTc76Fmm^_? zesU~U#&FLVJ7!#XOv$F|uI52<+-^4478s!dL1}uhO*|{ce4SBVk~xfDnPxo5cpXGo zpsWU%n(DD;I$LXn)_+gU>DHa@_hvze#ajEIiT6+GUo?HntkQ%@52j6_5tGc$6j)#y z;-HD6Xi=b$O_yt-Qtg+Tw^6@|W|z?ImMZ(7ZguY<-4kj5B$xW0G6gzWVBSg{J~l>M zKV)lrg0H4>{CnAh>Q0cc6pL*8WgR>7Ex8AMBwNcZRu_{V*Ukl z;A_8}YTgxmUG@AO&azFWi8GCM-r_|HfBBqv7puxLabYSoVs-PX#44>4+p3$j*=9mJ z#I$kCdH6UE7Kq{EUlIH3qWvR*w`UA5v%CaYxJWyIPanAh{%gP>hCugVqtg>{?%1%p+&r0c)7@s_Fz`*$4Dg6h; zrz9pOCMPFnr)H-nFDUa%;aplf!Mwv#sI8Vl{aAHowra@@s&%7&jJOxtd$Vp;d<>e|YK09Kh43up-*SDjU-8b!lKl{Jg2`MpAc^{%O z^O(_(jj>dsU*yeupkLq3SQ>Gk*?}UD8@B|tE~xD<{pUDy=2df1f4~?a?f_a%P4QJLu<)RfWf_c6Iy=FP{fS~zc!3$)9@2mH z39hrcv;ylnTYjR_uwc(o$j3&gUwON<7ur$HI5WU+0?2&?M{)kgM(41UYrA9XS~vV^ zx>c78scxd(LtTy;ndIl##A8M$x_`1cJkMn`~$F>`K$5z5VUU z0arVuiKEosmV$b8xnO9`oj(c#oQ1mZPX~zUcbL-YR1-vNj5sWAvur zujCL4_Tr>^<`dcBXH#MV;y#|uri;G*L5aeb7$|8glW9?~kYZSzzgECD7%7xlYgg54 zUsUOvElN*fTg-m6{xS4je%i>O$_D(-@CLSmZGN_nZRO&jXd!{l4z%Z!=LIpD%HM)h zmieuD24A~xdCTlU)t{J7@{|S)Ax9T8mY;dSj9@%-wRQZK_r+d4VSQ?!L@QUrfpuKQ z&2bEw-4UYr441sj_vAo+;R#cRyS(1Wta<@8zuc`sd$>98rUA*^W}dg01b;5fOyFa8 zng|i()R}Af+U;f`tM%A-IqcSdp|Bo5rHa?g9{hZRIXKwv!X4Ujp)A_IwW9P-W|?k3 zk8Qx$Gn=3KE2z(Q`x@GN)|@X+u?;2msE|xCl?Y$0_||-ogb8YP>&>`$p1nNC@3Kyy zMThObq4Ia_5mbE8-j4r%r#%Lt@ptVxwD6GK-uC+>|8L8z-zUjdh#$XE>ijQ!HQHzH zQvZnsU%t9vKfP(e6uaFlrOVITbAR%&nLFI}<{+xrW$#GE!$b%F%d7SjW-Tv;p7O z$P$C8dXK#$kEyo@GummrQt+NVm^`oImD^rLYIB3V6HPi|M~HKeeTl!#ByQhpA1x9J zpmwj>6}tUZyGgzG*)U`GJ^>^}RD8^(9PE4gDEdDtKn5H^!otS*R!$jA`9+CaC95ypJN{DPZh7*dr0|O2tBH}m_q9P(9A|Vclii(IxtQsPym@kNkXlA~Q=9+05 zIy#S)xk&D4WQLj?E7wNXth5x#b@`bYnweX!QfqFR$^SV6l5f@icK7$ce4csEIrrRi zp7WgN`JQv0dvWaT<*4dimcaERYZ5Kp-X%I+UnE7R)BO|qP2_7ioo*e{Dx~L-S|QD2 z>79$eCO%~^jrwGN@d?rkYdRF?JZ>Ioz56?EC?QWelZ1|bd$5&9@ZJWbpBft**E?qm zTge({JAHrdN1b!LK2{L0igTkrR9=RyCSqog^8{Cg zmF#hLklM2@rJm$p?m}P!Ilq@Dl3Q!mbay_h1n*ooimcUg?#}yYi?*Q*X((N5#%GW@@tq(!1NNNQ<#4P^FuNH1adoaZ%3H^0q<$ZgOIn!^6!urAWs){y7^e1 zgz2xa>bMe8L-YKT0vscx5Yf;U*o;=*m*Q^mJh)43N|Vv zHVYQytemCw&Cjl#VcTspWpdnk)j7w17_aNDgW)}ehn!KI%$-%8*ZngE6FvtA`f~x! z`gX0HrLD4rm^(|vGuY2d!Oj~+;m$2D2JcQZA0z*g)s&zN|A$r+_wQPbv&P3furn(0 zwwW-c4M1Uk!%QAvCC)CsK?yjw|B*rX-)|63w{J)Sn|Efxcg{Prjelj{!6A@Pr~6{i z?!>^@;QwIu$(h~X_3{-gB{<00sIxGnZb;u^-lcN}#dis~h3fkQW<#(z9OWH}=?LfO z_(5cgb8~!8B^Aq`#x8#rPuD5 zgb&xaR4$Z;R+BRX&KJpvyAx~92T*G!sV5Nqf@t4uzvO8ZG#Fd{H-zgavso|0E`LQx z$t$GaeL}*5NUqcF&|5e4fZ_bw;Yd43>grV5C)@Md;&?Y%AIH@j{DI-Z4O~G;dO(4Ru)%f zfa`}zBn{QLo1MTqA4i3%qownOXNl91W!q|tQW2)ZuwtA%6sEI+n@E-$eZAh}?{>9n0y!_eZWXm%~B)kDP7nD13ma`CF`H!b(0! zL-4^HNc}K94QUk8TbM6Ieg%0?_>SIB(3AyfFn}1(T7iS@3wb^^K;2)`b0SYG$HdK$wHzbYN8kjWu4?; zu+}MN`s7XS_W|x*Jm)s^in~V@nG&doC zl!KY?bFJ2tf8OU)%$`O145=RJ9MXBD&yg-5eSvfl=}V+bNPk263JEv!zjK8XE4AAA zdVTdR;Q|R_%`=)W>2wQ_Sb7n1heU66<~gwq=DpWbsMO`C)Z6(Purq6yEBz7U5Er zPC|(=2u-S`Hk1x0xInR>A)lRTg3rpAqelIKSXuBf*bJgvSQ#GGA{fYwwFiGxn0C=K~>l*kV zYoEi0?zMKNsAg-gB z?pcCX83>*zJ_hQakh{R}Dk0ot8r}Nb(B2InEG``z_i)_cd61t&%n%t*G?@Rgs8G)+ z!A{&C@?3Qn;T2*!aCvpchKI_}z4cC6P1NGTJ#g-A#Oz~Tg=jc9T8g4kHGGZU9V>8< zUnN+mrH+sEgOmfpKKEC==tBD;zidmd65mohVa?DL=^}NrbcMPi_~c>!A)kB75|g!T zzDPF_@@J^6oW+YpSg~AHsV7-OL9g)=EN-WDqunO)9}}3_R%r{X{P|$o`xgI!vHB)o zN{9<)|HQ9^Z(ULtdK%pl=8;vR#>6e1|DVEr4>I}nnXi`{u` zM6Y@S%d3%C`S*8t+4Cr`*Xvsu58>^@%~d{u&8_t#p|G7kG@dng5zD_sx`gyMq_2>^ zM!JmjcO+KlCv3J28o*KHfykLEyiIp(@=^zGk=zl2;vBKc0`)n1fd;J;1{2e=!NqUY zU)>hU=!W?&;U;%ps9}Dv%L<^)v@x9JEBZrwY% z$HJ%PM2vdwMCiUOIFQ6Al9BEVL1IP;Th?-+0$WDzu*gUUryyoe*YH;uacP!OYHL01cR=eobKUX0bQzy-)anhn+L|G)P-2yka_ZsL$y( zg)tA?M_4?Ie;DM`1PE<@k5U!Ffl)#>IEKm=I{l#Fl&jN)_QYiV<3n3VJ!RW;;E&tJ zeW8xO4&8?fCtO4Lo~9R`8CMW_bxd8Ti|j2`91+l=BNs%+4&h%Wu3RBSYJ)S{o6YL& zm;x=$6;=^SYK2DP3KvSnFVLWe_`$qz;Rg9L%lTx9uieKaV&!emS{0ahIvh>-tW6QBWG);kRC%?&bX?Sn{VK=8e_q)`sN2#a{ zfSOb;%6(SQaJPxmUnXoJLKJKKwp8*#)TfR0M}PWS;JN>_s|G=YF3)n7HRbW*U(iD* zG%fe*r*Jcf9YzqYlZ$XtpZ>UO{ajc~xZd() ztj2D=#IlQ=jxw8b%Y7^FW)T*Yi>5Tw6AV8tR%qo$ie1F1IauMsr`SKOSNC>1Jz zn9)|u$Ao&KK8L}q=3?6TH-T`j>p}#LNY$StZc#5(hw2GO$t~gYU)yHDt;48c+erak z3wNO{2S2`A)Ta`G4Th!}^lxzPP&_?Y^xWNkV7k{2KszBMcprn`wEtpvAhuU8R*{_y zP+JA>LDRaeI>z_jk*D?bqLti*;#GK?1?gDTdr_QXIoo=(d;dRByb~3y`kTakQRLiV zdNZ8wM1pDXSK!2!G45SUC#7aUAQL;=NJb+$1DprfG%VM0FO-&h^z#62rNKPc0c3FG_11v9! zg;1X@E7bb2SkJ+QFXd<&T_?^J+*d@7*&Vf=7tiwWtqHf$ws1Xe&hc31+!7eyjvem!w>4~0{9qR6s$Qf%z)FJqR^TJhNT3|gT<*(yc*S9KUQyr zs0zJAU3L1Y2B_U2C$7&^>=3g^PJvBsLj+A;ZP-tsY@~z(cN?OKyI#-1iCZKT=594C z2Tw=I$7xCkh3G0n9;D<*A&}QbGeg-w48gD}1XtYhorYYneX6&>#&h~$8hu`WtR3w7 zA2AzDt+X(>RjUTU6(2>UFYGc*B*5L!2SUTAV&I-Au|`iCXyYD3BY`D#d>80?f;ZDd zQ9S4e<5eYu2A?vZ`&aFkOJMbDau8HWia^WG8j1;cJYoWriAppr`qog^#vLefWE#r4{HNNGn98%EzmGYFM;C+y$`hqi5`sTyn-u6i>!2A zpj3!f=_{duhDfJ7xPwKFl)$;(5(14~y39k(Bqf<9XGm$-YKc-sFJ((rJWYO7GHLE` z(MmHPlXh@$@=eY}O)I42)?oXSJ{j^>ayB|KRotkjnVVUKw6;<@^eg6rU9X~)#g)=| zA9uRQ(abm5-Kzd-2F=(naVq#{i!pTmOmQHAjW6hzVt8r92NK~R`cL|JD1Jd70qMQO z3vlIx{z7$Mv4TL&BVrU}9+h@L!%{H_jM-cWjXNd<5h&?|yXw4|Vldr5Ta<`vvbYN} zk7GsqNI4p=y{81yAtxn?Y~3|kI*wD2+>&ar$EW%2`m}Ml{pO8S(}h9E((3By;3?J(w)U zbeg_G-i1&0m&4%DYhp6g|6K{C%LdA&#Qm%+0@qJUfrhQJmFA9-D@gTt`Nl&~F$Ti~ z^LEKoq13L1K-nodp22|#AxFgs8n{=!MBMvj!F5uugv^WbD5zhJ5t&KLm&-g@f8XauhvXC)c3EEkk?^+=r4t-gZ%gbgLpk@KQCF z4ml`S`NG=KLL@}35CwYXj9ljrF}sx*IJQCZq2&#-NrAQJzt4Y*esGzUgInBc6FbI}B zqb!7)E2;$L6C{CZ&ng3X+*h2|6~KtM#kaxQN(-TN#Y#AFNn#h;WsXvVc<`Je!oi!0 zl?E+T>IuzUuZUdrGR1^2@TiJ5`YpOKJLp5=3S}bA1m)&1di@JU34*yRu#2~-FAi^aNRJZ)6xsW#DvF zo_Pv_-9A~M)@XH!4R^pRVCO-p9b~*I#?p*JwF-UX4KWz5xY4R&iq*p?Ocv|N8l=TR z{6iYPhlUZa>$^eRL)u|XAE@99AZ418Rvo1UaHhvCPhX#tx(3=5uC5%Yy}R5SHlgv8 zPz!KiC}*LjIV$dADrO`2m?x{a45oM2N}#HzW~KG9niY{QQR@I7&rrL8Z6kV3q)Q2= z8FNwRP&*A#U|OlF(2!CU9qi=8e8jymlYc#q0vJc3rl3RrT$!&`K0l}X;QccfTsYKp z2z>DwwE))Ggh(1bPp!p?UZL8J-jf1u-wveiJY5MN9p!sc#|qU!FeJPK?+MNWaf4{; ze08r1rsvUly&#nQthygJan-n9mi5O_lXv@QFH_&*-2*jrw7qCsRflDbCa=RGxjiZ_ z*@w^$LR{+h_Ozs0U5$>*TB|ak@)F-t59(pzN^}MBlDg4-Q02B(TvE|7j-YC)-c{3R z-XZmv0+Ao9wdxQ*d_jT&%eo3M&C#YAr{@>v%$hxZdVa~2q5?=du68ArV4tVWgOPDs zH^|7>s$g6nO@jHOwWYBAOKh`vp=N@buA0(R^HnH3uC9Ztb*i7YCV9u(vSCVT!Sq=< z6CiT5mQJ>K%Y0{-PAQy~Gd;g_vNzS#-)r7`7A8qy!JGoD;AJHRMH353Aa53m@%#eb zhy1d{GA;gG?MN)I;K-V;5YJnfU?WYbV#ApO8M8D6Omnq?aJ8$JWr=OBh_QZ8IHn(C zRqq^m&0kf)$2cf|dO`ofDFsEPkrQUEA0Y(6jJcX0pED>Q?D1L$%W<}O_g>$>Q;+FD zOgA;-SwBi><*f~m_SHfyEGpG>nLGxp*^kxy?V`Fk!mr&E4q3VgI-I3)SLbGHZTwm+Uf0oM!4btkSzj#* z9Qm3Z1~1fld+h>-EYe!Rx*1voq|MNbV0=omSgy8=-yba+pJd~IfT^?&$Sinlz}$gT ziYE4-F}-+3QIqb6d4;;usKVgFY;3n<%UDA_x05NqXevJ1$lJz_BaQMldXEpVakgh~ zN1Lt=p4{d%`)sE<3HcO5gLz-5uXdz4#NRoz8~0M<9b5P zM#SsIC)9Tc=j_0>f*y0Uh^>xV6^{bTFl;-Og~IGWPN|C^`ZK*hl&wR>?Jv`kS-i{+ zjxG38{|WBJ@kTscyvhZ+>Qo10uG8kqv+$+<1YM49a#K9g{y(aRR$NhYABL>{(kRF~ zf-84Ur1l16bi|!f)L1o`W`}8`$gdzXATqb2!=km_#C2M=gMA(LUs5Ng(_^RAXk1<} zk0C~Hy? zPeXL0v(@(GGq{wbC3Cp-8_^v*ESsWg-j-naY}LoFL|1?=TA-VYZ`Gz?jQs8Z_)ym~ z>f^tH+tp_glflCV;`vPVkQ`XNRO?4Q^{m*w9>-Jq^DMfytHJQyd37k%wpAjbzBO+J zYnYM;MGJU;HA=_+*yU!h)ysv;KojOa*b-bRxW$<{OAGd`ASv-t`1fQ~R9ye4=2=Ix zSX4bwi);mvA0a&VX@TcEp{NCfe-Q%T`BnI>y%)DPdl6TgSTKI}V-sc+&6-iz4E?`? z5#+bA$_vx*RK404#{E@|hv9Kr4`4Vd$hwqY>eJ6Rg^AI>Nepz#?$D#JX4UR21?H2j z*vtH;E6~GU@S{Hh;{Hdl1=S0$_tAXe_-w7KMZR547vbrGX&chLt3WXvo2{v^@(Hc| ze`_gty%`&HI*aE>bNb%|c%}f$FkBD4*gva@H7x<~Ujca5fv*?ub6Sw!tK@$j;Ky64 zTJ)@W4r7|(G)wk!kQR^y^u zCeP$y*q_imp4-Jyy<%c}$1LzoFDQL{M#;1RbDCDAcxJ0zwL43jr2I>ZZfu_CF>PeJ z@p{l;SB_?vZksZkOXb_c>SC>pcSX2=-S938hkSW6AG6>^=C+-|UZ#2OSQ;|cYaX%* z$XRtUkzHuHHgBDE<^*^4WindqLdU`@^Gk>O)ZL)QaHBRnDI@Eg4&J$v|k_ z$=h8Y;Tsx^BAOumOAK>)!QS-%tpAj2S`rwN!og))f{>3N6g7h>!`KIcY4v)|LGBcj zqL}sg#ade>qy=M>sb`jk;ip-?awo9AulK>PkQ-2Q1-f`$^+nZ=@e^M;+4% zk52}ld5RfY$D?v(AR*s;WT^7^`bTvJbiI# zaXgPhd~jc7%I&`Txr(rA!!}%49Yr{g2dN=~%~3Ec->AirF-)F*?sb~zipP4H(YVJ( zX)GuShhy=0dt$5a<5~u!+Z7GQI<%J|1~lgIcO6Q5wG0W&GbGgH6nXEX;# z1X$eHxX4whg^`ZncSP$7<)3O+sCru|r5AT;K`Im<6eDTm4XqaUQZM0fU)F412^6mc z_#Pd_So(#UaIYA#)rU0><9y4tL<0u%*#+;tr1illhh`3(HDh+kgaSOK+;KFzM?m*8 zXc>;aM#*`)T@ct0;ISg%h*m^Ty{}zVjn6d0hL^*dceSqc+DR?d-`E;EGht_pw{E&o z`lU8sp$$EZa|q0-)g~I7mp1P=Y(d^SWIpsbq_u&Fk8yVudI-PEV_TrdbXIhX68xKi z6rr=i=6s=*%nCbW7aSxV)}DY}SF~JoX}_!5AAwto5sBc{+C=X#VO^pz;`TQo8ht;} z7)QLi4*M-#dq3)7Uw5>8)D!mGANA;TakoG0`RqUXum`7ZpO0~6vx>d^P8GLlRx#uE zs@UX%-`?5pe+O!MijU$O`pdXI4*>fz+@pGb9M9m!zSt#of$a^v#rOKtZkJTnaUxNK+J4B)t$JQ%?5cKSRPXjWQ~_{am0eJz8P z?K`!stqAHEx4#W#cVX%O9-sH)dltmpN`{C(E1RL`T<|Q=98iWscD;`8l08M3YXLV{ z_>z`<8($q7@dDAIm$ZZ?Olz5G zx{11UWU^FW-Bx z!eTsH6=Eywc`KF|uw$0@u=SWO3puOLYR$lxsI0~e-FV$pESuoXO>FL$LC6CK7(Ooec@2_UqfGsBL^1-j0+5@6CXc;Q&halnQ^y|_~ zk4S*)L60r^*03WFBe`r>@c_`+q&2wM0p0VmCb1@3F4%co?G!L!a(+qveV<#b`HT9H zW=NYdeLRb_42btGcWgC5n-f4hqQ%nqeHca6Gl9mzjx6Kv;P?om4L%xV)aY`-*a@St zNk)v%55pHfUG2tR1a1s6V$^+v5krXsjUqTQj52f^V{FSRropGF#tGoaHb&9T$wqwd zv@0E3tI0I_AhdA;OiMBDhsL4CZgkrKqd$SkLyd`a{UBp1f$d4gVbng@IEshVF~&zA zI18V={1)ye+K(~@z=kyA-=LrU-o7PxG0SLSO?rhg)7OU?@hhMqsZIIrOk5kBOgBcd zhr_`=3bU{4jVWxLL3HnM<1qrA2N)BeLyFNtv2X*&2J!O{`?A}aO;6{rry~a9Pp3zW zHz|%2-^yd(Hpm!e^mh$1wqupOIT4}+BVu_HHr3Fd^TGFUMu|YjY~xsHH^>+dbdO;u Pc=i~Q@A<)hA29ttUM!M>