From d0358f66d2b0f3160a69b86d525eafc113f72429 Mon Sep 17 00:00:00 2001 From: "raidmax@live.com" Date: Wed, 15 Jul 2015 16:11:29 -0500 Subject: [PATCH] Memory reading/writing stable. had to include a custom c++ libary :/ --- Admin/Command.cs | 37 +++------ Admin/File.cs | 4 +- Admin/IW4M ADMIN.csproj | 6 +- Admin/Manager.cs | 8 +- Admin/Server.cs | 45 ++++++----- Admin/Utilities.cs | 140 +++++++++++++++++++++++++++-------- Admin/WebFront.cs | 5 +- Admin/lib/AdminInterface.dll | Bin 0 -> 6656 bytes Admin/version.txt | 1 + 9 files changed, 156 insertions(+), 90 deletions(-) create mode 100644 Admin/lib/AdminInterface.dll diff --git a/Admin/Command.cs b/Admin/Command.cs index e8548348b..23b4a0f9e 100644 --- a/Admin/Command.cs +++ b/Admin/Command.cs @@ -75,10 +75,8 @@ namespace IW4MAdmin else E.Origin.Tell("This server already has an owner!"); } - } - class Warn : Command { public Warn(String N, String D, String U, Player.Permission P, int args, bool nT) : base(N, D, U, P, args, nT) { } @@ -95,10 +93,8 @@ namespace IW4MAdmin E.Owner.Broadcast(Message); if (E.Target.Warnings >= 4) E.Target.Kick("You were kicked for too many warnings!"); - } - + } } - } class WarnClear : Command @@ -112,7 +108,6 @@ namespace IW4MAdmin String Message = String.Format("All warning cleared for {0}", E.Target.getName()); E.Owner.Broadcast(Message); } - } class Kick : Command @@ -122,13 +117,12 @@ namespace IW4MAdmin public override void Execute(Event E) { E.Target.LastOffense = Utilities.removeWords(E.Data, 1); - String Message = "^1Player Kicked: ^5" + E.Target.LastOffense + " ^1Admin: ^5" + E.Origin.getName(); + String Message = "^1Player Kicked: ^5" + E.Target.LastOffense + " ^1Admin: ^5" + E.Origin.getName(); if (E.Origin.getLevel() > E.Target.getLevel()) E.Target.Kick(Message); else E.Origin.Tell("You cannot kick " + E.Target.getName()); } - } class Say : Command @@ -139,7 +133,6 @@ namespace IW4MAdmin { E.Owner.Broadcast("^1" + E.Origin.getName() + " - ^6" + E.Data + "^7"); } - } class TempBan : Command @@ -155,7 +148,6 @@ namespace IW4MAdmin else E.Origin.Tell("You cannot temp ban " + E.Target.getName()); } - } class SBan : Command @@ -170,7 +162,7 @@ namespace IW4MAdmin if (E.Owner.Website == null) Message = "^1Player Banned: ^5" + E.Target.LastOffense; else - Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal at nbsclan.org)"; + Message = "^1Player Banned: ^5" + E.Target.LastOffense + "^7 (appeal " + E.Owner.Website; if (E.Origin.getLevel() > E.Target.getLevel()) { E.Target.Ban(Message, E.Origin); @@ -179,7 +171,6 @@ namespace IW4MAdmin else E.Origin.Tell("You cannot ban " + E.Target.getName()); } - } class Unban : Command @@ -193,7 +184,6 @@ namespace IW4MAdmin else E.Origin.Tell("Unable to find a ban for that GUID"); } - } class WhoAmI : Command @@ -205,7 +195,6 @@ namespace IW4MAdmin String You = String.Format("{0} [^3#{1}^7] {2} [^3@{3}^7] [{4}^7] IP: {5}", E.Origin.getName(), E.Origin.getClientNum(), E.Origin.getID(), E.Origin.getDBID(), Utilities.levelToColor(E.Origin.getLevel()), E.Origin.getIP()); E.Origin.Tell(You); } - } class List : Command @@ -214,15 +203,17 @@ namespace IW4MAdmin public override void Execute(Event E) { - foreach (Player P in E.Owner.getPlayers()) + lock (E.Owner.getPlayers()) { - if (P == null) - continue; + foreach (Player P in E.Owner.getPlayers()) + { + if (P == null) + continue; - E.Origin.Tell(String.Format("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.levelToColor(P.getLevel()), P.getClientNum(), P.getName(), Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.getLevel().ToString().Length))); + E.Origin.Tell(String.Format("[^3{0}^7]{3}[^3{1}^7] {2}", Utilities.levelToColor(P.getLevel()), P.getClientNum(), P.getName(), Utilities.getSpaces(Player.Permission.SeniorAdmin.ToString().Length - P.getLevel().ToString().Length))); + } } } - } class Help : Command @@ -271,7 +262,6 @@ namespace IW4MAdmin E.Origin.Tell("Type !help to get command usage example"); } } - } class FastRestart : Command @@ -295,7 +285,6 @@ namespace IW4MAdmin E.Owner.Broadcast("Performing map rotate in 5 seconds..."); E.Owner.mapRotate(5); } - } class SetLevel : Command @@ -324,7 +313,6 @@ namespace IW4MAdmin else E.Origin.Tell("Invalid group specified."); } - } class Usage : Command @@ -335,7 +323,6 @@ namespace IW4MAdmin { E.Origin.Tell("IW4M Admin is using " + Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1) + "MB"); } - } class Uptime : Command @@ -347,7 +334,6 @@ namespace IW4MAdmin TimeSpan uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime; E.Origin.Tell(String.Format("IW4M Admin has been up for {0} days, {1} hours, and {2} minutes", uptime.Days, uptime.Hours, uptime.Minutes)); } - } class Admins : Command @@ -368,7 +354,6 @@ namespace IW4MAdmin } } } - } class Wisdom : Command @@ -379,7 +364,6 @@ namespace IW4MAdmin { E.Owner.Broadcast(E.Owner.Wisdom()); } - } @@ -405,7 +389,6 @@ namespace IW4MAdmin Utilities.Wait(3); E.Owner.Map(newMap); } - } class Find : Command diff --git a/Admin/File.cs b/Admin/File.cs index 43e04d031..4841cc524 100644 --- a/Admin/File.cs +++ b/Admin/File.cs @@ -26,7 +26,7 @@ namespace IW4MAdmin catch { - //Console.WriteLine("Unable to open log file for writing!"); + Console.WriteLine("Unable to open log file for writing!"); } } @@ -46,7 +46,7 @@ namespace IW4MAdmin { Name = file; writeHandle = new StreamWriter(new FileStream(Name, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)); - // writeHandle.AutoFlush = true; + // writeHandle.AutoFlush = true; sze = 0; } diff --git a/Admin/IW4M ADMIN.csproj b/Admin/IW4M ADMIN.csproj index 5ec7f6229..37ff7161c 100644 --- a/Admin/IW4M ADMIN.csproj +++ b/Admin/IW4M ADMIN.csproj @@ -54,13 +54,14 @@ prompt 0 true + On LocalIntranet - true + false IW4MAdmin.ico @@ -114,6 +115,9 @@ + + PreserveNewest + PreserveNewest diff --git a/Admin/Manager.cs b/Admin/Manager.cs index 663d9f6d7..4d282b8fe 100644 --- a/Admin/Manager.cs +++ b/Admin/Manager.cs @@ -20,7 +20,7 @@ namespace IW4MAdmin public Manager() { ThreadList = new SortedDictionary(); - file logFile = new file("IW4MAdmin_ApplicationLog.log"); + file logFile = new file("IW4MAdminManager.log", true); mainLog = new Log(logFile, Log.Level.All, 0); } @@ -31,7 +31,6 @@ namespace IW4MAdmin if (activePIDs.Count == 0) { mainLog.Write("No viable IW4M instances detected.", Log.Level.All); - mainLog.Write("Shutting Down...", Log.Level.All); Utilities.Wait(5); return; } @@ -199,12 +198,11 @@ namespace IW4MAdmin Utilities.Wait(5); - dvar net_ip = Utilities.getDvar(0x64A1DF8, (int)Handle); - dvar net_port = Utilities.getDvar(0x64A3004, (int)Handle); + dvar net_ip = Utilities.getDvarOld(0x64A1DF8, (int)Handle); + dvar net_port = Utilities.getDvarOld(0x64A3004, (int)Handle); return new Server(net_ip.current, Convert.ToInt32(net_port.current), "", (int)Handle, pID); } - return null; } return null; diff --git a/Admin/Server.cs b/Admin/Server.cs index 9fdad925d..a441e24d4 100644 --- a/Admin/Server.cs +++ b/Admin/Server.cs @@ -217,7 +217,6 @@ namespace IW4MAdmin NewPlayer.lastEvent = new Event(Event.GType.Say, null, NewPlayer, null, this); // this is messy but its throwing an error when they've started it too late else NewPlayer.lastEvent = P.lastEvent; - // lets check aliases if ((NewPlayer.Alias.getNames().Find(m => m.Equals(P.getName()))) == null || NewPlayer.getName() == null || NewPlayer.getName() == String.Empty) @@ -294,7 +293,7 @@ namespace IW4MAdmin } //finally lets check their clean status :> - checkClientStatus(NewPlayer); + //checkClientStatus(NewPlayer); lock (players) { @@ -529,27 +528,17 @@ namespace IW4MAdmin public void executeCommand(String CMD) { - if (CMD.ToLower() == "map_restart" || CMD.ToLower() == "map_rotate") - return; - - else if (CMD.ToLower().Substring(0, 4) == "map ") - { - backupRotation = getDvar("sv_mapRotation").current; - backupTimeLimit = Convert.ToInt32(getDvar("scr_" + Gametype + "_timelimit").current); - Utilities.executeCommand(PID, "sv_maprotation map " + CMD.ToLower().Substring(4, CMD.Length-4)); - Utilities.executeCommand(PID, "scr_" + Gametype + "_timelimit 0.001"); - Utilities.Wait(1); - Utilities.executeCommand(PID, "scr_" + Gametype + "_timelimit " + backupTimeLimit); - } - - else - Utilities.executeCommand(PID, CMD); - + Utilities.executeCommand(PID, CMD); } private dvar getDvar(String DvarName) { - return Utilities.getDvarValue(PID, DvarName); + return Utilities.getDvar(PID, DvarName); + } + + private void setDvar(String Dvar, String Value) + { + //Utilities.setDvar(PID, Dvar, Value); } [DllImport("kernel32.dll")] @@ -814,20 +803,30 @@ namespace IW4MAdmin { try { - // basic info dvars + // inject our dll + if (!Utilities.initalizeInterface(PID)) + { + Log.Write("Could not load IW4MAdmin interface!", IW4MAdmin.Log.Level.Debug); + return false; + } + // basic info dvars hostname = Utilities.stripColors(getDvar("sv_hostname").current); mapname = getDvar("mapname").current; IW_Ver = getDvar("shortversion").current; maxClients = Convert.ToInt32(getDvar("party_maxplayers").current); Gametype = getDvar("g_gametype").current; - // important log variables + // important log variables Basepath = getDvar("fs_basepath").current; Mod = getDvar("fs_game").current; logPath = getDvar("g_log").current; - //int logSync = Convert.ToInt32(getDvar("g_logSync").current); + int oneLog = Convert.ToInt32(getDvar("iw4m_onelog").current); - if (Mod == String.Empty) + // our settings + setDvar("sv_kickBanTime", "3600"); // 1 hour + setDvar("g_logSync", "1"); // yas + + if (Mod == String.Empty || oneLog == 1) logPath = Basepath + '\\' + "m2demo" + '\\' + logPath; else logPath = Basepath + '\\' + Mod + '\\' + logPath; diff --git a/Admin/Utilities.cs b/Admin/Utilities.cs index 3dc1d5f7b..92c1a4fc0 100644 --- a/Admin/Utilities.cs +++ b/Admin/Utilities.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading; using System.Text.RegularExpressions; using System.Runtime.InteropServices; +using System.IO; namespace IW4MAdmin { @@ -384,50 +385,61 @@ namespace IW4MAdmin [DllImport("kernel32.dll", SetLastError = true)] static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds); - public static dvar getDvar(int Location, int Handle) + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GetModuleHandle(string lpModuleName); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern int CloseHandle(IntPtr hObject); + + public static dvar getDvar(int Location, IntPtr Handle) { int numberRead = 0; Byte[] Buff = new Byte[72]; - Byte[] Ptr = new Byte[4]; - ReadProcessMemory(Handle, Location, Ptr, Ptr.Length, ref numberRead); // get location of dvar - - ReadProcessMemory(Handle, (int)BitConverter.ToUInt32(Ptr, 0), Buff, Buff.Length, ref numberRead); // read dvar memory + ReadProcessMemory((int)Handle, Location, Buff, Buff.Length, ref numberRead); // read dvar memory dvar_t dvar_raw = Helpers.ReadStruct(Buff); // get the dvar struct - dvar dvar_actual = new dvar(); // gotta convert to something readable - dvar_actual.name = getStringFromPointer((int)dvar_raw.name, Handle); - dvar_actual.description = getStringFromPointer((int)dvar_raw.description, Handle); + dvar_actual.name = getStringFromPointer((int)dvar_raw.name, (int)Handle); + dvar_actual.description = getStringFromPointer((int)dvar_raw.description, (int)Handle); if ((int)dvar_raw._default > short.MaxValue) - dvar_actual._default = getStringFromPointer((int)dvar_raw._default, Handle); + dvar_actual._default = getStringFromPointer((int)dvar_raw._default, (int)Handle); else dvar_actual._default = dvar_raw._default.ToString(); if ((int)dvar_raw.current > short.MaxValue) - dvar_actual.current = getStringFromPointer((int)dvar_raw.current, Handle); + dvar_actual.current = getStringFromPointer((int)dvar_raw.current, (int)Handle); else if ((int)dvar_raw.current <= 1025) dvar_actual.current = ((int)dvar_raw.current % 1024).ToString(); else dvar_actual.current = dvar_raw.current.ToString(); if ((int)dvar_raw.latched > short.MaxValue) - dvar_actual.latched = getStringFromPointer((int)dvar_raw.latched, Handle); + dvar_actual.latched = getStringFromPointer((int)dvar_raw.latched, (int)Handle); else dvar_actual.latched = dvar_raw.latched.ToString(); dvar_actual.type = dvar_raw.type; - dvar_actual.flags = getIntFromPointer((int)dvar_raw.flags, Handle); - dvar_actual.max = getIntFromPointer((int)dvar_raw.max, Handle); - dvar_actual.min = getIntFromPointer((int)dvar_raw.min, Handle); + dvar_actual.flags = getIntFromPointer((int)dvar_raw.flags, (int)Handle); + dvar_actual.max = getIntFromPointer((int)dvar_raw.max, (int)Handle); + dvar_actual.min = getIntFromPointer((int)dvar_raw.min, (int)Handle); // done! return dvar_actual; } + public static dvar getDvarOld(int Location, int Handle) + { + int loc = getIntFromPointer(Location, Handle); + return getDvar(loc, (IntPtr)Handle); + } + public static int getDvarCurrentAddress(int Location, int Handle) { int numberRead = 0; @@ -435,11 +447,9 @@ namespace IW4MAdmin Byte[] Ptr = new Byte[4]; ReadProcessMemory(Handle, Location, Ptr, Ptr.Length, ref numberRead); // get location of dvar - ReadProcessMemory(Handle, (int)BitConverter.ToUInt32(Ptr, 0), Buff, Buff.Length, ref numberRead); // read dvar memory dvar_t dvar_raw = Helpers.ReadStruct(Buff); // get the dvar struct - int current = (int)dvar_raw.current; return current; @@ -447,8 +457,8 @@ namespace IW4MAdmin public static void setDvar(int Location, int Handle, String Value) { - UIntPtr bytesWritten = UIntPtr.Zero; - WriteProcessMemory((IntPtr)Handle, (IntPtr)Location, Encoding.ASCII.GetBytes(Value), (uint)Value.Length, out bytesWritten); + //UIntPtr bytesWritten = UIntPtr.Zero; + //WriteProcessMemory((IntPtr)Handle, (IntPtr)Location, Encoding.ASCII.GetBytes(Value), (uint)Value.Length, out bytesWritten); } public static String getStringFromPointer(int Location, int Handle) @@ -491,7 +501,7 @@ namespace IW4MAdmin public static void executeCommand(int pID, String Command) { - IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); + /*IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); IntPtr memoryForCMDName = allocateAndWrite(Encoding.ASCII.GetBytes(Command + "\0"), ProcessHandle); uint threadID; @@ -521,13 +531,13 @@ namespace IW4MAdmin if (ThreadHandle == null || ThreadHandle == IntPtr.Zero) return; - WaitForSingleObject(ThreadHandle, 500000); + WaitForSingleObject(ThreadHandle, Int32.MaxValue); // gg if it doesn't finishe // cleanup if (!VirtualFreeEx(ProcessHandle, codeAllocation, 0, AllocationType.Release)) Console.WriteLine(Marshal.GetLastWin32Error()); if (!VirtualFreeEx(ProcessHandle, memoryForCMDName, 0, AllocationType.Release)) - Console.WriteLine(Marshal.GetLastWin32Error()); + Console.WriteLine(Marshal.GetLastWin32Error());*/ } @@ -559,19 +569,77 @@ namespace IW4MAdmin return true; } - public static dvar getDvarValue(int pID, String DVAR) + public static bool initalizeInterface(int pID) { - dvar requestedDvar = new dvar(); - uint threadID = 0; - IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); + String Path = AppDomain.CurrentDomain.BaseDirectory + "lib\\AdminInterface.dll"; + + if (!File.Exists(Path)) + return false; + + UIntPtr bytesWritten; + uint threadID; + + IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); + if (ProcessHandle == IntPtr.Zero) + return false; + + IntPtr lpLLAddress = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"); + + if (lpLLAddress == IntPtr.Zero) + return false; + + IntPtr pathAllocation = VirtualAllocEx(ProcessHandle, IntPtr.Zero, (uint)Path.Length + 1, AllocationType.Commit, MemoryProtection.ExecuteReadWrite); + + if (pathAllocation == IntPtr.Zero) + return false; + + byte[] pathBytes = Encoding.ASCII.GetBytes(Path); + + if (!WriteProcessMemory(ProcessHandle, pathAllocation, pathBytes, (uint)pathBytes.Length, out bytesWritten)) + return false; + + if (CreateRemoteThread(ProcessHandle, IntPtr.Zero, 0, lpLLAddress, pathAllocation, 0, out threadID) == IntPtr.Zero) + return false; + + CloseHandle(ProcessHandle); + + return true; + } + + public static void setDvar(int pID, String Name, String Value) + { + IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); + IntPtr memoryForDvarName = allocateAndWrite(Encoding.ASCII.GetBytes(Name + " " + Value + "\0"), ProcessHandle); - IntPtr memoryForDvarName = allocateAndWrite(Encoding.ASCII.GetBytes(DVAR + "\0"), ProcessHandle); if (memoryForDvarName == IntPtr.Zero) + { Console.WriteLine("UNABLE TO ALLOCATE MEMORY FOR DVAR NAME"); + return; + } setDvarCurrentPtr((IntPtr)0x2098D9C, memoryForDvarName, ProcessHandle); - byte[] copyDvarValue = { + if (!VirtualFreeEx(ProcessHandle, memoryForDvarName, 0, AllocationType.Release)) + Console.WriteLine("Virtual Free Failed -- Error #" + Marshal.GetLastWin32Error()); + + CloseHandle(ProcessHandle); + } + + public static dvar getDvar(int pID, String DVAR) + { + dvar requestedDvar = new dvar(); + IntPtr ProcessHandle = OpenProcess(ProcessAccessFlags.All, false, pID); + IntPtr memoryForDvarName = allocateAndWrite(Encoding.ASCII.GetBytes(DVAR + "\0"), ProcessHandle); + + if (memoryForDvarName == IntPtr.Zero) + { + Console.WriteLine("UNABLE TO ALLOCATE MEMORY FOR DVAR NAME"); + return requestedDvar; + } + + setDvarCurrentPtr((IntPtr)0x2089E04, memoryForDvarName, ProcessHandle); // sv_debugRate +#if ASD + /* byte[] copyDvarValue = { 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x08, // ----------------------------------------------- 0xC7, 0x45, 0xFC, 0x9C, 0x8D, 0x09, // dvar_t** surogateDvar = (dvar_t**)(0x2098D9C); 0x02, 0x8B, 0x45, 0xFC, 0x8B, 0x08, // @@ -593,15 +661,25 @@ namespace IW4MAdmin if (ThreadHandle == null || ThreadHandle == IntPtr.Zero) return requestedDvar; - WaitForSingleObject(ThreadHandle, 500000); + WaitForSingleObject(ThreadHandle, Int32.MaxValue); // gg if thread doesn't finish if (!VirtualFreeEx(ProcessHandle, codeAllocation, 0, AllocationType.Release)) Console.WriteLine(Marshal.GetLastWin32Error()); if (!VirtualFreeEx(ProcessHandle, memoryForDvarName, 0, AllocationType.Release)) - Console.WriteLine(Marshal.GetLastWin32Error()); + Console.WriteLine(Marshal.GetLastWin32Error());*/ +#endif + Utilities.Wait(.3); + int dvarLoc = getIntFromPointer(0x2089E04, (int)ProcessHandle); // this is where the dvar is stored - int dvarLoc = getIntFromPointer(0x2098D9C, (int)ProcessHandle); - return getDvar(dvarLoc + 0x10, (int)ProcessHandle); + if (dvarLoc == 0) + return requestedDvar; + + dvarLoc = getIntFromPointer(dvarLoc + 0x10, (int)ProcessHandle); + + requestedDvar = getDvar(dvarLoc, ProcessHandle); + CloseHandle(ProcessHandle); + + return requestedDvar; } public static String timesConnected(int connection) diff --git a/Admin/WebFront.cs b/Admin/WebFront.cs index 38c6de7cd..2264d0151 100644 --- a/Admin/WebFront.cs +++ b/Admin/WebFront.cs @@ -95,6 +95,9 @@ namespace IW4MAdmin_Web int cycleFix = 0; for (int i = 0; i < Servers.Count(); i++) { + if (IW4MAdmin.Program.getServers()[i] == null) + continue; + StringBuilder players = new StringBuilder(); if (Servers[i].getClientNum() < 1) players.Append("

No Players

"); @@ -577,7 +580,7 @@ namespace IW4MAdmin_Web body = Macro.findMacros(header + main + footer, toSend, server); } - IW4MAdmin.Program.getServers()[server].Log.Write("Webfront processed request for " + request.Uri, IW4MAdmin.Log.Level.Debug); + //IW4MAdmin.Program.getServers()[server].Log.Write("Webfront processed request for " + request.Uri, IW4MAdmin.Log.Level.Debug); } var headers = new HttpResponseHead() diff --git a/Admin/lib/AdminInterface.dll b/Admin/lib/AdminInterface.dll new file mode 100644 index 0000000000000000000000000000000000000000..b16bcd923888ccd81b52cf837467183778e10c2d GIT binary patch literal 6656 zcmeHLeQ;FO6~CM8;)ZO8xj&$1Ix1e!tSyk>HA0% z$8^vot;-X&7CRjW2Vz?7)Q=Y18Bs@B3?)bbQ>sV@)M(SrV)92EDM_7_*WZ1+3jty~ z)9EvPEO@{TRo zr>H-+pg9oabRBGLGwY4$eBNj@#_KjQI#!74f>B-Fy-m7E%+FL56wEHms2-_uAKi1m zb$1qC%KL2hvzWh>m)hMY_aE$jLGDk=^q!wj?e3T5D|f$!{=?D}8M!gJ|FHC|+^-4x z0#s-A1TH5b^$Hc)HNJaomNras7UU~5gv_P~NmI4gq1OU*@?0yAa|oFwPcQXkBM8)K zXgS28kQdQ)bb;Jo>?n~uT1rR_9XJRXhV)gXUgu)Gm6~jNKT?O-IPa3fAxCy@cGR0tp29z!{%lCN~GRu1JfKHkveg0DMD= z0MnX-kYokRu|Ck`vw|SYU5|cRb5MOO6OKWUo}Iu4vRuhDAq*Xa+;utS+u8rh15GWh z8}~iY)Ib97>4;VuiXT+&cRaO`h#lJA^L*955m3eB+pn$C3O8;$!GC9aqoT^C5az@W zfA42s~8Sdn?kjuLOJbCJP@c#$@FiE2&mWGD-eEnz4zV zDbb}IRaU>v&oL#%Bjbg8^LvxR_~9q^H>Vci4!X}i46n;c~SoXzfll|DyPLqCtaw#Zw8Vdi0t21&LR~b?c}TGn>fX zOP`n;n~)D;@0lv>O~O!9@<+L6UzG=Ee~S)YQxx_-hf&%wCP|!-7tidTT+{g#c3ER6 z$HV8rIr)g?s3ou!76f>pK^1izbz$m88Xq^@`jbEhWru-M5{i~PZIl}^R#JoFwd=5k z@bd$-PZdEWmhlCWply|tjglpm9KfUlc`5#tW)#9s%@3vH}|Py^zaLQ$7Ebq>GH7O=J9FD#M6N3ZVk0y$ws6sEK>Q$&T;9^6XA|8KN}Q zD^VkIg?qBQ%!Sy*sd>p;=oxr(cE#-(z69Gw=ZOttqCzAI=WwGd6+#r;?g5cWnNEve zU=_}d^uBP#|?CaJgx2M;OwYiKw=k)5kfeT+U{OG(fw zs6&d>ASmuYo{}U}hv{%VUHs5k8R8e3oBA_IaGolZ<34t4zs>+(7tE))xP;=HiYT^d zDX!H3w?A5Jle>b&a(9zj?o>*-TS(+?$rS9|^|(rpndl__Wpc0VUxa>#Q{8W-^WHOX z@1Uv!q6(rQeU#b17_&i@4wxD|s4A1Ws3t0XW)xDc+ma-MTg{l$r!qYk<+_!qS50Dm zV3>#nO7Q7CaFizWQTP-2I3O+Dh^e%&4LDsj;SABKHl09K5?9vb9xi-!SyADu?pg6< zN$!E()BHRnq1d6RzQE>*E7iHjtN+2yzAK(wRQ)G5WlAE=*I!kCLl*)rW-oGPxo)jQbM>kS6Z*)f*6}xsZx$sLoL)q)w?3c6Xea z$`<;Qkjs>I42O3(hvD537jCdY4 zFx3X}0kP){cH4uYTfT3o2=xfT#3}c@EH{L?s zk*Wvzg5E=X-o#KpO~%po9nNzusaOY-MC5?;9IBdBti;qn2kP`-hjR=<8Yzbjnx+P%;{s_i{4Xv6~r*WOLYfnja_VZ~l+_dL=bjCeucLg5X5t@}7P?@Aw6GpH;GiYuS(;n}>8zG4G}Xkq zGHoJUrriO!efUy>Pa(mP*OD+PI2z&OySNC2G9B^2TUu;P50+Yr zt>Onu=F;6s6PdP^GX5_SY||ov6YU7Giwh$;Vg~tov1Fk!~aF;|c)I{cfmmeWr=3e=YA4UfQ#jXv;{ zF_h`E^L0h(7~X>33dqt=O_NI?&bu zPyvsh*(7Y<%&?w7EVhM^7qJV-Yoh^g)E{R2&MqI*!3See@fyUP7}(q% z^F+N7hQkoXN4@aC135SS7rVcYs7d%bX#`u3)^pz~W44-V$Y_z_8seT!9!H@kV{j@|ZyFAhUDsX87d-%R)tF z^(sG^GmV-KB#7iBr8aeQJQHaSMwn$B;>OU?j9K^1C(`GC?+yFwvM0f{l<#?V{%!yN zFTg~+ z+>%Jp$Hur=JHG_Qq{_=hDmo42I$Zi-JHzoU(|3a^ovwyw1&&{Z0+rEL`X98JO67G} z$uT~G4f5TYIVddiJpsEgeis|;M0MEAa2J`=+0HI3rDeFD>14vXFvYdyUT#&iGq#0c z%XLCyEAOpbTlqz$(pqROvfg0TTko_wt?R52YnSyg>o2XZSiiIu+ZNdL zHoI+wO|bRYw%Z=J{mS-h+Y#H4?S0!PwhOjZ_SN