From a551a6e6c8a69b2fd2387ca279b72535013e5eff Mon Sep 17 00:00:00 2001 From: Jason Hood Date: Mon, 8 Nov 2010 15:31:01 +1000 Subject: [PATCH] Initial commit (v1.30) --- ANSI-LLA.c | 17 + ANSI.c | 1306 +++++++++++++++++++++++++++++++++ COPYING.MinGW-w64-runtime.txt | 169 +++++ ansi.rc | 41 ++ ansicon.c | 541 ++++++++++++++ ansicon.rc | 35 + injdll.h | 16 + injdll32.c | 112 +++ injdll64.c | 98 +++ makefile | 53 ++ readme.txt | 236 ++++++ wow64.h | 88 +++ x64/ANSI-LLA.exe | Bin 0 -> 10240 bytes x64/ANSI32.dll | Bin 0 -> 21504 bytes x64/ANSI64.dll | Bin 0 -> 25088 bytes x64/ansicon.exe | Bin 0 -> 23552 bytes x86/ANSI32.dll | Bin 0 -> 21504 bytes x86/ansicon.exe | Bin 0 -> 20480 bytes 18 files changed, 2712 insertions(+) create mode 100644 ANSI-LLA.c create mode 100644 ANSI.c create mode 100644 COPYING.MinGW-w64-runtime.txt create mode 100644 ansi.rc create mode 100644 ansicon.c create mode 100644 ansicon.rc create mode 100644 injdll.h create mode 100644 injdll32.c create mode 100644 injdll64.c create mode 100644 makefile create mode 100644 readme.txt create mode 100644 wow64.h create mode 100644 x64/ANSI-LLA.exe create mode 100644 x64/ANSI32.dll create mode 100644 x64/ANSI64.dll create mode 100644 x64/ansicon.exe create mode 100644 x86/ANSI32.dll create mode 100644 x86/ansicon.exe diff --git a/ANSI-LLA.c b/ANSI-LLA.c new file mode 100644 index 0000000..8f900b9 --- /dev/null +++ b/ANSI-LLA.c @@ -0,0 +1,17 @@ +/* + ANSI-LLA.c - Output the 32-bit address of LoadLibraryA. + + Jason Hood, 5 September, 2010. + + I don't know of a method to retrieve the 32-bit address of a function in + 64-bit code, so this is a simple workaround. +*/ + +#define WIN32_LEAN_AND_MEAN +#include + +int main( void ) +{ + return (DWORD)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), + "LoadLibraryA" ); +} diff --git a/ANSI.c b/ANSI.c new file mode 100644 index 0000000..ee7265f --- /dev/null +++ b/ANSI.c @@ -0,0 +1,1306 @@ +/* + ANSI.c - ANSI escape sequence console driver. + + Jason Hood, 21 & 22 October, 2005. + + Derived from ANSI.xs by Jean-Louis Morel, from his Perl package + Win32::Console::ANSI. I removed the codepage conversion ("\e(") and added + WriteConsole hooking. + + v1.01, 11 & 12 March, 2006: + disable when console has disabled processed output; + \e[5m (blink) is the same as \e[4m (underline); + do not conceal control characters (0 to 31); + \e[m will restore original color. + + v1.10, 22 February, 2009: + fix MyWriteConsoleW for strings longer than the buffer; + initialise attributes to current; + hook into child processes. + + v1.11, 28 February, 2009: + fix hooking into child processes (only do console executables). + + v1.12, 9 March, 2009: + really fix hooking (I didn't realise MinGW didn't generate relocations). + + v1.13, 21 & 27 March, 2009: + alternate injection method, to work with DEP; + use Unicode and the current output code page (not OEMCP). + + v1.14, 3 April, 2009: + fix test for empty import section. + + v1.15, 17 May, 2009: + properly update lpNumberOfCharsWritten in MyWriteConsoleA. + + v1.20, 26 & 29 May, 17 to 21 June, 2009: + create an ANSICON environment variable; + hook GetEnvironmentVariable to create ANSICON dynamically; + use another injection method. + + v1.22, 5 October, 2009: + hook LoadLibrary to intercept the newly loaded functions. + + v1.23, 11 November, 2009: + unload gracefully; + conceal characters by making foreground same as background; + reverse the bold/underline attributes, too. + + v1.25, 15, 20 & 21 July, 2010: + hook LoadLibraryEx (now cscript works); + Win7 support. + + v1.30, 3 August to 7 September, 2010: + x64 support. +*/ + +#define UNICODE +#define _UNICODE +#include + +#define lenof(str) (sizeof(str)/sizeof(TCHAR)) + +#include +#include +#include +#include +#include "injdll.h" + +#define isdigit(c) ('0' <= (c) && (c) <= '9') + +// ========== Auxiliary debug function + +#define MYDEBUG 0 // no debugging +//#define MYDEBUG 1 // use OutputDebugString +//#define MYDEBUG 2 // use %temp%\ansicon.log + +#if (MYDEBUG > 0) +#if (MYDEBUG > 1) +char tempfile[MAX_PATH]; +#endif +void DEBUGSTR( LPTSTR szFormat, ... ) // sort of OutputDebugStringf +{ + TCHAR szBuffer[1024], szEscape[1024]; + va_list pArgList; + va_start( pArgList, szFormat ); + _vsntprintf( szBuffer, lenof(szBuffer), szFormat, pArgList ); + va_end( pArgList ); + + szFormat = szBuffer; + if (*szFormat == '\\') + { + BOOL first = TRUE; + LPTSTR pos = szEscape; + while (*++szFormat != '\0' && pos < szEscape + lenof(szEscape) - 4) + { + if (*szFormat < 32) + { + *pos++ = '\\'; + switch (*szFormat) + { + case '\b': *pos++ = 'b'; break; + case '\t': *pos++ = 't'; break; + case '\r': *pos++ = 'r'; break; + case '\n': *pos++ = 'n'; break; + case '\e': *pos++ = 'e'; break; + default: pos += _tprintf( pos, TEXT("%.*o"), + (szFormat[1] >= '0' && szFormat[1] <= '7') ? 3 : 1, + *szFormat ); + } + } + else if (*szFormat == '"') + { + if (first) + first = FALSE; + else if (szFormat[1] == '\0') + ; + else + *pos++ = '\\'; + *pos++ = '"'; + } + else + { + *pos++ = *szFormat; + } + } + *pos = '\0'; + szFormat = szEscape; + } +#if (MYDEBUG > 1) + FILE* file = fopen( tempfile, "a" ); + if (file != NULL) + { + _ftprintf( file, TEXT("%s\n"), szFormat ); + fclose( file ); + } +#else + OutputDebugString( szFormat ); +#endif +} +#else +#define DEBUGSTR(...) +#endif + +// ========== Global variables and constants + +// Macro for adding pointers/DWORDs together without C arithmetic interfering +#define MakePtr( cast, ptr, addValue ) (cast)((DWORD_PTR)(ptr)+(DWORD)(addValue)) + + +const char APIKernel[] = "kernel32.dll"; +const char APIKernelBase[] = "kernelbase.dll"; +const char APIConsole[] = "API-MS-Win-Core-Console-L1-1-0.dll"; +const char APIProcessThreads[] = "API-MS-Win-Core-ProcessThreads-L1-1-0.dll"; +const char APIProcessEnvironment[] = "API-MS-Win-Core-ProcessEnvironment-L1-1-0.dll"; +const char APILibraryLoader[] = "API-MS-Win-Core-LibraryLoader-L1-1-0.dll"; +const char APIFile[] = "API-MS-Win-Core-File-L1-1-0.dll"; + +PCSTR APIs[] = +{ + APIKernel, + APIConsole, + APIProcessThreads, + APIProcessEnvironment, + APILibraryLoader, + APIFile, + NULL +}; + + +HMODULE hKernel; // Kernel32 module handle +HINSTANCE hDllInstance; // Dll instance handle +HANDLE hConOut; // handle to CONOUT$ + +#define ESC '\x1B' // ESCape character + +#define MAX_ARG 16 // max number of args in an escape sequence +int state; // automata state +//TCHAR prefix; // escape sequence prefix ( '[' or '(' ); +TCHAR suffix; // escape sequence suffix +int es_argc; // escape sequence args count +int es_argv[MAX_ARG]; // escape sequence args + +// color constants + +#define FOREGROUND_BLACK 0 +#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE + +#define BACKGROUND_BLACK 0 +#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE + +WORD foregroundcolor[8] = +{ + FOREGROUND_BLACK, // black foreground + FOREGROUND_RED, // red foreground + FOREGROUND_GREEN, // green foreground + FOREGROUND_RED | FOREGROUND_GREEN, // yellow foreground + FOREGROUND_BLUE, // blue foreground + FOREGROUND_BLUE | FOREGROUND_RED, // magenta foreground + FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan foreground + FOREGROUND_WHITE // white foreground +}; + +WORD backgroundcolor[8] = +{ + BACKGROUND_BLACK, // black background + BACKGROUND_RED, // red background + BACKGROUND_GREEN, // green background + BACKGROUND_RED | BACKGROUND_GREEN, // yellow background + BACKGROUND_BLUE, // blue background + BACKGROUND_BLUE | BACKGROUND_RED, // magenta background + BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan background + BACKGROUND_WHITE, // white background +}; + + +// screen attributes +WORD org_fg, org_bg, org_bold, org_ul; // original attributes +WORD foreground; +WORD background; +WORD bold; +WORD underline; +WORD rvideo = 0; +WORD concealed = 0; + +// saved cursor position +COORD SavePos = { 0, 0 }; + + +// ========== Hooking API functions +// +// References about API hooking (and dll injection): +// - Matt Pietrek ~ Windows 95 System Programming Secrets. +// - Jeffrey Richter ~ Programming Applications for Microsoft Windows 4th ed. + +typedef struct +{ + PCSTR lib; + PSTR name; + PROC newfunc; + PROC oldfunc; + PROC apifunc; +} HookFn, *PHookFn; + +HookFn Hooks[]; + +//----------------------------------------------------------------------------- +// HookAPIOneMod +// Substitute a new function in the Import Address Table (IAT) of the +// specified module. +// Return FALSE on error and TRUE on success. +//----------------------------------------------------------------------------- + +BOOL HookAPIOneMod( + HMODULE hFromModule, // Handle of the module to intercept calls from + PHookFn Hooks, // Functions to replace + BOOL restore // Restore the original functions + ) +{ + PIMAGE_DOS_HEADER pDosHeader; + PIMAGE_NT_HEADERS pNTHeader; + PIMAGE_IMPORT_DESCRIPTOR pImportDesc; + PIMAGE_THUNK_DATA pThunk; + PHookFn hook; + + // Tests to make sure we're looking at a module image (the 'MZ' header) + pDosHeader = (PIMAGE_DOS_HEADER)hFromModule; + if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) + { + DEBUGSTR( TEXT("error: %s(%d)"), TEXT(__FILE__), __LINE__ ); + return FALSE; + } + + // The MZ header has a pointer to the PE header + pNTHeader = MakePtr( PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew ); + + // One more test to make sure we're looking at a "PE" image + if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) + { + DEBUGSTR( TEXT("error: %s(%d)"), TEXT(__FILE__), __LINE__ ); + return FALSE; + } + + // We now have a valid pointer to the module's PE header. + // Get a pointer to its imports section. + pImportDesc = MakePtr( PIMAGE_IMPORT_DESCRIPTOR, + pDosHeader, + pNTHeader->OptionalHeader. + DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. + VirtualAddress ); + + // Bail out if the RVA of the imports section is 0 (it doesn't exist) + if (pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pDosHeader) + { + return TRUE; + } + + // Iterate through the array of imported module descriptors, looking + // for the module whose name matches the pszFunctionModule parameter. + for (; pImportDesc->Name; pImportDesc++) + { + PCSTR* lib; + PSTR pszModName = MakePtr( PSTR, pDosHeader, pImportDesc->Name ); + for (lib = APIs; *lib; ++lib) + if (stricmp( pszModName, *lib ) == 0) + break; + if (*lib == NULL) + continue; + + // Get a pointer to the found module's import address table (IAT). + pThunk = MakePtr( PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk ); + + // Blast through the table of import addresses, looking for the ones + // that match the original addresses. + while (pThunk->u1.Function) + { + for (hook = Hooks; hook->name; ++hook) + { + PROC patch = 0; + if (restore) + { + if ((PROC)pThunk->u1.Function == hook->newfunc) + patch = (lib == APIs) ? hook->oldfunc : hook->apifunc; + } + else if ((PROC)pThunk->u1.Function == hook->oldfunc || + (PROC)pThunk->u1.Function == hook->apifunc) + { + patch = hook->newfunc; + } + if (patch) + { + DWORD flOldProtect, flNewProtect, flDummy; + MEMORY_BASIC_INFORMATION mbi; + + DEBUGSTR( TEXT(" %hs"), hook->name ); + // Get the current protection attributes. + VirtualQuery( &pThunk->u1.Function, &mbi, sizeof(mbi) ); + // Take the access protection flags. + flNewProtect = mbi.Protect; + // Remove ReadOnly and ExecuteRead flags. + flNewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); + // Add on ReadWrite flag + flNewProtect |= (PAGE_READWRITE); + // Change the access protection on the region of committed pages in the + // virtual address space of the current process. + VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), + flNewProtect, &flOldProtect ); + + // Overwrite the original address with the address of the new function. + if (!WriteProcessMemory( GetCurrentProcess(), + &pThunk->u1.Function, + &patch, sizeof(patch), NULL )) + { + DEBUGSTR( TEXT("error: %s(%d)"), TEXT(__FILE__), __LINE__ ); + return FALSE; + } + + // Put the page attributes back the way they were. + VirtualProtect( &pThunk->u1.Function, sizeof(PVOID), + flOldProtect, &flDummy ); + } + } + pThunk++; // Advance to next imported function address + } + } + + return TRUE; // Function not found +} + +//----------------------------------------------------------------------------- +// HookAPIAllMod +// Substitute a new function in the Import Address Table (IAT) of all +// the modules in the current process. +// Return FALSE on error and TRUE on success. +//----------------------------------------------------------------------------- + +BOOL HookAPIAllMod( PHookFn Hooks, BOOL restore ) +{ + HANDLE hModuleSnap; + MODULEENTRY32 me; + BOOL fOk; + + // Take a snapshot of all modules in the current process. + hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, + GetCurrentProcessId() ); + + if (hModuleSnap == INVALID_HANDLE_VALUE) + { + DEBUGSTR( TEXT("error: %s(%d)"), TEXT(__FILE__), __LINE__ ); + return FALSE; + } + + // Fill the size of the structure before using it. + me.dwSize = sizeof(MODULEENTRY32); + + // Walk the module list of the modules. + for (fOk = Module32First( hModuleSnap, &me ); fOk; + fOk = Module32Next( hModuleSnap, &me )) + { + // We don't hook functions in our own module. + if (me.hModule != hDllInstance && me.hModule != hKernel) + { + DEBUGSTR( (restore) ? TEXT("Unhooking from %s") : TEXT("Hooking in %s"), + me.szModule ); + // Hook this function in this module. + if (!HookAPIOneMod( me.hModule, Hooks, restore )) + { + CloseHandle( hModuleSnap ); + return FALSE; + } + } + } + CloseHandle( hModuleSnap ); + return TRUE; +} + +// ========== Print Buffer functions + +#define BUFFER_SIZE 256 + +int nCharInBuffer = 0; +TCHAR ChBuffer[BUFFER_SIZE]; + +//----------------------------------------------------------------------------- +// FlushBuffer() +// Writes the buffer to the console and empties it. +//----------------------------------------------------------------------------- + +void FlushBuffer( void ) +{ + DWORD nWritten; + if (nCharInBuffer <= 0) return; + WriteConsole( hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL ); + nCharInBuffer = 0; +} + +//----------------------------------------------------------------------------- +// PushBuffer( char c ) +// Adds a character in the buffer and flushes the buffer if it is full. +//----------------------------------------------------------------------------- + +void PushBuffer( TCHAR c ) +{ + ChBuffer[nCharInBuffer++] = c; + if (nCharInBuffer >= BUFFER_SIZE) + { + FlushBuffer(); + DEBUGSTR( TEXT("flush") ); + } +} + +// ========== Print functions + +//----------------------------------------------------------------------------- +// InterpretEscSeq() +// Interprets the last escape sequence scanned by ParseAndPrintString +// prefix escape sequence prefix +// es_argc escape sequence args count +// es_argv[] escape sequence args array +// suffix escape sequence suffix +// +// for instance, with \e[33;45;1m we have +// prefix = '[', +// es_argc = 3, es_argv[0] = 33, es_argv[1] = 45, es_argv[2] = 1 +// suffix = 'm' +//----------------------------------------------------------------------------- + +void InterpretEscSeq( void ) +{ + int i; + WORD attribut; + CONSOLE_SCREEN_BUFFER_INFO Info; + DWORD len, NumberOfCharsWritten; + COORD Pos; + SMALL_RECT Rect; + CHAR_INFO CharInfo; + + //if (prefix == '[') + { + GetConsoleScreenBufferInfo( hConOut, &Info ); + switch (suffix) + { + case 'm': + if (es_argc == 0) es_argv[es_argc++] = 0; + for (i = 0; i < es_argc; i++) + { + switch (es_argv[i]) + { + case 0: + foreground = org_fg; + background = org_bg; + bold = (es_argc == 1) ? org_bold : 0; + underline = (es_argc == 1) ? org_ul : 0; + rvideo = 0; + concealed = 0; + break; + case 1: bold = FOREGROUND_INTENSITY; break; + case 5: /* blink */ + case 4: underline = BACKGROUND_INTENSITY; break; + case 7: rvideo = 1; break; + case 8: concealed = 1; break; + case 21: bold = 0; break; + case 25: + case 24: underline = 0; break; + case 27: rvideo = 0; break; + case 28: concealed = 0; break; + } + if (30 <= es_argv[i] && es_argv[i] <= 37) foreground = es_argv[i]-30; + if (40 <= es_argv[i] && es_argv[i] <= 47) background = es_argv[i]-40; + } + if (concealed) + { + if (rvideo) + { + attribut = foregroundcolor[foreground] + | backgroundcolor[foreground]; + if (bold) + attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + else + { + attribut = foregroundcolor[background] + | backgroundcolor[background]; + if (underline) + attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + } + else if (rvideo) + { + attribut = foregroundcolor[background] | backgroundcolor[foreground]; + if (bold) + attribut |= BACKGROUND_INTENSITY; + if (underline) + attribut |= FOREGROUND_INTENSITY; + } + else + attribut = foregroundcolor[foreground] | backgroundcolor[background] + | bold | underline; + SetConsoleTextAttribute( hConOut, attribut ); + return; + + case 'J': + if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[J == ESC[0J + if (es_argc != 1) return; + switch (es_argv[0]) + { + case 0: // ESC[0J erase from cursor to end of display + len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X + + Info.dwSize.X - Info.dwCursorPosition.X - 1; + FillConsoleOutputCharacter( hConOut, ' ', len, + Info.dwCursorPosition, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten ); + return; + + case 1: // ESC[1J erase from start to cursor. + Pos.X = 0; + Pos.Y = 0; + len = Info.dwCursorPosition.Y * Info.dwSize.X + + Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter( hConOut, ' ', len, Pos, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten ); + return; + + case 2: // ESC[2J Clear screen and home cursor + Pos.X = 0; + Pos.Y = 0; + len = Info.dwSize.X * Info.dwSize.Y; + FillConsoleOutputCharacter( hConOut, ' ', len, Pos, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, Pos, + &NumberOfCharsWritten ); + SetConsoleCursorPosition( hConOut, Pos ); + return; + + default: + return; + } + + case 'K': + if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[K == ESC[0K + if (es_argc != 1) return; + switch (es_argv[0]) + { + case 0: // ESC[0K Clear to end of line + len = Info.srWindow.Right - Info.dwCursorPosition.X + 1; + FillConsoleOutputCharacter( hConOut, ' ', len, + Info.dwCursorPosition, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, len, + Info.dwCursorPosition, + &NumberOfCharsWritten ); + return; + + case 1: // ESC[1K Clear from start of line to cursor + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter( hConOut, ' ', + Info.dwCursorPosition.X + 1, Pos, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, + Info.dwCursorPosition.X + 1, Pos, + &NumberOfCharsWritten ); + return; + + case 2: // ESC[2K Clear whole line. + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + FillConsoleOutputCharacter( hConOut, ' ', Info.dwSize.X, Pos, + &NumberOfCharsWritten ); + FillConsoleOutputAttribute( hConOut, Info.wAttributes, + Info.dwSize.X, Pos, + &NumberOfCharsWritten ); + return; + + default: + return; + } + + case 'L': // ESC[#L Insert # blank lines. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L + if (es_argc != 1) return; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo ); + return; + + case 'M': // ESC[#M Delete # lines. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M + if (es_argc != 1) return; + if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) + es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; + Rect.Left = 0; + Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwSize.Y - 1; + Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo ); + return; + + case 'P': // ESC[#P Delete # characters. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P + if (es_argc != 1) return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X + es_argv[0]; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1; + Rect.Bottom = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Info.dwCursorPosition, + &CharInfo ); + return; + + case '@': // ESC[#@ Insert # blank characters. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ + if (es_argc != 1) return; + if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) + es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; + Rect.Left = Info.dwCursorPosition.X; + Rect.Top = Info.dwCursorPosition.Y; + Rect.Right = Info.dwSize.X - 1 - es_argv[0]; + Rect.Bottom = Info.dwCursorPosition.Y; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + Pos.Y = Info.dwCursorPosition.Y; + CharInfo.Char.UnicodeChar = ' '; + CharInfo.Attributes = Info.wAttributes; + ScrollConsoleScreenBuffer( hConOut, &Rect, NULL, Pos, &CharInfo ); + return; + + case 'A': // ESC[#A Moves cursor up # lines + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[A == ESC[1A + if (es_argc != 1) return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) Pos.Y = 0; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'B': // ESC[#B Moves cursor down # lines + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[B == ESC[1B + if (es_argc != 1) return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; + Pos.X = Info.dwCursorPosition.X; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'C': // ESC[#C Moves cursor forward # spaces + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C + if (es_argc != 1) return; + Pos.X = Info.dwCursorPosition.X + es_argv[0]; + if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'D': // ESC[#D Moves cursor back # spaces + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D + if (es_argc != 1) return; + Pos.X = Info.dwCursorPosition.X - es_argv[0]; + if (Pos.X < 0) Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'E': // ESC[#E Moves cursor down # lines, column 1. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[E == ESC[1E + if (es_argc != 1) return; + Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; + if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; + Pos.X = 0; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'F': // ESC[#F Moves cursor up # lines, column 1. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[F == ESC[1F + if (es_argc != 1) return; + Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; + if (Pos.Y < 0) Pos.Y = 0; + Pos.X = 0; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'G': // ESC[#G Moves cursor column # in current row. + if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G + if (es_argc != 1) return; + Pos.X = es_argv[0] - 1; + if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; + if (Pos.X < 0) Pos.X = 0; + Pos.Y = Info.dwCursorPosition.Y; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 'f': // ESC[#;#f + case 'H': // ESC[#;#H Moves cursor to line #, column # + if (es_argc == 0) + es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H + if (es_argc == 1) + es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H + if (es_argc > 2) return; + Pos.X = es_argv[1] - 1; + if (Pos.X < 0) Pos.X = 0; + if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; + Pos.Y = es_argv[0] - 1; + if (Pos.Y < 0) Pos.Y = 0; + if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; + SetConsoleCursorPosition( hConOut, Pos ); + return; + + case 's': // ESC[s Saves cursor position for recall later + if (es_argc != 0) return; + SavePos = Info.dwCursorPosition; + return; + + case 'u': // ESC[u Return to saved cursor position + if (es_argc != 0) return; + SetConsoleCursorPosition( hConOut, SavePos ); + return; + + default: + return; + } + } +} + + +//----------------------------------------------------------------------------- +// ParseAndPrintString(hDev, lpBuffer, nNumberOfBytesToWrite) +// Parses the string lpBuffer, interprets the escapes sequences and prints the +// characters in the device hDev (console). +// The lexer is a three states automata. +// If the number of arguments es_argc > MAX_ARG, only the MAX_ARG-1 firsts and +// the last arguments are processed (no es_argv[] overflow). +//----------------------------------------------------------------------------- + +BOOL +ParseAndPrintString( HANDLE hDev, + LPCVOID lpBuffer, + DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten + ) +{ + DWORD i; + LPTSTR s; + + if (hDev != hConOut) // reinit if device has changed + { + hConOut = hDev; + state = 1; + } + for (i = nNumberOfBytesToWrite, s = (LPTSTR)lpBuffer; i > 0; i--, s++) + { + if (state == 1) + { + if (*s == ESC) state = 2; + else PushBuffer( *s ); + } + else if (state == 2) + { + if (*s == ESC) ; // \e\e...\e == \e + else if ((*s == '[')) // || (*s == '(')) + { + FlushBuffer(); + //prefix = *s; + state = 3; + } + else state = 1; + } + else if (state == 3) + { + if (isdigit( *s )) + { + es_argc = 0; + es_argv[0] = *s - '0'; + state = 4; + } + else if (*s == ';') + { + es_argc = 1; + es_argv[0] = 0; + es_argv[1] = 0; + state = 4; + } + else + { + es_argc = 0; + suffix = *s; + InterpretEscSeq(); + state = 1; + } + } + else if (state == 4) + { + if (isdigit( *s )) + { + es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); + } + else if (*s == ';') + { + if (es_argc < MAX_ARG-1) es_argc++; + es_argv[es_argc] = 0; + } + else + { + es_argc++; + suffix = *s; + InterpretEscSeq(); + state = 1; + } + } + } + FlushBuffer(); + *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; + return( i == 0 ); +} + + +// ========== Child process injection + +// Inject code into the target process to load our DLL. +void Inject( LPPROCESS_INFORMATION pinfo, LPPROCESS_INFORMATION lpi, + DWORD dwCreationFlags ) +{ + char* ptr = 0; + MEMORY_BASIC_INFORMATION minfo; + BOOL con = FALSE; +#ifdef _WIN64 + BOOL x86 = FALSE; +#endif + + while (VirtualQueryEx( pinfo->hProcess, ptr, &minfo, sizeof(minfo) )) + { + IMAGE_DOS_HEADER dos_header; + SIZE_T read; + if (ReadProcessMemory( pinfo->hProcess, minfo.AllocationBase, + &dos_header, sizeof(dos_header), &read )) + { + if (dos_header.e_magic == IMAGE_DOS_SIGNATURE) + { + IMAGE_NT_HEADERS nt_header; + if (ReadProcessMemory( pinfo->hProcess, (char*)minfo.AllocationBase + + dos_header.e_lfanew, &nt_header, + sizeof(nt_header), &read )) + { + if (nt_header.Signature == IMAGE_NT_SIGNATURE) + { + if (nt_header.OptionalHeader.Subsystem == + IMAGE_SUBSYSTEM_WINDOWS_CUI) + { + if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_I386) + { + con = TRUE; +#ifdef _WIN64 + x86 = TRUE; + } + else if (nt_header.FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64) + { + con = TRUE; +#endif + } + else + { + DEBUGSTR( TEXT(" Ignoring unsupported machine (%x)"), + nt_header.FileHeader.Machine ); + } + } + else + { + DEBUGSTR( TEXT(" Ignoring non-console subsystem (%u)"), + nt_header.OptionalHeader.Subsystem ); + } + break; + } + } + } + } + ptr += minfo.RegionSize; + } + + if (con) + { + CHAR dll[MAX_PATH]; +#ifdef _WIN64 + DWORD len = GetModuleFileNameA( GetModuleHandleA( "ANSI64.dll" ), + dll, sizeof(dll) ); + if (x86) + { + dll[len-6] = '3'; + dll[len-5] = '2'; + InjectDLL32( pinfo, dll ); + } + else + { + InjectDLL64( pinfo, dll ); + } +#else + GetModuleFileNameA( GetModuleHandleA( "ANSI32.dll" ), dll, sizeof(dll) ); + InjectDLL32( pinfo, dll ); +#endif + } + + if (lpi) + memcpy( lpi, pinfo, sizeof(PROCESS_INFORMATION) ); + + if (!(dwCreationFlags & CREATE_SUSPENDED)) + ResumeThread( pinfo->hThread ); +} + + +BOOL WINAPI MyCreateProcessA( LPCSTR lpApplicationName, + LPSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCSTR lpCurrentDirectory, + LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation ) +{ + PROCESS_INFORMATION pi; + + if (!CreateProcessA( lpApplicationName, + lpCommandLine, + lpThreadAttributes, + lpProcessAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + &pi )) + return FALSE; + + DEBUGSTR( TEXT("CreateProcessA: \"%hs\", \"%hs\""), + (lpApplicationName == NULL) ? "" : lpApplicationName, + (lpCommandLine == NULL) ? "" : lpCommandLine ); + Inject( &pi, lpProcessInformation, dwCreationFlags ); + + return TRUE; +} + + +BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName, + LPWSTR lpCommandLine, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + LPSECURITY_ATTRIBUTES lpProcessAttributes, + BOOL bInheritHandles, + DWORD dwCreationFlags, + LPVOID lpEnvironment, + LPCWSTR lpCurrentDirectory, + LPSTARTUPINFOW lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation ) +{ + PROCESS_INFORMATION pi; + + if (!CreateProcessW( lpApplicationName, + lpCommandLine, + lpThreadAttributes, + lpProcessAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + &pi )) + return FALSE; + + DEBUGSTR( TEXT("CreateProcessW: \"%ls\", \"%ls\""), + (lpApplicationName == NULL) ? L"" : lpApplicationName, + (lpCommandLine == NULL) ? L"" : lpCommandLine ); + Inject( &pi, lpProcessInformation, dwCreationFlags ); + + return TRUE; +} + + +HMODULE WINAPI MyLoadLibraryA( LPCSTR lpFileName ) +{ + HMODULE hMod = LoadLibraryA( lpFileName ); + if (hMod && hMod != hKernel) + { + DEBUGSTR( TEXT("Hooking in %hs (LoadLibraryA)"), lpFileName ); + HookAPIOneMod( hMod, Hooks, FALSE ); + } + return hMod; +} + + +HMODULE WINAPI MyLoadLibraryW( LPCWSTR lpFileName ) +{ + HMODULE hMod = LoadLibraryW( lpFileName ); + if (hMod && hMod != hKernel) + { + DEBUGSTR( TEXT("Hooking in %ls (LoadLibraryW)"), lpFileName ); + HookAPIOneMod( hMod, Hooks, FALSE ); + } + return hMod; +} + + +HMODULE WINAPI MyLoadLibraryExA( LPCSTR lpFileName, HANDLE hFile, + DWORD dwFlags ) +{ + HMODULE hMod = LoadLibraryExA( lpFileName, hFile, dwFlags ); + if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATAFILE)) + { + DEBUGSTR( TEXT("Hooking in %hs (LoadLibraryExA)"), lpFileName ); + HookAPIOneMod( hMod, Hooks, FALSE ); + } + return hMod; +} + + +HMODULE WINAPI MyLoadLibraryExW( LPCWSTR lpFileName, HANDLE hFile, + DWORD dwFlags ) +{ + HMODULE hMod = LoadLibraryExW( lpFileName, hFile, dwFlags ); + if (hMod && hMod != hKernel && !(dwFlags & LOAD_LIBRARY_AS_DATAFILE)) + { + DEBUGSTR( TEXT("Hooking in %ls (LoadLibraryExW)"), lpFileName ); + HookAPIOneMod( hMod, Hooks, FALSE ); + } + return hMod; +} + + +//----------------------------------------------------------------------------- +// MyWrite... +// It is the new function that must replace the original Write... function. +// This function have exactly the same signature as the original one. +//----------------------------------------------------------------------------- + +BOOL +WINAPI MyWriteConsoleA( HANDLE hCon, LPCVOID lpBuffer, + DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved ) +{ + DWORD Mode; + #define BUF_SIZE 4096 + WCHAR buf[BUF_SIZE]; + DWORD len; + BOOL rc = TRUE; + + // if we write in a console buffer with processed output + if (GetConsoleMode( hCon, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT)) + { + UINT cp = GetConsoleOutputCP(); + DEBUGSTR( TEXT("\\WriteConsoleA: %lu \"%.*hs\""), nNumberOfCharsToWrite, nNumberOfCharsToWrite, lpBuffer ); + *lpNumberOfCharsWritten = 0; + while (nNumberOfCharsToWrite) + { + len = (nNumberOfCharsToWrite > BUF_SIZE) ? BUF_SIZE + : nNumberOfCharsToWrite; + MultiByteToWideChar( cp, 0, lpBuffer, len, buf, len ); + rc = ParseAndPrintString( hCon, buf, len, &Mode ); + *lpNumberOfCharsWritten += Mode; + lpBuffer += len; + nNumberOfCharsToWrite -= len; + } + return rc; + } + else + { + return WriteConsoleA( hCon, lpBuffer, + nNumberOfCharsToWrite, + lpNumberOfCharsWritten, + lpReserved ); + } +} + +BOOL +WINAPI MyWriteConsoleW( HANDLE hCon, LPCVOID lpBuffer, + DWORD nNumberOfCharsToWrite, + LPDWORD lpNumberOfCharsWritten, LPVOID lpReserved ) +{ + DWORD Mode; + if (GetConsoleMode( hCon, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT)) + { + DEBUGSTR( TEXT("\\WriteConsoleW: %lu \"%.*ls\""), nNumberOfCharsToWrite, nNumberOfCharsToWrite, lpBuffer ); + return ParseAndPrintString( hCon, lpBuffer, + nNumberOfCharsToWrite, + lpNumberOfCharsWritten ); + } + else + { + return WriteConsoleW( hCon, lpBuffer, + nNumberOfCharsToWrite, + lpNumberOfCharsWritten, + lpReserved ); + } +} + +BOOL +WINAPI MyWriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ) +{ + DWORD Mode; + if (GetConsoleMode( hFile, &Mode ) && (Mode & ENABLE_PROCESSED_OUTPUT)) + { + DEBUGSTR( TEXT("\\WriteFile: %lu \"%.*hs\""), nNumberOfBytesToWrite, nNumberOfBytesToWrite, lpBuffer ); + return MyWriteConsoleA( hFile, lpBuffer, + nNumberOfBytesToWrite, + lpNumberOfBytesWritten, + lpOverlapped ); + } + else // here, WriteFile is the old function (this module is not hooked) + { + return WriteFile( hFile, lpBuffer, + nNumberOfBytesToWrite, + lpNumberOfBytesWritten, + lpOverlapped ); + } +} + + +// ========== Environment variable + +void set_ansicon( PCONSOLE_SCREEN_BUFFER_INFO pcsbi ) +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (pcsbi == NULL) + { + HANDLE hConOut; + hConOut = CreateFile( TEXT("CONOUT$"), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0 ); + GetConsoleScreenBufferInfo( hConOut, &csbi ); + CloseHandle( hConOut ); + pcsbi = &csbi; + } + + TCHAR buf[64]; + wsprintf( buf, TEXT("%dx%d (%dx%d)"), + pcsbi->dwSize.X, pcsbi->dwSize.Y, + pcsbi->srWindow.Right - pcsbi->srWindow.Left + 1, + pcsbi->srWindow.Bottom - pcsbi->srWindow.Top + 1 ); + SetEnvironmentVariable( TEXT("ANSICON"), buf ); +} + +DWORD +WINAPI MyGetEnvironmentVariableA( LPCSTR lpName, LPSTR lpBuffer, DWORD nSize ) +{ + if (lstrcmpiA( lpName, "ANSICON" ) == 0) + set_ansicon( NULL ); + return GetEnvironmentVariableA( lpName, lpBuffer, nSize ); +} + +DWORD +WINAPI MyGetEnvironmentVariableW( LPCWSTR lpName, LPWSTR lpBuffer, DWORD nSize ) +{ + if (lstrcmpiW( lpName, L"ANSICON" ) == 0) + set_ansicon( NULL ); + return GetEnvironmentVariableW( lpName, lpBuffer, nSize ); +} + + +// ========== Initialisation + +HookFn Hooks[] = { + { APIProcessThreads, "CreateProcessA", (PROC)MyCreateProcessA, NULL, NULL }, + { APIProcessThreads, "CreateProcessW", (PROC)MyCreateProcessW, NULL, NULL }, + { APIProcessEnvironment, "GetEnvironmentVariableA", (PROC)MyGetEnvironmentVariableA, NULL, NULL }, + { APIProcessEnvironment, "GetEnvironmentVariableW", (PROC)MyGetEnvironmentVariableW, NULL, NULL }, + { APILibraryLoader, "LoadLibraryA", (PROC)MyLoadLibraryA, NULL, NULL }, + { APILibraryLoader, "LoadLibraryW", (PROC)MyLoadLibraryW, NULL, NULL }, + { APILibraryLoader, "LoadLibraryExA", (PROC)MyLoadLibraryExA, NULL, NULL }, + { APILibraryLoader, "LoadLibraryExW", (PROC)MyLoadLibraryExW, NULL, NULL }, + { APIConsole, "WriteConsoleA", (PROC)MyWriteConsoleA, NULL, NULL }, + { APIConsole, "WriteConsoleW", (PROC)MyWriteConsoleW, NULL, NULL }, + { APIFile, "WriteFile", (PROC)MyWriteFile, NULL, NULL }, + { NULL, NULL, NULL, NULL } +}; + +//----------------------------------------------------------------------------- +// OriginalAttr() +// Determine the original attributes for use by \e[m. +//----------------------------------------------------------------------------- +void OriginalAttr( void ) +{ + static const char attr2ansi[8] = // map console attribute to ANSI number + { + 0, 4, 2, 6, 1, 5, 3, 7 + }; + HANDLE hConOut; + CONSOLE_SCREEN_BUFFER_INFO csbi; + + hConOut = CreateFile( TEXT("CONOUT$"), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0 ); + if (!GetConsoleScreenBufferInfo( hConOut, &csbi )) + csbi.wAttributes = 7; + CloseHandle( hConOut ); + foreground = org_fg = attr2ansi[csbi.wAttributes & 7]; + background = org_bg = attr2ansi[(csbi.wAttributes >> 4) & 7]; + bold = org_bold = csbi.wAttributes & FOREGROUND_INTENSITY; + underline = org_ul = csbi.wAttributes & BACKGROUND_INTENSITY; + + set_ansicon( &csbi ); +} + + +//----------------------------------------------------------------------------- +// DllMain() +// Function called by the system when processes and threads are initialized +// and terminated. +//----------------------------------------------------------------------------- + +__declspec(dllexport) // to stop MinGW exporting everything +BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved ) +{ + BOOL bResult = TRUE; + HMODULE api; + PHookFn hook; + +#if (MYDEBUG > 1) + _snprintf( tempfile, MAX_PATH, "%s\\ansicon.log", getenv( "TEMP" ) ); +#endif + + if (dwReason == DLL_PROCESS_ATTACH) + { +#if (MYDEBUG > 1) + DeleteFileA( tempfile ); +#endif + + hDllInstance = hInstance; // save Dll instance handle + DEBUGSTR( TEXT("hDllInstance = %p"), hDllInstance ); + + // Get the entry points to the original functions. + hKernel = GetModuleHandleA( APIKernel ); + for (hook = Hooks; hook->name; ++hook) + { + hook->oldfunc = GetProcAddress( hKernel, hook->name ); + api = GetModuleHandleA( hook->lib ); + if (api) + hook->apifunc = GetProcAddress( api, hook->name ); + } + + bResult = HookAPIAllMod( Hooks, FALSE ); + OriginalAttr(); + DisableThreadLibraryCalls( hInstance ); + } + else if (dwReason == DLL_PROCESS_DETACH && lpReserved == NULL) + { + DEBUGSTR( TEXT("Unloading") ); + HookAPIAllMod( Hooks, TRUE ); + } + + return( bResult ); +} diff --git a/COPYING.MinGW-w64-runtime.txt b/COPYING.MinGW-w64-runtime.txt new file mode 100644 index 0000000..9544220 --- /dev/null +++ b/COPYING.MinGW-w64-runtime.txt @@ -0,0 +1,169 @@ +MinGW-w64 runtime licensing +*************************** + +This program or library was built using MinGW-w64 and statically +linked against the MinGW-w64 runtime. Some parts of the runtime +are under licenses which require that the copyright and license +notices are included when distributing the code in binary form. +These notices are listed below. + + +======================== +Overall copyright notice +======================== + +Copyright (c) 2009, 2010 by the mingw-w64 project + +This license has been certified as open source. It has also been designated +as GPL compatible by the Free Software Foundation (FSF). + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying + copyright notice, this list of conditions, and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of + the copyright holders. Use of them is covered by separate agreement + with the copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of + any change. + +Disclaimer + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +=============================================================== +gdtoa: Converting between IEEE floating point numbers and ASCII +=============================================================== + +The author of this software is David M. Gay. + +Copyright (C) 1997, 1998, 1999, 2000, 2001 by Lucent Technologies +All Rights Reserved + +Permission to use, copy, modify, and distribute this software and +its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of Lucent or any of its entities +not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER +IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + * * * * * * * + +The author of this software is David M. Gay. + +Copyright (C) 2005 by David M. Gay +All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that the copyright notice and this permission notice and warranty +disclaimer appear in supporting documentation, and that the name of +the author or any of his current or former employers not be used in +advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN +NO EVENT SHALL THE AUTHOR OR ANY OF HIS CURRENT OR FORMER EMPLOYERS BE +LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + + * * * * * * * + +The author of this software is David M. Gay. + +Copyright (C) 2004 by David M. Gay. +All Rights Reserved +Based on material in the rest of /netlib/fp/gdota.tar.gz, +which is copyright (C) 1998, 2000 by Lucent Technologies. + +Permission to use, copy, modify, and distribute this software and +its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of Lucent or any of its entities +not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER +IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. + + +========================= +Parts of the math library +========================= + +Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunSoft, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice +is preserved. + + * * * * * * * + +Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + +Developed at SunPro, a Sun Microsystems, Inc. business. +Permission to use, copy, modify, and distribute this +software is freely granted, provided that this notice +is preserved. + + * * * * * * * + +FIXME: Cephes math lib +Copyright (C) 1984-1998 Stephen L. Moshier + +It sounds vague, but as to be found at +, it gives an +impression that the author could be willing to give an explicit +permission to distribute those files e.g. under a BSD style license. So +probably there is no problem here, although it could be good to get a +permission from the author and then add a license into the Cephes files +in MinGW runtime. At least on follow-up it is marked that debian sees the +version a-like BSD one. As MinGW.org (where those cephes parts are coming +from) distributes them now over 6 years, it should be fine. diff --git a/ansi.rc b/ansi.rc new file mode 100644 index 0000000..843e45b --- /dev/null +++ b/ansi.rc @@ -0,0 +1,41 @@ +/* + ansi.rc - Version resource for ANSI{32,64}.dll. + + Jason Hood, 11 November, 2009. +*/ + +#include + +#ifdef _WIN64 +# define BITS "64" +#else +# define BITS "32" +#endif + +1 VERSIONINFO +FILEVERSION 1,3,0,0 +PRODUCTVERSION 1,3,0,0 +FILEOS VOS_NT +FILETYPE VFT_DLL +{ + BLOCK "StringFileInfo" + { + BLOCK "040904B0" + { + VALUE "Comments", "http://ansicon.adoxa.cjb.net/" + VALUE "CompanyName", "Jason Hood" + VALUE "FileDescription", "ANSI Console" + VALUE "FileVersion", "1.30" + VALUE "InternalName", "ANSI" BITS + VALUE "LegalCopyright", "Freeware" + VALUE "OriginalFilename", "ANSI" BITS ".dll" + VALUE "ProductName", "ANSICON" + VALUE "ProductVersion", "1.30" + } + } + + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0x04B0 + } +} diff --git a/ansicon.c b/ansicon.c new file mode 100644 index 0000000..28d513b --- /dev/null +++ b/ansicon.c @@ -0,0 +1,541 @@ +/* + ANSICON.c - ANSI escape sequence console driver. + + Jason Hood, 21 to 23 October, 2005. + + Original injection code was derived from Console Manager by Sergey Oblomov + (hoopoepg). Use of FlushInstructionCache came from www.catch22.net. + Additional information came from "Process-wide API spying - an ultimate hack", + Anton Bassov's article in "The Code Project" (use of OpenThread). + + v1.01, 11 & 12 March, 2006: + -m option to set "monochrome" (grey on black); + restore original color on exit. + + v1.10, 22 February, 2009: + ignore Ctrl+C/Ctrl+Break. + + v1.13, 21 & 27 March, 2009: + alternate injection method, to work with DEP; + use Unicode. + + v1.20, 17 to 21 June, 2009: + use a combination of the two injection methods; + test if ANSICON is already installed; + added -e (and -E) option to echo the command line (without newline); + added -t (and -T) option to type (display) files (with file name). + + v1.21, 23 September, 2009: + added -i (and -u) to add (remove) ANSICON to AutoRun. + + v1.24, 6 & 7 January, 2010: + no arguments to -t, or using "-" for the name, will read from stdin; + fix -t and -e when ANSICON was already loaded. + + v1.25, 22 July, 2010: + added -IU for HKLM. + + v1.30, 3 August to 7 September, 2010: + x64 support. +*/ + +#define PVERS "1.30" +#define PDATE "7 September, 2010" + +#define UNICODE +#define _UNICODE + +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0500 // MinGW wants this defined for OpenThread +#include +#include +#include +#include +#include +#include +#include +#include +#include "injdll.h" + +#ifdef __MINGW32__ +int _CRT_glob = 0; +#endif + + +#ifdef _WIN64 +# define InjectDLL InjectDLL64 +# define BITS "64" +#else +# define InjectDLL InjectDLL32 +# define BITS "32" +#endif + + +#define CMDKEY TEXT("Software\\Microsoft\\Command Processor") +#define AUTORUN TEXT("AutoRun") + + +void help( void ); + +void display( LPCTSTR, BOOL ); +LPTSTR skip_spaces( LPTSTR ); +LPTSTR skip_arg( LPTSTR ); + +void process_autorun( TCHAR ); + +BOOL find_proc_id( HANDLE snap, DWORD id, LPPROCESSENTRY32 ppe ); +BOOL GetParentProcessInfo( LPPROCESS_INFORMATION ppi ); + + +// Find the name of the DLL and inject it. +void Inject( LPPROCESS_INFORMATION ppi ) +{ + DWORD len; + CHAR dll[MAX_PATH]; + + len = GetModuleFileNameA( NULL, dll, sizeof(dll) ); + while (dll[len-1] != '\\') + --len; + lstrcpyA( dll + len, "ANSI" BITS ".dll" ); + + InjectDLL( ppi, dll ); +} + + +static HANDLE hConOut; +static CONSOLE_SCREEN_BUFFER_INFO csbi; + +void get_original_attr( void ) +{ + hConOut = CreateFile( TEXT("CONOUT$"), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, 0 ); + GetConsoleScreenBufferInfo( hConOut, &csbi ); +} + + +void set_original_attr( void ) +{ + SetConsoleTextAttribute( hConOut, csbi.wAttributes ); + CloseHandle( hConOut ); +} + + +DWORD CtrlHandler( DWORD event ) +{ + return (event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT); +} + + +//int _tmain( int argc, TCHAR* argv[] ) +int main( void ) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + TCHAR* cmd; + BOOL option; + BOOL opt_m; + BOOL installed; + HMODULE ansi; + int rc = 0; + + int argc; + LPWSTR* argv = CommandLineToArgvW( GetCommandLineW(), &argc ); + + if (argc > 1) + { + if (lstrcmp( argv[1], TEXT("--help") ) == 0 || + (argv[1][0] == '-' && (argv[1][1] == '?' || argv[1][1] == 'h')) || + (argv[1][0] == '/' && argv[1][1] == '?')) + { + help(); + return rc; + } + if (lstrcmp( argv[1], TEXT("--version") ) == 0) + { + _putts( TEXT("ANSICON (" BITS "-bit) version " PVERS " (" PDATE ").") ); + return rc; + } + } + + option = (argc > 1 && argv[1][0] == '-'); + if (option && (_totlower( argv[1][1] ) == 'i' || + _totlower( argv[1][1] ) == 'u')) + { + process_autorun( argv[1][1] ); + return rc; + } + + get_original_attr(); + + opt_m = FALSE; + if (option && argv[1][1] == 'm') + { + WORD attr = 7; + if (_istxdigit( argv[1][2] )) + { + attr = _istdigit( argv[1][2] ) ? argv[1][2] - '0' + : (argv[1][2] | 0x20) - 'a' + 10; + if (_istxdigit( argv[1][3])) + { + attr <<= 4; + attr |= _istdigit( argv[1][3] ) ? argv[1][3] - '0' + : (argv[1][3] | 0x20) - 'a' + 10; + } + } + SetConsoleTextAttribute( hConOut, attr ); + + opt_m = TRUE; + ++argv; + --argc; + option = (argc > 1 && argv[1][0] == '-'); + } + + installed = (GetEnvironmentVariable( TEXT("ANSICON"), NULL, 0 ) != 0); + + if (option && argv[1][1] == 'p') + { + // If it's already installed, there's no need to do anything. + if (installed) + ; + else if (GetParentProcessInfo( &pi )) + { + pi.hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pi.dwProcessId ); + pi.hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, pi.dwThreadId ); + SuspendThread( pi.hThread ); + Inject( &pi ); + ResumeThread( pi.hThread ); + CloseHandle( pi.hThread ); + CloseHandle( pi.hProcess ); + } + else + { + _putts( TEXT("ANSICON: could not obtain the parent process.") ); + rc = 1; + } + } + else + { + ansi = 0; + if (!installed) + ansi = LoadLibrary( TEXT("ANSI" BITS ".dll") ); + + if (option && (argv[1][1] == 't' || argv[1][1] == 'T')) + { + BOOL title = (argv[1][1] == 'T'); + if (argc == 2) + { + argv[2] = L"-"; + ++argc; + } + for (; argc > 2; ++argv, --argc) + { + if (title) + _tprintf( TEXT("==> %s <==\n"), argv[2] ); + display( argv[2], title ); + if (title) + _puttchar( '\n' ); + } + } + else + { + // Retrieve the original command line, skipping our name and the option. + cmd = skip_spaces( skip_arg( skip_spaces( GetCommandLine() ) ) ); + if (opt_m) + cmd = skip_spaces( skip_arg( cmd ) ); + + if (cmd[0] == '-' && (cmd[1] == 'e' || cmd[1] == 'E')) + { + _fputts( cmd + 3, stdout ); + if (cmd[1] == 'e') + _puttchar( '\n' ); + } + else if (!isatty( 0 ) && *cmd == '\0') + { + display( TEXT("-"), FALSE ); + } + else + { + if (*cmd == '\0') + { + cmd = _tgetenv( TEXT("ComSpec") ); + if (cmd == NULL) + cmd = TEXT("cmd"); + } + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + if (CreateProcess( NULL, cmd, NULL,NULL, TRUE, 0, NULL,NULL, &si, &pi )) + { + SetConsoleCtrlHandler( (PHANDLER_ROUTINE)CtrlHandler, TRUE ); + WaitForSingleObject( pi.hProcess, INFINITE ); + } + else + { + *skip_arg( cmd ) = '\0'; + _tprintf( TEXT("ANSICON: '%s' could not be executed.\n"), cmd ); + rc = 1; + } + } + } + + if (ansi) + FreeLibrary( ansi ); + } + + set_original_attr(); + return rc; +} + + +void print_error( LPCTSTR name, BOOL title ) +{ + LPTSTR errmsg; + + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, GetLastError(), 0, (LPTSTR)(LPVOID)&errmsg, 0, NULL ); + if (!title) + _tprintf( TEXT("ANSICON: %s: "), name ); + _fputts( errmsg, stdout ); + LocalFree( errmsg ); +} + + +// Display a file. +void display( LPCTSTR name, BOOL title ) +{ + // Handle the pipe differently. + if (*name == '-' && name[1] == '\0') + { + if (title) + _puttchar( '\n' ); + int c; + while ((c = getchar()) != EOF) + putchar( c ); + return; + } + + HANDLE file = CreateFile( name, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, NULL ); + if (file == INVALID_HANDLE_VALUE) + { + print_error( name, title ); + return; + } + + LARGE_INTEGER size; + GetFileSizeEx( file, &size ); + if (size.QuadPart != 0) + { + HANDLE map = CreateFileMapping( file, NULL, PAGE_READONLY, 0, 0, NULL ); + if (map) + { + if (title) + _puttchar( '\n' ); + LARGE_INTEGER offset; + offset.QuadPart = 0; + do + { + DWORD len = (size.QuadPart > 65536) ? 65536 : size.LowPart; + LPVOID mem = MapViewOfFile( map, FILE_MAP_READ, offset.HighPart, + offset.LowPart, len ); + if (mem) + { + fwrite( mem, 1, len, stdout ); + UnmapViewOfFile( mem ); + } + else + { + print_error( name, title ); + break; + } + offset.QuadPart += len; + size.QuadPart -= len; + } while (size.QuadPart); + CloseHandle( map ); + } + else + print_error( name, title ); + } + CloseHandle( file ); +} + + +// Add or remove ANSICON to AutoRun. +void process_autorun( TCHAR cmd ) +{ + HKEY cmdkey; + TCHAR ansicon[MAX_PATH+8]; + LPTSTR autorun, ansirun; + DWORD len, type, exist; + BOOL inst; + + len = GetModuleFileName( NULL, ansicon+2, MAX_PATH ); + ansicon[0] = '&'; + ansicon[1] = ansicon[2+len] = '"'; + _tcscpy( ansicon + 3+len, L" -p" ); + len += 6; + + inst = (_totlower( cmd ) == 'i'); + RegCreateKeyEx( (_istlower( cmd )) ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, + CMDKEY, 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, + &cmdkey, &exist ); + exist = 0; + RegQueryValueEx( cmdkey, AUTORUN, NULL, NULL, NULL, &exist ); + autorun = malloc( exist + len * sizeof(TCHAR) + sizeof(TCHAR) ); + // Let's assume there's sufficient memory. + if (exist > sizeof(TCHAR)) + { + exist += sizeof(TCHAR); + RegQueryValueEx( cmdkey, AUTORUN, NULL, &type, (PBYTE)autorun, &exist ); + ansirun = _tcsstr( autorun, ansicon+1 ); + if (inst) + { + if (!ansirun) + { + _tcscpy( (LPTSTR)((PBYTE)autorun + exist - sizeof(TCHAR)), ansicon ); + RegSetValueEx( cmdkey, AUTORUN, 0, type, (PBYTE)autorun, + exist + len*sizeof(TCHAR) ); + } + } + else + { + if (ansirun) + { + if (ansirun == autorun && exist == len*sizeof(TCHAR)) + RegDeleteValue( cmdkey, AUTORUN ); + else + { + if (ansirun > autorun && ansirun[-1] == '&') + --ansirun; + else if (autorun[len-1] != '&') + --len; + memcpy( ansirun, ansirun + len, exist - len*sizeof(TCHAR) ); + RegSetValueEx( cmdkey, AUTORUN, 0, type, (PBYTE)autorun, + exist - len*sizeof(TCHAR) ); + } + } + } + } + else if (inst) + { + RegSetValueEx( cmdkey, AUTORUN, 0, REG_SZ, (PBYTE)(ansicon+1), + len*sizeof(TCHAR) ); + } + + free( autorun ); + RegCloseKey( cmdkey ); +} + + +// Search each process in the snapshot for id. +BOOL find_proc_id( HANDLE snap, DWORD id, LPPROCESSENTRY32 ppe ) +{ + BOOL fOk; + + ppe->dwSize = sizeof(PROCESSENTRY32); + for (fOk = Process32First( snap, ppe ); fOk; fOk = Process32Next( snap, ppe )) + if (ppe->th32ProcessID == id) + break; + + return fOk; +} + + +// Obtain the process and thread identifiers of the parent process. +BOOL GetParentProcessInfo( LPPROCESS_INFORMATION ppi ) +{ + HANDLE hSnap; + PROCESSENTRY32 pe; + THREADENTRY32 te; + DWORD id = GetCurrentProcessId(); + BOOL fOk; + + hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD, id ); + + if (hSnap == INVALID_HANDLE_VALUE) + return FALSE; + + find_proc_id( hSnap, id, &pe ); + if (!find_proc_id( hSnap, pe.th32ParentProcessID, &pe )) + { + CloseHandle( hSnap ); + return FALSE; + } + + te.dwSize = sizeof(te); + for (fOk = Thread32First( hSnap, &te ); fOk; fOk = Thread32Next( hSnap, &te )) + if (te.th32OwnerProcessID == pe.th32ProcessID) + break; + + CloseHandle( hSnap ); + + ppi->dwProcessId = pe.th32ProcessID; + ppi->dwThreadId = te.th32ThreadID; + + return fOk; +} + + +// Return the first non-space character from cmd. +LPTSTR skip_spaces( LPTSTR cmd ) +{ + while ((*cmd == ' ' || *cmd == '\t') && *cmd != '\0') + ++cmd; + + return cmd; +} + + +// Return the end of the argument at cmd. +LPTSTR skip_arg( LPTSTR cmd ) +{ + while (*cmd != ' ' && *cmd != '\t' && *cmd != '\0') + { + if (*cmd == '"') + { + do + ++cmd; + while (*cmd != '"' && *cmd != '\0'); + if (*cmd == '\0') + --cmd; + } + ++cmd; + } + + return cmd; +} + + +void help( void ) +{ + _putts( TEXT( +"ANSICON by Jason Hood .\n" +"Version " PVERS " (" PDATE "). Freeware.\n" +"http://ansicon.adoxa.cjb.net/\n" +"\n" +#ifdef _WIN64 +"Process ANSI escape sequences in Windows console programs.\n" +#else +"Process ANSI escape sequences in Win32 console programs.\n" +#endif +"\n" +"ansicon -i|I | -u|U\n" +"ansicon [-m[]] [-p | -e|E string | -t|T [file(s)] | program [args]]\n" +"\n" +" -i\t\tinstall - add ANSICON to the AutoRun entry\n" +" -u\t\tuninstall - remove ANSICON from the AutoRun entry\n" +" -I -U\t\tuse local machine instead of current user\n" +" -m\t\tuse grey on black (\"monochrome\") or as default color\n" +" -p\t\thook into the parent process\n" +" -e\t\techo string\n" +" -E\t\techo string, don't append newline\n" +" -t\t\tdisplay files (\"-\" for stdin), combined as a single stream\n" +" -T\t\tdisplay files, name first, blank line before and after\n" +" program\trun the specified program\n" +" nothing\trun a new command processor, or display stdin if redirected\n" +"\n" +" is one or two hexadecimal digits; please use \"COLOR /?\" for details." + ) ); +} diff --git a/ansicon.rc b/ansicon.rc new file mode 100644 index 0000000..0b88235 --- /dev/null +++ b/ansicon.rc @@ -0,0 +1,35 @@ +/* + ansicon.rc - Version resource for ansicon.exe. + + Jason Hood, 11 November, 2009. +*/ + +#include + +1 VERSIONINFO +FILEVERSION 1,3,0,0 +PRODUCTVERSION 1,3,0,0 +FILEOS VOS_NT +FILETYPE VFT_APP +{ + BLOCK "StringFileInfo" + { + BLOCK "040904B0" + { + VALUE "Comments", "http://ansicon.adoxa.cjb.net/" + VALUE "CompanyName", "Jason Hood" + VALUE "FileDescription", "ANSI Console" + VALUE "FileVersion", "1.30" + VALUE "InternalName", "ansicon" + VALUE "LegalCopyright", "Freeware" + VALUE "OriginalFilename", "ansicon.exe" + VALUE "ProductName", "ANSICON" + VALUE "ProductVersion", "1.30" + } + } + + BLOCK "VarFileInfo" + { + VALUE "Translation", 0x0409, 0x04B0 + } +} diff --git a/injdll.h b/injdll.h new file mode 100644 index 0000000..8541941 --- /dev/null +++ b/injdll.h @@ -0,0 +1,16 @@ +/* + injdll.h - Simple header file for injecting the DLL. + + Jason Hood, 20 June, 2009. +*/ + +#ifndef INJDLL_H +#define INJDLL_H + +#define WIN32_LEAN_AND_MEAN +#include + +void InjectDLL32( LPPROCESS_INFORMATION, LPCSTR ); +void InjectDLL64( LPPROCESS_INFORMATION, LPCSTR ); + +#endif diff --git a/injdll32.c b/injdll32.c new file mode 100644 index 0000000..6a1f73b --- /dev/null +++ b/injdll32.c @@ -0,0 +1,112 @@ +/* + Inject code into the target process to load our DLL. The target thread + should be suspended on entry; it remains suspended on exit. + + Initially I used the "stack" method of injection. However, this fails + when DEP is active, since that doesn't allow code to execute in the stack. + To overcome this I used the "CreateRemoteThread" method. However, this + would fail with Wselect, a program to assist batch files. Wselect runs, + but it has no output. As it turns out, removing the suspended flag would + make Wselect work, but it caused problems with everything else. So now I + allocate a section of memory and change the context to run from there. At + first I had an event to signal when the library was loaded, then the memory + was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 + worked fine). Since it's possible the DLL might start a process suspended, + I've decided to simply keep the memory. +*/ + +#include "injdll.h" + +#ifdef _WIN64 +#include "wow64.h" + +TWow64GetThreadContext Wow64GetThreadContext; +TWow64SetThreadContext Wow64SetThreadContext; + +#define CONTEXT WOW64_CONTEXT +#undef CONTEXT_CONTROL +#define CONTEXT_CONTROL WOW64_CONTEXT_CONTROL +#define GetThreadContext Wow64GetThreadContext +#define SetThreadContext Wow64SetThreadContext +#endif + + +DWORD LLA; + + +void InjectDLL32( LPPROCESS_INFORMATION ppi, LPCSTR dll ) +{ + CONTEXT context; + DWORD len; + LPVOID mem; + DWORD mem32; + #define CODESIZE 20 + BYTE code[CODESIZE+MAX_PATH]; + + len = lstrlenA( dll ) + 1; + if (len > MAX_PATH) + return; + + if (LLA == 0) + { +#ifdef _WIN64 + extern HMODULE hKernel; + #define GETPROC( proc ) proc = (T##proc)GetProcAddress( hKernel, #proc ) + GETPROC( Wow64GetThreadContext ); + GETPROC( Wow64SetThreadContext ); + // Assume if one is defined, so is the other. + if (Wow64GetThreadContext == 0) + return; + + STARTUPINFO si; + PROCESS_INFORMATION pi; + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + CopyMemory( code, dll, len - 7 ); // ...ANSI32.dll\0 + CopyMemory( code + len - 7, "-LLA.exe", 9 ); // ...ANSI-LLA.exe\0 + if (!CreateProcess( (char*)code, NULL, NULL, NULL, FALSE, 0, NULL, NULL, + &si, &pi )) + return; + WaitForSingleObject( pi.hProcess, INFINITE ); + GetExitCodeProcess( pi.hProcess, &LLA ); + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); +#else + LLA = (DWORD)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), + "LoadLibraryA" ); +#endif + } + + CopyMemory( code + CODESIZE, dll, len ); + len += CODESIZE; + + context.ContextFlags = CONTEXT_CONTROL; + GetThreadContext( ppi->hThread, &context ); + mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, + PAGE_EXECUTE_READWRITE ); + mem32 = (DWORD)(DWORD_PTR)mem; + + union + { + PBYTE pB; + PDWORD pL; + } ip; + ip.pB = code; + + *ip.pB++ = 0x68; // push eip + *ip.pL++ = context.Eip; + *ip.pB++ = 0x9c; // pushf + *ip.pB++ = 0x60; // pusha + *ip.pB++ = 0x68; // push "path\to\ANSI32.dll" + *ip.pL++ = mem32 + CODESIZE; + *ip.pB++ = 0xe8; // call LoadLibraryA + *ip.pL++ = LLA - (mem32 + (ip.pB+4 - code)); + *ip.pB++ = 0x61; // popa + *ip.pB++ = 0x9d; // popf + *ip.pB++ = 0xc3; // ret + + WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); + FlushInstructionCache( ppi->hProcess, mem, len ); + context.Eip = mem32; + SetThreadContext( ppi->hThread, &context ); +} diff --git a/injdll64.c b/injdll64.c new file mode 100644 index 0000000..0a04140 --- /dev/null +++ b/injdll64.c @@ -0,0 +1,98 @@ +/* + Inject code into the target process to load our DLL. The target thread + should be suspended on entry; it remains suspended on exit. + + Initially I used the "stack" method of injection. However, this fails + when DEP is active, since that doesn't allow code to execute in the stack. + To overcome this I used the "CreateRemoteThread" method. However, this + would fail with Wselect, a program to assist batch files. Wselect runs, + but it has no output. As it turns out, removing the suspended flag would + make Wselect work, but it caused problems with everything else. So now I + allocate a section of memory and change the context to run from there. At + first I had an event to signal when the library was loaded, then the memory + was released. However, that wouldn't work with -p and CMD.EXE (4NT v8 + worked fine). Since it's possible the DLL might start a process suspended, + I've decided to simply keep the memory. +*/ + +#define WIN32_LEAN_AND_MEAN +#include + +void InjectDLL64( LPPROCESS_INFORMATION ppi, LPCSTR dll ) +{ + CONTEXT context; + DWORD len; + LPVOID mem; + DWORD64 LLA; + #define CODESIZE 92 + static BYTE code[CODESIZE+MAX_PATH] = { + 0,0,0,0,0,0,0,0, // original rip + 0,0,0,0,0,0,0,0, // LoadLibraryA + 0x9C, // pushfq + 0x50, // push rax + 0x51, // push rcx + 0x52, // push rdx + 0x53, // push rbx + 0x55, // push rbp + 0x56, // push rsi + 0x57, // push rdi + 0x41,0x50, // push r8 + 0x41,0x51, // push r9 + 0x41,0x52, // push r10 + 0x41,0x53, // push r11 + 0x41,0x54, // push r12 + 0x41,0x55, // push r13 + 0x41,0x56, // push r14 + 0x41,0x57, // push r15 + 0x48,0x83,0xEC,0x28, // sub rsp, 40 + 0x48,0x8D,0x0D,41,0,0,0, // lea ecx, "path\to\ANSI.dll" + 0xFF,0x15,-49,-1,-1,-1, // call LoadLibraryA + 0x48,0x83,0xC4,0x28, // add rsp, 40 + 0x41,0x5F, // pop r15 + 0x41,0x5E, // pop r14 + 0x41,0x5D, // pop r13 + 0x41,0x5C, // pop r12 + 0x41,0x5B, // pop r11 + 0x41,0x5A, // pop r10 + 0x41,0x59, // pop r9 + 0x41,0x58, // pop r8 + 0x5F, // pop rdi + 0x5E, // pop rsi + 0x5D, // pop rbp + 0x5B, // pop rbx + 0x5A, // pop rdx + 0x59, // pop rcx + 0x58, // pop rax + 0x9D, // popfq + 0xFF,0x25,-91,-1,-1,-1, // jmp original Rip + 0, // dword alignment for LLA, fwiw + }; + + len = lstrlenA( dll ) + 1; + if (len > MAX_PATH) + return; + CopyMemory( code + CODESIZE, dll, len ); + len += CODESIZE; + + context.ContextFlags = CONTEXT_CONTROL; + GetThreadContext( ppi->hThread, &context ); + mem = VirtualAllocEx( ppi->hProcess, NULL, len, MEM_COMMIT, + PAGE_EXECUTE_READWRITE ); + LLA = (DWORD64)GetProcAddress( GetModuleHandleA( "kernel32.dll" ), + "LoadLibraryA" ); + + union + { + PBYTE pB; + PDWORD64 pL; + } ip; + ip.pB = code; + + *ip.pL++ = context.Rip; + *ip.pL++ = LLA; + + WriteProcessMemory( ppi->hProcess, mem, code, len, NULL ); + FlushInstructionCache( ppi->hProcess, mem, len ); + context.Rip = (DWORD64)mem + 16; + SetThreadContext( ppi->hThread, &context ); +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..d8c4452 --- /dev/null +++ b/makefile @@ -0,0 +1,53 @@ +# Simple makefile for ANSICON. +# Jason Hood, 11 March, 2006. Updated 20 June, 2009. + +# I've used TDM64 (gcc 4.5.0), building the 32-bit version in the x86 directory +# and the 64-bit version in the x64 directory. MinGW32 (gcc 3.4.5) will also +# build the 32-bit version, but will of course fail on the 64-bit. + +CC = gcc +CFLAGS = -O2 -Wall + +x86/%.o: %.c + $(CC) -m32 -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + +x86/%v.o: %.rc + windres -U _WIN64 -F pe-i386 $< $@ + +x64/%.o: %.c + $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ + +x64/%v.o: %.rc + windres $< $@ + +all: ansicon32 ansicon64 + +ansicon32: x86/ansicon.exe x86/ANSI32.dll + +ansicon64: x64/ansicon.exe x64/ANSI64.dll x64/ANSI32.dll x64/ANSI-LLA.exe + +x86/ansicon.exe: x86/ansicon.o x86/injdll32.o x86/ansiconv.o + $(CC) -m32 $+ -s -o $@ + +x86/ANSI32.dll: x86/ANSI.o x86/injdll32.o x86/ansiv.o + $(CC) -m32 $+ -s -o $@ -mdll -Wl,-shared + +x64/ansicon.exe: x64/ansicon.o x64/injdll64.o x64/ansiconv.o + $(CC) $+ -s -o $@ + +x64/ANSI64.dll: x64/ANSI.o x64/injdll64.o x64/injdll32.o x64/ansiv.o + $(CC) $+ -s -o $@ -mdll -Wl,-shared + +x64/ANSI32.dll: x86/ANSI32.dll + cmd /c copy x86\ANSI32.dll x64\ANSI32.dll + +x64/ANSI-LLA.exe: ANSI-LLA.c + $(CC) -m32 $(CFLAGS) $< -s -o $@ + +x86/ansiconv.o: ansicon.rc +x86/ansiv.o: ansi.rc +x64/ansiconv.o: ansicon.rc +x64/ansiv.o: ansi.rc + +clean: + -cmd /c "del x86\*.o x64\*.o" diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..d9ab8b8 --- /dev/null +++ b/readme.txt @@ -0,0 +1,236 @@ + + ANSICON + + Copyright 2005-2010 Jason Hood + + Version 1.30. Freeware + + + =========== + Description + =========== + + ANSICON provides ANSI escape sequences for Windows console programs. It + provides much the same functionality as `ANSI.SYS' does for MS-DOS. + + + ============ + Requirements + ============ + + Windows 2000 Professional and later (it won't work with NT or 9X). + + + ============ + Installation + ============ + + Add x86 (if your OS is 32-bit) or x64 (if 64-bit) to your PATH, or copy + the relevant files to a directory already on the PATH. Alternatively, + use option `-i' (or `-I') to install it permanently, by adding an entry + to CMD.EXE's AutoRun registry value (current user or local machine, + respectively). Uninstall simply involves closing any programs that are + currently using it, running with `-u' (and again with `-U') to remove + the AutoRun entry/ies, then removing the directory from PATH or deleting + the files. No other changes are made. + + --------- + Upgrading + --------- + + Delete ANSI.dll, it has been replaced with ANSI32.dll. + + + ===== + Usage + ===== + + Running ANSICON with no arguments will start a new instance of the com- + mand processor (the program defined by the `ComSpec' environment var- + iable, typically `CMD.EXE'), or display standard input if it is redir- + ected. Passing the option `-p' (case sensitive) will enable the parent + process to recognise escapes (i.e. the command shell used to run ANSI- + CON). Use `-m' to set the current (and default) attribute to grey on + black ("monochrome"), or the attribute following the `m' (please use + `COLOR /?' for attribute values). The option `-e' will echo the command + line - the character after the `e' is ignored, the remainder is display- + ed verbatim; use `-E' to prevent a newline being written. The option + `-t' will display each file (or standard input if none or it is "-"), as + though they are a single file; `-T' will display the file name (in be- + tween "==> " and " <=="), a blank line (or an error message), the file + and another blank line. Anything else will be treated as a program and + its arguments. Eg: `ansicon -m30 -t file.ans' will display `file.ans' + using black on cyan as the default color. + + Once installed, the ANSICON environment variable will be created. This + variable is of the form "WxH (wxh)", where W & H are the width and + height of the buffer and w & h are the width and height of the window. + The variable is updated whenever a program reads it directly (i.e. as + an individual request, not as part of the entire environment block). + For example, "set an" will not update it, but "echo %ansicon%" will. + + + ========= + Sequences + ========= + + The following escape sequences are recognised. + + \e[#A CUU: CUrsor Up + \e[#B CUD: CUrsor Down + \e[#C CUF: CUrsor Forward + \e[#D CUB: CUrsor Backward + \e[#E CNL: Cursor Next Line + \e[#F CPL: Cursor Preceding Line + \e[#G CHA: Cursor Horizontal Absolute + \e[#;#H CUP: CUrsor Position + \e[#;#f HVP: Horizontal and Vertical Position + \e[s SCP: Save Cursor Position + \e[u RCP: Restore Cursor Position + \e[#J ED: Erase Display + \e[#K EL: Erase Line + \e[#L IL: Insert Lines + \e[#M DL: Delete Lines + \e[#@ ICH: Insert CHaracter + \e[#P DCH: Delete CHaracter + \e[#;#;#m SGM: Set Graphics Mode + + `\e' represents the escape character (ASCII 27); `#' represents a + decimal number (optional, in most cases defaulting to 1). Regarding + SGM: bold will set the foreground intensity; underline and blink will + set the background intensity; conceal uses background as foreground. + + I make a distinction between "\e[m" and "\e[0;...m". Both will restore + the original foreground/background colors (and so "0" should be the + first parameter); the former will also restore the original bold and + underline attributes, whilst the latter will explicitly reset them. + + + =========== + Limitations + =========== + + The entire console buffer is used, not just the visible window. + + If running CMD.EXE, its own COLOR will be the initial color. + + + =============== + Version History + =============== + + Legend: + added, - bug-fixed, * changed. + + 1.30 - 7 September, 2010: + + x64 version. + + 1.25 - 22 July, 2010: + - hook LoadLibraryEx (now CScript works); + - fixed -i when AutoRun existed, but was empty; + + support for Windows 7; + + -I (and -U) use HKEY_LOCAL_MACHINE. + + 1.24 - 7 January, 2010: + - fix -t and -e when ANSICON was already running; + + read standard input if redirected with no arguments, if -t has no + files, or if the name is "-" (which also serves as a workaround for + programs that don't get hooked, such as CScript). + + 1.23 - 11 November, 2009: + - restore hooked functions when unloading; + - reverse the "bold" and "underline" settings; + * conceal characters by making foreground color same as background. + + 1.22 - 5 October, 2009: + - hook LoadLibrary to inject into applications started via association. + + 1.21 - 23 September, 2009: + + -i (and -u) option to add (remove) entry to AutoRun value. + + 1.20 - 21 June, 2009: + * use another injection method; + + create ANSICON environment variable; + + -e (and -E) option to echo the command line (without newline); + + -t (and -T) option to type (display) files (with file name). + + 1.15 - 17 May, 2009: + - fix output corruption for long (over 8192 characters) ANSI strings. + + 1.14 - 3 April, 2009: + - fix the test for an empty import section (eg. XCOPY now works). + + 1.13 - 21 & 27 March, 2009: + * use a new injection method (to work with DEP); + * use Unicode. + + 1.12 - 9 March, 2009: + - fix processing child programs (generate a relocatable DLL). + + 1.11 - 28 February, 2009: + - fix processing child programs (only use for console executables). + + 1.10 - 22 February, 2009: + - fix output corruption (buffer overflow in MyConsoleWriteW); + - recognise current screen attributes as current ANSI atrributes; + - ignore Ctrl+C and Ctrl+Break; + + process child programs. + + 1.01 - 12 March, 2006: + * \e[m will restore original color, not set grey on black; + + -m option to set default (and initial) color; + - restore original color on exit; + - disable escape processing when console has disabled processed output; + + \e[5m (blink) is the same as \e[4m (underline); + - do not conceal control characters (0 to 31). + + 1.00 - 23 October, 2005: + + initial release. + + + =============== + Acknowledgments + =============== + + Jean-Louis Morel, for his Perl package Win32::Console::ANSI. It + provided the basis of `ANSI.dll'. + + Sergey Oblomov (hoopoepg), for Console Manager. It provided the basis + of `ansicon.exe'. + + Anton Bassov's article "Process-wide API spying - an ultimate hack" in + "The Code Project". + + Richard Quadling - his persistence in finding bugs has made ANSICON + what it is today. + + Dmitry Menshikov, Marko Bozikovic and Philippe Villiers, for their + assistance in making the 64-bit version a reality. + + + ======= + Contact + ======= + + mailto:jadoxa@yahoo.com.au + http://ansicon.adoxa.cjb.net/ + + Jason Hood + 11 Buckle Street + North Rockhampton + Qld 4701 + Australia + + + ============ + Distribution + ============ + + The original zipfile can be freely distributed, by any means. However, + I would like to be informed if it is placed on a CD-ROM (other than an + archive compilation; permission is granted, I'd just like to know). + Modified versions may be distributed, provided it is indicated as such + in the version text and a source diff is included. + + + ============================== + Jason Hood, 7 September, 2010. diff --git a/wow64.h b/wow64.h new file mode 100644 index 0000000..6b8a1d2 --- /dev/null +++ b/wow64.h @@ -0,0 +1,88 @@ +/* + wow64.h - Definitions for Wow64. + + Mingw64/TDM does not include these Wow64 definitions. +*/ + +#ifndef WOW64_H +#define WOW64_H + +#define WIN32_LEAN_AND_MEAN +#include + +#define WOW64_CONTEXT_i386 0x00010000 + +#define WOW64_CONTEXT_CONTROL (WOW64_CONTEXT_i386 | 0x00000001L) +#define WOW64_CONTEXT_INTEGER (WOW64_CONTEXT_i386 | 0x00000002L) +#define WOW64_CONTEXT_SEGMENTS (WOW64_CONTEXT_i386 | 0x00000004L) +#define WOW64_CONTEXT_FLOATING_POINT (WOW64_CONTEXT_i386 | 0x00000008L) +#define WOW64_CONTEXT_DEBUG_REGISTERS (WOW64_CONTEXT_i386 | 0x00000010L) +#define WOW64_CONTEXT_EXTENDED_REGISTERS (WOW64_CONTEXT_i386 | 0x00000020L) + +#define WOW64_CONTEXT_FULL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS) + +#define WOW64_CONTEXT_ALL (WOW64_CONTEXT_CONTROL | WOW64_CONTEXT_INTEGER | WOW64_CONTEXT_SEGMENTS | \ + WOW64_CONTEXT_FLOATING_POINT | WOW64_CONTEXT_DEBUG_REGISTERS | \ + WOW64_CONTEXT_EXTENDED_REGISTERS) + +#define WOW64_SIZE_OF_80387_REGISTERS 80 + +#define WOW64_MAXIMUM_SUPPORTED_EXTENSION 512 + +typedef struct _WOW64_FLOATING_SAVE_AREA { + DWORD ControlWord; + DWORD StatusWord; + DWORD TagWord; + DWORD ErrorOffset; + DWORD ErrorSelector; + DWORD DataOffset; + DWORD DataSelector; + BYTE RegisterArea[WOW64_SIZE_OF_80387_REGISTERS]; + DWORD Cr0NpxState; +} WOW64_FLOATING_SAVE_AREA; + +typedef WOW64_FLOATING_SAVE_AREA *PWOW64_FLOATING_SAVE_AREA; + +typedef struct _WOW64_CONTEXT { + + DWORD ContextFlags; + + DWORD Dr0; + DWORD Dr1; + DWORD Dr2; + DWORD Dr3; + DWORD Dr6; + DWORD Dr7; + + WOW64_FLOATING_SAVE_AREA FloatSave; + + DWORD SegGs; + DWORD SegFs; + DWORD SegEs; + DWORD SegDs; + + DWORD Edi; + DWORD Esi; + DWORD Ebx; + DWORD Edx; + DWORD Ecx; + DWORD Eax; + + DWORD Ebp; + DWORD Eip; + DWORD SegCs; + DWORD EFlags; + DWORD Esp; + DWORD SegSs; + + BYTE ExtendedRegisters[WOW64_MAXIMUM_SUPPORTED_EXTENSION]; + +} WOW64_CONTEXT; + +typedef WOW64_CONTEXT *PWOW64_CONTEXT; + + +typedef BOOL WINAPI (*TWow64GetThreadContext)( HANDLE hThread, PWOW64_CONTEXT lpContext ); +typedef BOOL WINAPI (*TWow64SetThreadContext)( HANDLE hThread, CONST WOW64_CONTEXT *lpContext ); + +#endif diff --git a/x64/ANSI-LLA.exe b/x64/ANSI-LLA.exe new file mode 100644 index 0000000000000000000000000000000000000000..42afd668d89409ca90db94b7b84ba8a9c0f43544 GIT binary patch literal 10240 zcmeHNeQ;CPmA{guMSzWz#41h$;Za0m(v-FNXd#PtWv~deCKzl}Cd8G6^=uUM;eC2C zn2D)bS#4im)OB0Z$z;+ES~EM@-OMiRriuBOpv6ptH(y?d1{y=+nWa0=u{U)S54b?1 z{hj-sjDRG6u(O@nOy10S_ug~PJzw|SbMLdf;gN1uz!95c4HC4Ypzivk$Dzr+G7ReV9ntb7KL>9J-f)oo2fv`}uu~rC0{Nm!Gq8Tnj zbd86xYST0}bT(0)(>BcP1v5?NVpa@{1Mnw|4SdPyml2m+$D4J#O~8=-{OQIUgD<_# zxL=G3Ixh(#S$K>_A2c#{+NyVN>)pv%$!$zrEQ`Bk%sYJ-UStsYI^_iVcm!6{xY+NL zedy02L>T}+->lHh%F>G^ow1kbZUD*pG9d2(pL_B4Xp~TeM6bys=5;$+V|sBwm&aq% z2qM;i-J8dRF4wzea~)xq(W@0e`du`*{%}4y$n^?@?gS9uR{?ns@#PJ_=>8ERP_O)^ zp)VNp0%Wjr+c0xjVI%B0d#L?f^O<_`X*OPH7MT~Pn+y7Iy$cido|_N_(!JBD_^l!y*jGVdQQ>4XXzS@{m%YU{h3Ri0aMQz z1f+KcIbl7IE?;EN$)0oQr+ChL_9ra~Dx$DX`@jx6>i4S^v_BM2cX>K7xYtuY)ZDFF zo+XA~fg#yyQkJ>2lbp9(u{^C7KO65eDYDB37NyQ*(f$Usl@0s9*AHPy%af1}vB*h^ zCQHe1PjQk8)kzytain;Ig-vnAh?nZwPx_n%n7N#9n@c?|@Y=g(B8%0zw3LaV*bMF4 zP?aJ|Qk>ZKT`!6N-?e;+k5R(YIwwB*LpF#uV#U{=s#mQ~oZ8&LRdVDMPLz29+ z;^)+qRs4*a=Tfu-$htwyQyOggoa`{-TY;R^7)w(UkU6TruhvC6{KDUAEXfBW+|kZd#@|e9>6@VWaJ*2be+nj z&YGhB;bSU#BbU(ddX~q@qj#|oLA%5y*}De7rFbT^ho&J_Jrg5ojBj0^_?RtYr7Fm? z;%ngTANkdlT)m{z#-PIurp@H^UlN_3WVOo@zc`-#%WGKaP4Tf}ZI51> zr+u!ykE(K=2lJ6`#j5i;%%-E@U|OR?kIDOc4u(4U`||D5}%ICI)+k z!DR3HR}xV~6Hske!r|q*eWW+v8r9>-Ei-(#k?-*PL&SOHAxai2iK8cDC1LdNGEl5< z(~OlA(~}dbt04?Id#tP#C-7Bz!!4wUS^!(jIA;1RIy&~>xtV%!Zq+yXw6P!f`Py)O zQTck>QoCVFe|DI!FYE_Hj$iqWJ~q-oUq4rhZXKu@O-4TSFhjWt|5oo~K+&pK=Z;z5e9#m0S835Ydl>x;s4;fcm2cC;_mER=`iy79TOi ze{bD2O)X9yuXM8u?ve|Sx`YdE2bz)#ZW|hh>b6jF)0!Wm$+!-to+908HyJt~cF~QC zN9+^gP5ICHA;5MI7CG5aNV1G{J_EHRi7uJ%qGnJ!J&LUaj(jbzf~38Hm$6X!_t~w9 zex+doafnSSZz~!NjZIFkz5`UXKIxXvtDZB-`ip8&BSauFg-U&z#Hyat%7){l$W4k+TmFNj zjyW*N{-h@Z`Dk9r*N`L)_36%zSD3otxV>vFR;;rlgT!Bjp6a>C#X}-g=2URGIjbO@ z9HfX*Ovyte%{XGJ?W3B^^I?VRtizV07$HI(*Kk?BF+N9oSA#t8b!y_1(=t|Kr}f!; zEv8I zd%O~%vA z5sDAeK%*-A~qKX0yY>r%yKdCOh#@PE0FWjw zcAQz-ac*hH`SQz{QvTl3jsazIUzXHg9*|3v3Nq_fwY`V#K2ddIBe+J-^L#B;J(gs> zMRB?|;W)FpQAej+SUO{^LXX1UQvyOyHAbiw2n~7GQwW^yxMw`YUGhxmpCT{@OkJz# z2Jk5CxeOrq~3WaL+>$%*Jg;zeDzw4~?3YzT_H{TAbi>2V>)-8Fe{bbin?^YWPfFhlz9# zDUk0Wm;V`9Zj|xS)pG!((I>$F6z5N*H*@aTAW#Pl)D|6eB8TF)Zrw>*l1-dEdf*f9 zbE$(+K!8d`fKAjo&ZO{sIVDrvnbEb6-QByc?4L zRIP9ks(?GT6H*4>(g~n4q&wxxwS%%*A6+o~@c!o{P;GU8~ww{#q84lj>@k{ky3N zhTn!Xb)8N57QZy?fkQVljs6<5d${ofqwg37>VN;?fBm70?W9RwE!}}Da?GlI0}IQq zX#IWK{pj<>1V`-sjFlou&yy>n+X)iV_;$QvY@ynoKh2IkiClW~X|57$x_10`nGR&;N03TeH zQcEl(hGoGY3HiXuWeBBJ8#h$0e|XV#auhbkf<7rAw+p4U>mLG*ZEO>z=3r!(Ao;>A zVs7fjEuPJ5t2cgU5v$vQpS8qjEGP?ss31ooLNw$H2GQd`c!{kbLbd1@O6wo4%FSY| zE+YGaLNF4I3Xx_Z8fXazngjTytVt9~>#8@_)*0vm2S2I z&HgMO;`lKegya_;2FR{z&`IDb+Lzr3_6)@JiKHMCsWZO8VJt~W#I@Get=-NO~q#lbBr7qC4b4>(wj%;IA z$JCx%d^QzSi@vs7(5#@~#sjORfEUIZRR4$+qPon5grp50AiD@7C5 zAeOAcQX3S-R<_RTu{l12NpjJm?3TBH=cUVJv_#9pt{&R>lNV>hPNtZ=hK(PZys`gPBqP`$w@0z2s6codZ{odS` z6G9ui@&69K?b-aWr`ovtKus}p{jq@G*Ni(FRNwaFaJZ-DHlpvi`}&yfU~a${&*w7& ZcXztk96%ZFUqb5_>-_m#Lp$tCsd^Z;;-vX`gMQMw4o62;@TIvkm!EMUzHe#C9go8a0hn)6Dy=ea;L6 ziD~b<{l0hayAI!;ea_lzuYY^(wb$O~Fva(@vslKM9wiiFY&UB9)!wxPDk+~{_#cURS$tE(CsT#|X6!|e7nm}?u%OYST+*Sl&Q3unxj zo~)5Bu`^bro6f%Wwy!A6ZHUdh^$J}~0$T{kEhq-YhOR~(kCKnVI$ZoZMwMF!2+7Ys z1Ng?xuZJ1ei!rm>k2+9)e0bH!*fP3lVQg?3>K%+_ks&63x*0nbAwIbg_uCjtx&-)d zffh=R%@UBd&c_!CM7mDmV`e3l3u~&RDm1MjCG%0BGqa#Xeezjl_d>UdI7Bo`P^O@y zMIqr5c=-$0H8l}bqV`I|!s}jG zxU!6(q%LbmAw8DT&G-lOx)(}yTs|{_x1kUo7Xr2R;dt&QcQx+$nC-v?>3zF~pj9fK zqs|3HK8D?BAiY;;2wDZaf{Bp-K6mBv2c_Q8gnoT;3zQE0@oS99y|O+z@X0NV`HpuL zIXhfk%kZ_E98fa!^Eo1Pd-o^YThZ%mNB>nI5U-RQeMdYVC6QHjxWsPXk%ub1r*+B= z_dU|#k~wfB-Y))RHKm4H(q3qtt1J;yrBaOWM1F}3xn ztv8_xtVAmp#_t%r_Zt;GM&|uY#{r%{)^yTbRMLs7vPM6W*lry(wJitnFyFVL_{#Ss z>%%Vm)RjP=X?WBJv8IJ2_AZA^PJ88ys;(r1%wt;I(SM{y4H50Rl781N^ylR9mTy(1Ke%zP-Z*3m8xZ zrW18g(yK5=uyb>X50N7!gP&apgl^E-M>>F9^N?U6-E$W`rpdb543mDSGuLq#L+?9iYWp`d z_(J{mVbsiXLYb0NXq4h|3gbP;l|l$4+lQsYetS2Dz%UyqT#U5jP@+V!#eU3RKI9$x zo%evAhN>(%GF*DXCA>!9Auxs#hg&;b{_Z$AMk z!Op1IhsXgmisTO}RcHXhP{Nl3C-U~`m07+cCf^j?w0rG46UZ)WU|*B(84Mz~d+=^t zQT%Cb-a$5&u2Bl(gR{^BV<&d=(c|-3DKrK{z-^D7ze$SSD0|K;o_M)pj0}OHh3EzZ ztx#ez7oDbOuQVek?yLjU820sbPTfCOIo z>9u_^J(hLU)z}qOKLm(Qdz_*ie(d!*Gh~3`LLz3iwx%=}_sNYB5Dd?m{ z+Q7ZQ0~S0mU-6wlV@}+88p@kAEw4cgksriV)F;zo7x)AyK)x;9|JHDSPd8?3C~-UF z;I4-SH!40v+91w^hH?8~z34Pn(h~bn2049a0CM=t&xR)u%{H1BL)F}ULWwOfVt*WU zp;5qS9qgSL{|uZS#Vh$qM%}#Kx?F5_SQmAAIObsQ!I4K*|HOLLI(h(2etQquk29ym z3riOkF7kmPXlB|KHw3A?fqdA^=^aZqJ@!3xGVSUPCHq)OhpTfG%@o3}p9+!2be?Z_ zGAyIGLLiz>Afz=ru<<~YsxJ>;|w;I7Y9yl>x7eDKHU zj`m3Z(s2Jnh<+H}@%}yN-xukB0R5qpD#QC2nFxiC;xq~ToB|gf_ABmFrH6^2L8R+; zbdBc!PvPEoPUasiKbL)E+`Dqay{;YS36>FqLDP}N5ed&?zH=LmAmV8{dIL0iJx*&6 zRHShJ@D0uZ zL?+gN|X)n~=Yu}G~*Ebh_UEcHQQPVGabiKic z(HbF{A11j6P;EUVTfqkpz|#lef85`p4BQ*u4p)=y?>b|-^Qh31fW848$k%~%3hqy? z9~`6Mr~N=UPP~L^$mPEi%oIMcsGVDcMrH3l)83Egp0N%#$90i*@&M+Y#tMU^$piL~ zt_L&hq$(Q)OIR6lK|cE%s89LrLy#tZWb*|6SBLo@LSX0is8|hw>9xB4m*r{Pw|!M}^l;KCnypz$OHXAcAIZ zEerZMZz6UOs}$9uRWMPi;^G(lBp$Y7OqhBO?o(?f%A7 z-V0MV#rij&@;45811Y#OEj{4v(OHk0zVjUzRnb4i$LpkbZ1$!BEDwWG%Y(1#Gs^1w zx_Brq!wn4hD0V3?YWm|AdDBQ2>aD2TQFoyJCqmP)5rx`Od#SyJ1?wRgp%}EwcA9dp zlkl_)+VwICPF=a{g0RW{7nA)X4DusRxfxb!Zga|>tw9lJTE523P1fC^8@F`u157e? z{}*ZJ8^b7+0-!kC)!~{rPQhK{^J#oIqq>fcMaKV@L%9d zmlR?O8lvTUb&`-T(_lo&KS6&78Yk&Lxqr;%`v2=+F6uvlzWw6%$@GU|q-gmjO@?3r zZ-40Pd@cu1k;xEGoU%4G?3f5N;xr39k2+;-s^rrAadxSbMVlz;e3E!`sbbPfr#F`- z5caT7z`&Ry>RfBPYJNVtUeR{dY}9g3%NtvPN#x*Vh?(7yInU>Mkru=&KZP0^Md_PZ zA5?0IG;>Y6+Sp{GEm1_}9`75-zH&)gVlwi5theEwmlP+A8LT4@=kZk&KaQ(>MI6^( zQ$iy~I*}Mb+OuQb=s|vF+EsCYaxY3m%`gL$QxnyF?^E7p)C#P^Dxr%c*w?+WRlJFi$Qe(MUFTw7eX^pisZOj zCrTta66KD(o62wE()-gttCk?1#RtF>i)de%ujv>2!e;T`7o)qFH&N8s1q+Finr^)c zS(%y>^aK}>!)~PWBYxU28#}O^INAwde=nK3=&C<%KY}w&TVF_8s(6g@=OZ^D=aBC5 zj>Sq3c*pd}U)#2!-8-U_=6L&JI;jIzSKduxB@x?Pw8FmQQ8zl$QY0nWqs0Ct63lSE>K&W;umPUfMUwXGeN00S zJ_)3N_2aM@Sgu*HMeP{9zgvb4l~Q5w;~)-)cNn+k^B7ffMZP^8iV0VhM`U{M{6ftRoxMG#din*Zgyi_Dn4Ej{V?4+B#!!bFB1y7w?E6DX|EyrQmrhNgj! zzj)a1BV6xj63~q#7R_K)^uX(wMM`mkQa-G^a_&jblR-M2R9ALC9i^)0vy;Xno{t9) z4VN1aBiBDX9u(R1p~N=G6frZ+b8S3m058!Y@F2Y7VthE-xW8a%#N(}_&tT5sxDT;r zBsR9oxrVba} zZM(Czd28`Ko^3$(Kh*}&V(t6Gy(nWxHqA{txu>b^e%Q73b?QQgpC;cuxOaMAA0ir_ z+nueiZ^c!S&a((-Gevjdq&kt&=y4`@K_~+~0Ree6%$|q4_*JU>Ms_#5)Hj&@_P5utEKU2WQ17;oBJYDB!7 zJ3uA@U?u?5!vM4v#-PDy+8gVCb{z&DvxX+>28=VA3rD~h2l7x1ej6YB5m8jf_Yr+Q=yX0s+j2UDrc*ea zv+>M@=d1jAOo|$Rqk}K_=t(p_;oDW|a5QBEA7US%XA7NY$*?$H$3d6}F>*|zl%Qi7s&gMHyIS3g5A;Ssp<(n84o+y)9~$6Q=O(? zefM!Z_=7cJBd4rw7kTz5_c-^R)<+?sS=~bNWE9PFTA25g0bq4I$#<#FF20-Oxptrp zUa5J57M>%|Z+QMQA9D`pyU1OnBBdf8j+w|eTv%k}K{~#t6Lzd;P~i{iP7DKzkU|gU z@{zj+js}TA6!Olm(7<+57?N}8s27;e&pe6V$NJ_sOlz#Qd%{v^cmZ`PqpYlze~J+>I%olB6I z&^lnljo&{2`dBQ9i~kIEf3&oE`{@XkL=9Xy14vKlv0{?cc!1RJqZ=L5G5M*pgij&W zsQNBch|eb#XntbNCR$;&lvs{g2TX-C>hlrn4Sl}fqI#y!_ZF(kJ|8_q1@Hu_fZV6} z=$MI?%%WsNpYMMITGi(}h3Z+^cNmp_X@cw<#06KY&-Xqqdr1y5CHUi<=gPTfWZ&-q zC!I7gv`0%MU=(x!CRS9fURL?iQ!LV&%U-5xsoB351GNpluP$0JGHLoV05~qW1V*@v zj2(O$%)?_vHO33!J()lDron=pWq#5WlI>@u%^-|(LKb-T3XE3x(f9@+pf&Pbd=1?r z&#<5I+s{xy_1n*)(g$4VCF8*SERih$7Jpqq0TS*lxYx+@7Z?J|3474HKN&AB@G!TV z1mB;G=hA}!Mb7_`NvSJaU=d>ZK}@-8yL-br@%Ty4!{Fmr(>7x!UrXp2Dvz&tCxApF z0+cI2jvwHVe;h}(M}m^rP2OQQdb49Uir(C!#dyRnO~*5Rx`0La^MM$+E!dmdegFuk z!5=6Yk(XD(*k~mU_wbR4JW1*@S0Ze+okOE?1C0$0)xHwhwrrk*T$=JhiXW}^$^Nm!Sis<{O4gnZ+AX4HwOL$ok@Ky z1LwV{4Q_B8O@Wj6(in~Bx8Bh;zJn9rk2vI+5lM=%y!NWe?)Ya+*s;yoBjLp}&X z=+I%JIvS5orl;)vppS~S`Qe>a;CDD)SIWCvn)|SG+GA>4it%n~?q>+OxWelSoIH1m zV__DYZdfW%Ck4r36GHC3l#vLQ;XV-TLlc0&m;_8{+h;9Rcu{v^MF^DDkmZ zdwUIX`4GzE!2BIi&tKtJE_3?nOXXl_cMcZjrMK=g=Lte1ziIT z$P!GiWPRG-0*cbjt%vZ=W@zrA)(@$#KP^-C43B)(vJQi-4YM(b%Jr~6ngALh0oq%t5uh25k9-l=+whBvqwMu7|v zBFspY;g=M_s29CIGjf0i9vT#p2G3)S3&&Eb`S>PvE<@N;ki1COga-JHzeKO(>*Yjh zz?cO)vnuIAn%kMCUYPsi2n0szW|)FWe;hRu2w~-5FW=vX-%p!CP09_MZ6I?{%A;rD zw<3!&h7z777>orRkFxUK;14Y2@8;Ld zH;-%^nGtP1)qaLf0SqW89_;OpqigDMP<@eO0(yHnkCX_3v|xqpzpS$aW{ym++!epi ze+^HzK!a&cC|&dEK*aUC$_Sgn61M4hm_(WyAS|Z4(mW*FkHH@5iwJUGC~*`6Blm_9 z<0;tK2k7Asb;!bPYV)zw=E2nF6XK`nBEFZ}JRpy?hKT*A15%QlOJZHjcK42J_b%Cc zC$L6NaQ{hB?0SDWyhUF{CNtxZZ<-!^q_|}5e6VW_HtCv=J49y~m^AAe1x!`kex{SF zWIr29Tm!iv43GwCU=lZ|_v&jrG`K{WYf#Bs41sCbmqm;WdDQdD&K7Wk=gti0P}=hp z>;TaaO56{#C^tNVYx17s(rrra7=hH>u}0+z`@KyO3opAFC^f^DZa}#g+fvWl5KtwQ zq@sq|TkM7Wea62ta5f)|uxtb?BEBTE&7^&QD9 z_b3I_`=iNvYt)ow2KjcLZ)HQF$w(vbpm!G+eqiKXO~CQ$T_ZLt~=1+vpLiT*-YvZsM;4B2J z@tTRS9Kg(zV9Nm8Hfti@YQl?xk#0`F*jLB#2qpp6_?0La_&!2-DdR9z!OMS>|I()o zIRs@rw06CK<>~Uh{`0g>xeluoN;e9-zQa|3(t`2|$}q~;XLq_te^0$5s?T;@ zQ=xv_Q2qzX;uWRKQ!NW?>gpIPWOw4o z>w|mHDZFz9d%)ptaMWoO)thxyO%9cT=0b~0mM?lXH{*T$L+|T z&dlajwQk8%Rd<)i;oia<95rUA%Wa-hV_vsKax|H%By&|wjT_!#p2HUdv$?$Cfd<#6 z26JPR!&BojyXk+gs-#*MAa0lBs&>_xH#*!+1ejB^a5}WST%UEdlDVn&TaGYpebdHj zcb2Lyzs`)hmbkCixf<5rUk_<$50H-pKVrx)FTT(M;s3p~z;2YVhD!?W=c5QH+ZG@L zMXg3BEha6gt1GUmZO9iV{X^mxa{992Kbr+=TE-=+lo{LJivKl534bKOo_U0^$|%?# zz4p`1q1;}ysxloXVu zDCQtg4{~tF2c{ z259MB%{nHg28$W{=ZTQj^e^IS`cUR_)opOpHKtlh8>$+cHn=4C7F*(|b6}CjN?%)D zRafe$rge`UW=m?DD5+30DlK^^tg6GmIlss34U)q>xeH6uL8dw_UAWUDHF~7QlH{&k z=aC$kK^M0cZm4osRfE)Owp_Qg&eODEc|()r_HYpjtEx9RSgCHQ+u_jIvuAb4d^85d z_>X)%e2appEDK9;MivDvtp?y1j@^%L{`7be`V zW-qGuVAc#;Qs%%MANZ>PI;+) zq_mcP$|G$<6uHDw*nvD{Vy>b%Xu=uYf{g=}IBP`~iO!N*M_9&Nt<7p`Wd7d>p@Ec7r z75M3&J2~>}`VTJ<-&oFpezS=kJH#2!J!a1_4soDUt;uwLmN;_c&3rfZo^*{;7y=e4ZH^YG*cB$ zZ(TUQt-^IRyPvH?-GJT_a2d;BS43zw^1fT3VHKy3X8bHs6{ryF;ACg~u3fK(v z3(LL)^qN4ObZP|UTHb^3!so+gE;mW$;@VQ%JUp8jr?Cn&++1?vMjsK9rgF{cC#I$X zmuw_)yavcs7bR(!Ppb9u;d*W{`UxjD+8t~YFzAXtdE6d% zYIrn4#3!sL-I3+T^&)-?VUs%i(MP}!e(CUx$;~b0flZ_+DaZS%XY8aG*Keg`4^|M9_uH)6_}g*n1qLZ#poo)DfD z-V$QPtHm^NjrdKmO?*+@BOVfeD}F3q5aTRYS+2DtTkf!Y%hGCj!!nZkaM}}Tmh^kl ztJ5D#-;w@FdO^lr84qUkWE{>opK)7eUS>(=!Lb@waj|A^#SY8tgl#qYdvW_YyGQrYW8*6S=o!SAI<(@_P*?Q zvUNGv<`m}Ko70-}{hVDnhjKp1`D@NRTdHlDt=8tY{n)nCw$JvqEg^SRZfkBw?#sFR za&>vvMTafEXyqx!Q!yEEDu_ImhV}9WcjIOpXIlf_be%?1*zXieKqy3 zsZXbEPwP+nP1@UOAEb?@olA>NpPpWjen)ykdULvx{u9{bRQjw8A>)pW2QnUmCAMY! zB;&=5JsAfx4rQFqIFq5zjL)2(nU%RH^N!5&%x`2mGhLZmG9Sr&JoBl{f6na8{GXZq znMX6HW~FB>%ep6PUDo|sTe5zb^?cTEvW{h)gx8K{eV)av2J4m9>#a9i=UH>C_gI^( zTdWUTAGdzby2sjM4OnMq&&$3oJ3YG~ds+5|Y;U%l{Y3W8>{sE(?`4l>e>KOFlM8RY z3;x`cvn8i5C&o72c9rcq+byHSiVVO`StP$>mZR>imnZIU*wYJ9RYgDXD-By3VXKnCVowLiVD;n$E6EiYK+63#; zBF5@&9?1^9-0m`3JH{rSJ1Qk*EV~pKA{@imrc(ejiNfloZ=O!k<)*B}kfG-=KIqKm zFB$I_=z+k)>n0@&87tfTSOlvJ3MY$bkfogddxD+)>7auN}eg)FadVuMb{b=UzU#*SU}6}s@m8FR}C zTSZ@;_z*vy0TfRmD-2Imyj(us)Fu=V-2#I`hkAdT{k6#QF$*I>{a%Dm(gXRR7q_<& zgsp_{#fSJ+Bd-yZaJfV&2$x%M9vcjeIl*#Hd#I=2Y;K!O}{SwCP;r9JM z@dV#B*~6-(9fym=rr?xGxB2V1K)Bl&1q&b6XCzpbO$s5A%wfQ4!SwZ`k9S^hacn zUL}Vtdv5_gl<{YPi1bU~sVmV62!_I6>4j?nqBDSsNY{aaEU-Nu7)c`iR9Z-kHEAp? zki^O;R*!8b3RyiE?+r>IE>dLQ-P$0jq@_n53@WP(Ms*2WoNK$T+PK4}1M zHsRDBIrx6Mod0plDY7<<+W3GF@m`=-zK3R_o4`npNROimJxFdB$%qsdDP7n{kQ*wD zCUpY4llt8rHn&Aj#N)Ar@p#;0p5P?vqjLLx)naoXtuC@zW%UMIp8A5UdTrBWRk3Bu z>hmtO&Q|DBpA6J!(!#|Uq2rJ;8zT}e$zrTnjjFH6>Sxj3v#1HR$K3V+8Vi1rs$AE8 zNLisCi%4CCw4BA`vPMm4#8yK)vW8Y%8n{Dhx!XK}g=WNq{ic=Uq8~#y!7oe>^KH8W zcSqmJhA!8mJUS{zZ$x=8(eAi^#^cm(;OV|!CXlHdsg$=LH@97nmJ~}8^~~d6@|t?412tn=ptT>5ozJTma{a-iBT}PqZb}AqC3Zj5 zO!Gx8M=wX0A2M@W24SI%Jgq5HPF2Vht^oQtZ}J8hAq+*9wGF)Ia_~p#EO1`zX_V^rtDP$6 zmt~sUj_}61vpI#i?RUr)wZAP#^o}9N0x!_~x@^Wcg}$wUeX=$q6Z1`0bs8n?*h=om zrG6x11lA@dfFHdt7)08R%cTel?fY;oH#*6Hr?~2YsQ+)@q z;w09alkhJY%-{cQ{O1qwe;?CtF#j@I#Nw6_13b@!%x4qN??bln19ks|y3_%ERW$5T z)PMAbM4zet>$Rf(Yts^a^PjZ-JoUv&ctk!g>(k)WEXRL@#fS!9-4%TT_5(daIl(XD zibux$vBKiXNTz`pwh_G#B(RIi%@g$TfD4f2%QCzq!8jz!C}DPxA$=Zw{|oYLScl1| z4n`Y!-M}+Q$1NwWHx}15yGauA1sESHdJ9GH5QMddIex$%;P0K9^oL5C9^s!{lrUgd z$Yq1;u>7SOb+wEgOQ~hE6N!Ne^b#JW3?5Pb0!={2b=snZb z(bHAQfx;VyqjGvT)^C7VOOidVIeLItYZ+IeMOocoBfloAn{Ay0L*=$oS>0iKkRuU^;bz+&4tLmk0ppf3^xwQiI#z=vBwFYR_D?bYl(P+4Hq4}=uw0vV z6Z~Xm{7p?d2r^n3?HEL?J%Q^q={VKwH<{Zm7)1$eYW9xgK5L$N=OWuR=ABMkd9d4* z(=CTK^J(qGFqS*i1KX_-0G;_U7|;xZsot}iS}eC!@jPfaEokP0CT?j#F?lyGpC-Dk zi5_dB$C~Id@?kXK0v+!$=%~jS>jMC^ernQ4-krchnq~JqF3X42)XpW)N#FVoO?nqu z>h4XVyEkc5hNFtGN0PMMyv1cJHSgSI-g(f}lmGFJ={eo%_%~6{yk-0$fMCpI-r}@X zc(58Yd4!9#l2h>H95lV+!BXS*V7X9;QI*rY^huD1KQ*m(dALs*=}MGFSO3q zN)Xgm=%lEcWDP;^rSDwTVBAK`JcKt1L#M5AU9SGx=Evvxt6jarudcm>P;P@rnDD1Tr zcgcJJ(=@pc5{Ys*LKF-hb5%5Fw3r_bBaC)Z&oE#w5r1gSFnZe@%U8YBX4@O}u zP!iYAF7vD|^bYM6_wo+*EzqPYQJc=jeAd0lO*6&(^S9uGG1SXJ0R2=0*q@>!i&@_1 zA-*!S1XB&o$51{^$+_l|7kIAAJgXaHYTmLRLVvJWAJ$1E@z23?V7)^BQAqutD_U>& zfk2Z!2AT^K<8}lW@FOHZ*glIG7~^5yS&mVHKbT9Np**LT5T&2KVE*GSQ+Mn&_-{>G zC0f{&Dl|Nrx~2Pnt{SchV=8#CS~NlR$&W7Ovv=3U%!!AdCL76_G)AN|M1mHPitijdAnLT7voH2ewYj2TQOy>piX z9wFyI{+=HXQ>FbNaH@MesnQ;#xKwCWNYCf_Ko>|T+A?Svb)eN1@Bd<)655 zqoN)WX&fVA76+^aBLc~D7VM^{@4Cq-+jjXpkupM1k5jBau`2;4()48 z*x%!%iP&|C{i|a~DWLzA(NU+_ z`IbFye=m0K!2Tj4od4->21U4c) zEK;#62j*`?>H(IXh_ucqfBDnI{EkQuB+9);c?2^|%>RgVv%p5A)fV(fJ}3`(KzE$u~1mG+Ly%RmG-@^79hqN2><<&qqt|f1-TvKzU%G{D{D!{{!XH zKzUE1eBD6#?fnV;K9DH)ijz?7CcEHSnxOiQ|2S}D5cD8chUD2X*2DOLYCJq;?u1M zz`SMbbkC@<4z+jmT98i$H)bxvDn%;@?U&=0FW{GBvyVfr#CZplRlJE7%;6=RskD}J z%lHNAfN3etYaU?0TL$k|)Hr|=;;r@u_oO4=Bl3N>c0jcG$U(H}r~^-MA!jy7v|kDG zST))CmW(JOw`jz~DJ|Ed)HBKw;9#tb@^sEn`Ve6U5+atvt1YrsBzCW~fR%2yLMrsN zs{%m9-nHwkOa~n&UiFc?Arii(0LP0#x*nu)%Vw0vru~Ct0aH#{Zm>3(dXt&rfWR3C z4aVGKS0ynn0J#HXaZ4pJKlBNhV|~RU3n^p~k+2^(xQ`)n^+SqXSq8|8<<_OG}=xi>}j4$eHKw4C*r=rquDqQP6EC1 zJz34T0Evk7eg0PNRrCP2fe{KSvLJ7>}!q>8zKX2x)c?$Y9_DL;KA{{c^cBqkx;X#RC9tPA%UdzG0`!iA&)A3 zT81dwy0%?GvDNP+t|4-U12s9@1VSZK3w3@b=-)se}uB6e)VJ1|(89 zfFE@tcDK$?lYWh|VE>pKN5EWFX&rKyyniNeMXsnm($jVVp0g20<8=`?zFi~0mSLsY#m^y#|gy7 z>2WU*!QE90uSps|5lOfmi-!W&g~NL*ARAj-bZK<87R;DX^Tx z-^A<19O^LyK3wYGL?@1+%XB;@wla>rTml?>xdb>4lLRgtkEwYV^1MyZamhhPZR2>n z768ZNLQMUM)ckRW8i^D!dh~GUU9J%J6`qbYC+N2CFZ5Nc_M2zE?o>as?}ulin3)Sf zackUiKRCG5cT2cSfv8d14`{dPy_v@l3^%=;^Lk)&YzCPWe-iq|tgff{8=@Ktg#-}G zxxrS=M;arq4>rW+gQ*Yt%7&8vx9RkSrB+suD#8+j#?r2jzB9VQ}qfIMn{BL)cWa~8*GIX_%^q% z=AL1PZ37xe4O~VWcc-VoWm1|w1$8%L#)L9nn?fr=V?7P#n}}vQwx)q|+%nI=d>B1} zsb10^S58IbxL6%7Yctb})hiLuo|y@l2@57|qy8rW5QZG?F2E`*W12_(jIc>7h>y5h&MWFoL}V72Clv$`%{js zKo4EH-19ThE#k06nka^-@Y&u4i{1HZz$K$Q! z53+?n$b&0zsu75L9a@Q7PJw8+Fd1%tD*7enutV(^(^``{h~JP)wcze)@xx-h`4;}? zaQ+Wr00jRHL-=QZ3;&9*DCX9S86zXD=Y#SMDk`cY6+u7H5>#{#vLpmW5ai(~fws;7 zj;5{*RroMUAPoL(xX1j&=#d^Z+II3zqkSg=8fEqOZQh{GObp|{$%3B(anE{FW!rIxSDYtM8JjHky?OI#;@!c z@fUHwwgA-VFE0BFi2#$+DTk(9PFSs=3&edRv7f=d^c)o9MqV2ut24);DkRE~wTg7o zDlY10T5-LEt41Io$Ma~Se*&E>qjrd(_Cznmi1S;6*OtQ5&`nZD(u1`iTOvU=*wQ)N zY|G>j3)c?YIG%jiX65iDS=|@6_%LQ_#2LEMB9@YHQJ(v<`cgHydu*?>zVHj{Q zvN-yhg}QS!WPKOIUTwm<8@GG{K^)t}5{#=?Z4}C^g9}jKk0K zu%ZVk4rY3Q6yBjg5jHqm&|6;Dp+iUdq#sh$n5H z`Z?-FQfZW<9=72PDhL35o{dHw>TX9!v6?usg z0AaZy&iiQxRY+qn4YkocO>N}9;zX%5Zn?0UT!Oj15`$p)GpL^{66)uQg!)NkGSttt zsp;CJy&zZNn%$)TB!8}V0==SrM`-p~SEvqw$Q~Td-;K^Xg*aWEjs+M}VF5Nl3OiJW zw>}0LqHdmXOAa@aC)Hh8Q2D+jH2ZtS>W46)o;I$mdcZ6wZ3A{r=KzMLj|JvDq^i*% zlIJ5yL>B=@L6i-sNl#wM7?5^kY5*?=yc=*5U^gkuN(wUd=j?&K=1{dGz0XM*mC@TulU8 z#@k49n&_p3z}7e01p~D5%`0H^8D7UlYh-uyJYLB~#nPXMivZtj@7%dydJ=dQf7+(VWG;7UWu+lNYm+Ilx7=Kmv@NDXA1 ztPjzJm@5N#eSef4EI8$CoU4_Q5otIjFj&FQQ^zpzNRr$LyjNRD=aCJ6g_(ba&WTT~o(_dG?vPerNiWOVEi(X>8!PZ1e<|-U9$m6wwC!G=30wRtg2@a^@ zFyH>(-#f!!!)syorN;8FX9emdmh@0E~lwD zaQ=q9RpEYq3j8A}5T>d?9%!$Sv?t@vo?gZ(Rn8|pVU|%T^i)WClIcyVQVf07fPEq^ zcqf{JncWHykq%sk}3mz?towl>3m@@IUH`bmqH+XRaC_X4A}f@>)iSLV|Db_Zaa z709m)$)ViE5$eGWwu=n?X;-J=G((Dmc1yKHdqX!zzD`hZs1dKoA) zD*P6BzPCPYBQdUr&+ z%&0>z&%BaYYiW>(-dKKU2|&JH-g@!9Z{*%d69+B?u3`N8(>=Te}!G|m?V1H_v}FVJXw8*!dTF{9cNoQh~nP+43b70 ze;V{Chvr53=Ja6n!BNPzhl|xNbKCtiB%z|ivNkUYPj7#Uyu>up3oVKWz!qWsaANln zk`D5-2JJ~2iGe1C(GV)AQ+|jNpJR4$JsF6YfrzdK3;3YlMCr}*;GTGl zUPRKncMXaNvx6|aJ(s}z9vIBj9q&NYciD){?bUG@Hu7>Jxw$-F2 z*q@B0^1_n^7HH;S!AAx3wJLGGfe+pXTe{swmUtl;(9lPZKw?>aQg;+s;va!)yA=*r zR--&bi?uiv2I>rD?Z86kQ2&7~8J+mlH)QoSN6shqLyuZ{{^h>ho+36vD; zY3gaIrRi$anG=I|)c>)9t$4pc&KlRHr>l+l37am(x z1Vb4kh$Vbw9{Eb_#zfSILU4sJ`lHh{sb?xkAitG{K3k^O>*+1~=SY4`C3ssaallGT zFU2E%ml)6O<1l{wg@mYy+eJjTZ|u(YpzRPCtCz^?5=5V~=8zoHz0_7HqnS5Za2p3# zX2va#p%@eC*1_vdnf`Afn)E*)gg9nnW*g^=P(}_);+9A$=k6qaSl-XjGV@;vjOx2u z`(FMlN)8=4G#To7D)2rUKh8(f$y+q0VjDU_Eaqg&YH4Ql5Se5sV=t#X1C)J#m9w6u9oW8Ch5oYX)O4laf+JW!iuS=kjRg-!wdf(_HOY+%bi?@$SguAoBF)A(J#ouF z@p?+%UeO=CPly6^;M9n85mGcg2H3&BVm|gsdq|gS9jMV|4`cH!gN>uF8|12m&uvG&;JG=uk?hyfWJI2 zQ?UDiq3Iq5i(<3~4jOJaedSq$U+o@bxZR#L1Nt07|6?z%iV%}0e%)F&^ET+mscvk}dyc1fKIW9^5?rdWl&EH|Z8Y0l?j68iXV#@!( z|0;a?3d0|een^D{A0i?F9h-F}Cj6(XRd0~S{e8nQzR>6K!t< z7;wQ~m({1q`g3+gU#8Orgnve>vY}z?r!RE%O`w!~OFe3yN`ZRX~AK z|LMhR-sKU~;7BItQc5pq`FnBBu3{ivYHxAyT|~D2ItMWjor7cHR~a}?#(nAJpvkeqke_J_XxOBK%>4-w!6^0yw-z_44 zi^v=OI8m(YMaTh1Lr=n9^lTz;;Qw84Lyi9b)L{YHQaxWNV5@*(0lz9|8p4gku&zlg zm0=Ug3YnEv!y%NiJo<-XQSY#TofnD!-yX_Hlz$+g^=s$~MfnK3%c;B-^{PlX!mRAjB)V@y4LKI^E2oaLC3z1PW1OC zL3cRkIezyPrXi5vh4rL^Z0hH^{^Wrx2eGeBQ*zx+5NM!e=u+AK~+lZOA_!k6#D4AF$~O z9Y=WU&Ve-L|6hHKc2CBy`)k_l4D0O|(tZc&Y51%KEq$_)rVpjZ0j4LF88qJp|0#G8 zUBCEH`Y1k8d>pgNilkO*JaPO z=jII}9b^YaW@!CVe`Bq?xw*XF@2;t9{&qHBQ@_Z!vccczTjBO8-^^oZeOJR$e~o{& ztFfle?f>SgybD9?&ukdn`5EYk;ugJfutC{McBNZMlC+}6-%zvE>vnt%zLJqxLPAG! zW>87dszGH%t14Ngzd><}p>Yh#i82x!FLtF2GfAnmh$fyvHZZ7pousm{W$I*fLmx0S z!GA}DW6OqOsYXeIZ&^z&>R9okEK0))x7AbA;0^fQmyYBNt=5VLzY?hN&I!2vt9hZj z&gyCOTeIq{OIIuIW^0XNt*NW?!%D1KSm5=?YMtj>?rUuES(}>Ofx1R3|DBB*rJ)fR zf1}b^+vv5fbo-kLkySTQZ;i2Us<*U3u{Jkc?@p#)(Y&(OKTS7mp1ve0t#EtyHu{!b zyFy4dvB~(s6hqSQ;7|=1zrAyDs~(@A7zKT@k-rF^JbXHSfEX*F?wy2}b9%icH4Q$( ze)s%mYM?H0vS#e8yD{E~g5Q9Exbfrx=e(Vcbo$UEU;8WJ? zrI`YrE8t`SO9iYKkYAIc$_A0&F5m$Hj|q4}z|3Fi{Ld56DPWa=J^|YVyhp&T0zN0; z>jHivVEP?8-!lZ9ETAl4m4J$X>jc~&;3EQt1?&~@O#wd=F#S%wopS}8D&R~37YW!T z;LilSN5CBd9uV*&0Z0E@=W7-43IS&dxIn;q0ow#zFW`d$ZWZup0S^m!Oh9&*-p*(N z&lPZ@fR_rWQ!zGkNPe!!R}1J9@CE_b3fLjwuLZncz=s8VOu#1u+#}$A0bdpHh=9ig z{9HgozG3t-chd8n9qY>Y&W@9e0?Ge0JD$d7cpIDDa*eMJyUvATQ%qaXO16CP7TLj= zZ!o5k;T)rHqsto`z4dNy)1-^bd^JtY^^FQ+YnjvSbz`%O9dAQzjknBQOM5_eD|0q9 z)8<;=sESSWj2bU~-t9N62-{Zw&?@X%2ARBK+dMm}yv10WQLfnp(UhA7?ZDZntQjU24MCSu{|BUnXSi@+6Gj^M!Z+RO9OsCPCMeH!LIr$uDFhsBw=+$0LhjR_N3>K>aQuO zWHr2GRRg52Gmba75+|&yrdcWS`y2hZqM|%H7Vx@hcxTnDz~P3q4`6j00PX?8k?;jQx%&(7z0~ekc>gTDMjz@q>E=Z_?s1A{0Yn~{KR4Ff|;XVf$)I9?eivo{InYHVB{XflXl{9wsqR3Z*v z^L#B0^j`bX6j)nhVh$vm9(tCRiX)z|-WdUZbEChsu^CFobYrI_m6fC40~6;wDmjU@ zRkAr$GGtsA5^kQap8JZrqE)r-CTak-K)P9$TIO}Tn^;q7x!b>@!8dT!hApO+dz)b> z6*b;~o86T9wM^LhRMBmR7r)I|w2HA`8yKivfd|1J(7^Cv!rRG%@>J*k)XJI$WoDzl z3`bnAd-l?6sp20~2cNyS4a!%t?RuVUn|15y!9&enP4&Wu)UIf1aIn`B=}PtvPkY@y z*kuV@0-6R8F~o9~EI|mU(dWkOL|kXdl7_~mOFXa)=!7k)S<$>~2`^&*U`rY>NubCT z>|E0l^nE#NG%XP|dreCMK90LMrDkcPUtw3Iz@Y+ze;7*fzzH!{o8oDq&;nz(q^uBS zx23Fbuc&Q;Mg1YAxnY^F2GOV;DG(2$;-?iU?l1tBdB&DsqYV8&onDI39D*;|wet`TyHbGQZ2gZ(do^+<_KpF#b(X=HR%pA%zit z?idn>Hp;()|5+MvY|_WsDWFx{8{~DKeEk2jdiR~Q|B42T_#Z|6GEre1-m0W}$#@g; zc_+c+olz#EtSXG3-cGt~I-D{Y*a}+z@Lj~_@*57i6Ip_rj#+Fb?ndZ&Jne%M)NU$| zIMR<4P)o@V-7gP*uHc9=rZ7Ki!1-huxHT{@bAz*wc^J0j>9osUgKz7DBXfD?hcmMsPJp|%2A z#-_4S37$>7?rQX~hVw@;e*U@!+=!DEx@yE9wsv@?vkcTTWOssJGq_Wqn!ve%*C0CM ze#pw@CdnFkZ>j78+`A9(r~nT?mz-Lo#|}v+@t!lJhJ%)Lq;NVP1zlebdAB{rlQupIiyc^EY;#SaS7 zwihsYJVvC^KNtR&Jf)0PQ&&3EJ2N}Sby_>KJF7eEJDWP!c6M~$(YdK}YiGFgK9iFtlC(;an;7wjcYe{ZhUa#rj2_x_HI0|@z}=xjqKh{_l584 iz3;$%#8>}0`*EFlozswJo5b(!i}rx|?)f)r;Qs(a@EdzS#^ z)xhqm*X#C8ty;CVwZ3gXtA2g8ub=#>Z36)Uq7@@m0b8;D>0Kf%{wX4D-S2ni&dqL6 z`?cTm^m+PzpPOgznK^Uj%sFSy%$%9I*?CL5Spj3L5Z`Q;vCV+=spikW{pdpR>EpJZ z&YmiM{({Xm-}4tVw6?|Mj%cJM>Tj2u{NZp!l~)GjXgn;pg=O#jdbvH)9GFy6GTbFt z*VQoAd}|SV^_7&*YV8=i)IP#)AH^zwDZ_U-W2I*UmJ&rZJ$UF@y+{GvlTs)q9)<75h$p`cO<^k^N5hFW_8t*R>oq4 zJywjz3QB$Y(?$H+On%%p9iTZA-aJ(;`K!R+tB##J5YIi8+i`?I90VVNnk&@U^)GLkaqB_XW z&>f55FnwDS!PO8Nmb$8Mf+Y$Kp~U9$1#iwXTaFkQffZr zkv7d4<4eyR6Ws9e2%9>EfL%&WLrG#Q+2eHS)-q(=J&CO+kY&<6J(;tC2u`x>kr z4k|mR@+3Czv&fEp0`}vWXQ8`iO9_ykhJ}k4)hip`y$>0^eE+pjI^E!sQ%4o;l@FB1 zq*O1g6;SlT2^FAF^d&{^o@0vi^|OK=!7H?S?3>Cf<$xN29CzV=Odji znOjAb$Bslv_9ax8k|RKh{#Z%{+P19oV?Pq$TMX*4e}y6U8TXEZm{m`JO)9H)qV0Cl zc8{X7fisf*(mmUgdsKI_UoBS>JvQm-(yo*8&A-?7CO)urog6QD_YVBt>Yp6JErrSc z__5<&*K!+Lf9hR$gXXBh6t)@56(n(bkd{)1kkK6FNbBX_!V;IBXH%N?DBIpBFb-eD zSgp3h-Ge^Ut2UzWW{>+2x<(&4F0spI>_)ja-D;cHbkwJ1j3+sH;dzQaYTP`{fUZiS zx5&5)=*mCc@~qOdQ`vU7pti4&FB^*PoB@tn2HGx$v3%MD8F@u3_34iDDWy9l0OQe- z5N+BRBsIr{c&3g^$&VnkKDLN*hvFlQYtV%5C`MM;{pdy_j{DuR!in%B7uaP5Pw(eU1}YTlf(EFVxIvoT6mP6_#_P-ZU}gIn0YD z=3K%!9pKBI=m4;pr}5CX8699{^*4r?wwESFMYrj!>Kh0!IrMrJi5vex| zl@vZECPE(azsq<*cHd?gCrgv!vKxE5tL{csDY*+(q}#gith$RMUZy1GydL$(=&*a| z2(g|AnyM<=fn_&l4+p!e&=~6f58$QS{+%kpP+d~;UP?Mp7Vm*XwCEd8POv2ELzX1^ zAuq`8BaU1uNK|zXHM+D440_^8)FtmR;)8V`GwVvp0JPwRL+iVbXFldr6U}dsVJxxR zTy-aalyVErt;T}g?w-thAcwP<^joR(Obgo3X6iU6m6Fea2P-=%V(`a<0RZgXOp6%4t37b39xtye19qv<_r`aFX1y8W}3zh;sTE z?N)gb<=yz89P?=6W1Dnu4=NV*2Ki@j{$AwSTuA+|{mQ+EX6G4{NiTQFigqyToP7f( zfVyng`cd+7^$cn+Vc0B0`~$5A?!9#e)ficjb++C}j1DT=d&W))$qhS*8~QAmTQ4Aw zvK5k|X33pYv(M|I`JMrm5v+0VK~lzC&ODpxnRQk#rxu*oA@;(QHIsvF@VXAdCw3_C zvq62x9?~XfIT7h4qXDu`1zI3e%yEw2E#tMyvWH2ZMbNxndkNK+WS#TD&_hajT?*uI z_kT({9y@GdXAs*1V1(&Lf+qc!fv5Jqar&33;33|g&!s5ox(dS%oT4L_buKmqUILR? z{sfah0won~y=#q6d&cz;&%B^$%U!LCrn)*j8kV%jU0uNYv>&)`=kRe?H-}HTXm$%G zD$Awh!$?8sPl=qfCOU(W?Yk81%sh`%&jz!4EncMJBi*iYH0fZDoV1> z$;1D};&^tjt@FS5aqRM4_j=>;(cQoa)ZbyzL0C>Vvu5QY} zEAQj*ao0nD+ONnDA3@5!#|O`R0x2J8sSC)1djOSmePPyF3I(*CL3C2xg+MuXPsMCf zW0y7^r!MI8dh5nB&7krH<5BLLH7v1nm`8ip_y`LyCXo{y1QRFPc(~<}?%t`SpK`51 z1IAk@!|J7^ldcZlpfd{Y$~k+e*Q#k8eDosXG;)I(%7zS-y=BPAxSC0W-ZRhkR@V6+ z2=Y*k>ZF|=#7n=Ev+hgs7SYo%h3YCJHZU@j)u-KzN}lY?UN|{P<<(BqrhSEW<7vcx zo>S(-~I&LcDwm4^<7oxtR3!`JiaC?Ipw>L%-+1rPaMb`Eq&utAl zZfnqSTW{DwJ=kl;Gq$4$vg{tdP0&J+788kVD)sFt`AzRdgqK+bNAV>3>>%)^?{uvP zA7~%VBlMz2Ni|b-+BrdAbT3r<*oZ()XeOmJs0b&PlIM{BR-TZOPZ6fFuRA6M6Kg)X z5Dxl@l%)0FtDE*r&&z7h1`}5qMK(>^i23$6u^bA`w_1CMrF z;waj_%-0~im7iC+LrN_nZPLz_L)zI$?YQ~q@iMm9r}Zt;UiN9*Mc^fS`q&JJXl#XX zu>Uw?V3a~*_JHvuN2rd<&Z79cShr+ErM)!&bMrpv>f-PzJ_6l`XxNcE&|o7ssMVgM zb$~o0>%0{*Y#9b?YTLCwV+9z~Os>#O&gbiN$R$&w+iEq#xE@;Q4X$dWX=$E=EOpi0 z)I;fKTq}69@4?L}bsk~0lg216tkxSpYv6n?|9eKUK&d4Z-&7`DA<)Nv2cB7Hy1|Tk zJm{)Y*u51+lpdwMs=!}wr#9f{Tt)b;>F1_4RZ6;}2)KQn3#cz<*>N>s1ZD# z&0}b-;$p0aUZ94X_H%%dP_?Fh{V=s6?vsiE9^l};x7&A z2m-1ZFNlWhQu6zdq5mz^yI9gNe%$@W$7mcbL#9Ae(4DOosTm7v5F?RI!vA$LV|GM`+HAQU=zKpC@ga zWmnQS*^#TkPQb(mq~vy_+kO_+-kVptQ+DJ=Lm?!t-h=_3OXn$5+NzkQME@v&(MOPSD8#Ew1Kx zrU^qz_EB5(=yqo_uTq=2@)Ib3h05vCt!2>mx8R$n>R_E;Y6pHvW-#ji%;jAUy@GuI zs~t3cIT}mNT4FyK|ItEHMx>Uf7OP`3-y%DX>%x9@6Zyb%__w}z(PPn5d)WH>R=3?KH&wJ*`4)d_bchS!)S;Z<&Z3x z_%{}iQgjl*i>#4%4oTh18>}WLfjv55cQZc8>;X$@lTx^~3k?`!L7VE?dK%JF>MwX= zpXR@u+~k`$$;_V>Y+_cp`AkKhjhS&#kvW5v!Fr1J!OzKvnHk)O4~k)O!1(S7bFHIA zkyiG7fH*RsgBRFHN4 zoXA8DA^WZKfW#I_4561YRWJigP%I^PKr}R)*=F|hem5pYk~!<#1J0RIAjG)&X(8Fh z7>0EM=O2y0NSl@vCI%{i9%h>C$S6LFH@ze!U!cy@hw$-R;Tc))x#1- zMe(cBrhS?JAX;H3MSD5ZLVhmT@0Ak+iw7RGs_d2%#^Qn9bbQc__@vB1nm_0&;_xX~ zDTjM0KpDf6kGo_JU*<<&t6&nX&zru+=Gkz9&K9KGxDnGO4`R|#5SmofV2`c6pXj^6 zNS#IYn_Hx4M=)Qlu;D5R^AxP;*@7g3o)cJ;^bshNtGTiEqYOTrg84A&v(BgJkT31r z$n!W%eVp?8-7a#ATJ2-p20VKN@5pQrx9p0 z>-=yop`PM=gn(J+X+(!GhFbz*4Yvfs8EyuI=iCe%c96k(x%K$@OBrH`F^Q7}>RFqq z!x0~p<>nJ(nS@iJL3}sP?{I>VsOp8S?CS77$0kakTH>T#Ju`9Ard~}9cnvzAQ_^E= zTfj6uZ6<-r57fhoUVsHfwbQv5*3QZg;@A`9su-EmF_Ui{oG<8n2<1hl#1wBKka?TT z$O9RnhXr`KwwqU=`8hvNG$IPFz&N&5V}L(LOA3!ou^g$Z&6BhFXM($4a`^9UpB3dW~CoB_9<#9w07pAUJOzHW1=3XAr+v zA!o%hgp}BOFC~?*)&hfqWn#ZX);6vocGyjxLxw1`x{j-i@>beIRnrDYp-l)j#v#`d zpVsVJk*FNDI*cCp>!rE*SO02}Pxs4;-ZnYYC2lM?w{JxsT~|ftbkOlLSFq0L<)O}S<@(VexW^k7y=xhgf30; z{z#A2kjbb}N#yUNd~+`UW6C$=^2Okm@#gX`Qu$Qm$pF;9`TmhRUq-z{vG! zK02_X4t!4{RZC`qElx#Ncs8F4_cepG(PRuLSY|xOiIf$HR&Zou+<*dO1(li?MDyqb zXE`*7)mwe&H5k5w47LX6b+jew0#7=9xE&A|NOSAf0v6St{Sl-i0i7I^M*I!uS|;n%D;QpAK8!Z>{%< z6V>XQzI3x|xo2}VDk#`#;q0AUQ!l?1tmbKNWPeR;205ULN4jSx*aGY_HiBPG5|fQ7Ku81gd#aYk7`e_SYF11+Z)pI*87hNM7S63J>z)0~};}b;l18_K*SLWGdu= z^{UBwN=>SVe6LQui0p(>N~N$MK@V2#R4n7tI=yNDw%x5cYyE5^h zk~n$V&HoF}5jS`0<#nLIImbx7Y7V8c>H^$9RwACAHXZrZX(e4=)lV9%cU@xHPoGT@ z5OMrndVLY5NW_8=5o|^(>8hWBhVBRxBe?OV{p>}OAmoy3pFyj5hXk9CxrJVAX7sZ> zh(s0Jdx;aca~e5XZR#AKI8h`eGbCQR#0E~F*f+R@7=OeX%Hx}RL4j_KAE=%9V(rBD zd1-OdwTApvu=fyE9$rGwXZ~dpOTWJ*yGbI>}TXb)|6}ez z`5lmy{4p`3nH!|meba5gb;iV9(>i3TuqOA!U$yRX>Kt(Y=?f)&z^^+M5% zEi|iPY{vk-=?TwZ;CQq@nePZF+Utt;8_&cK-G`o(@y-CS1LeDFSg~G*Bbr{DR|^zs zXBX5JX@+;Ap(J(|Y1b5Kiwl)RAIkqMs~1oNzlR!!T)Pyyz2P!s$dmd02=gj#u+k+e zW~{99otYvy%M^paS`QDs2^DkkQme9g^24u}-;V)cMhg1a(X_gKM!hQitr#4CGSEqvs$tokraH>la zf6l7YFc+MQwRROSIeP8Y?5JId0bBeyF+wlRh2+a_%oP5~j1O_poSt?WstlaQ{a@=% z&$3}{Ph9yji@!hc7;4ngn*#c@IgkrS{}h-xZbcNJo`*q;iyhb(`>v@ZwdS}11(`6K zK)tajvnnhEKK=rbqw9Vvb+w4W#%AexWVOz`Uk!74hX~q$EP})v}l$?kDUKmlNtCbIDPh_R=Q*lDrcRC zs*usgzD1m1=__!ISwTAM)6TgH2Dj!reFVLNv@`oMSf8$>j^eEzgcgdHA+K0gN>k2j z5NI0em<^{(+KA=Q$&}hR@24R@-fz25$S>2RQl{X(8WCNE_A8PfV+*r~OdQ~eK%;W* zeYUOGJ~H#Av0^NNxR5f}BSdGrq1#c+$l7I!)>wr1kgg;-9Cm6eUGfr5_FC1499&b1 zxEKb&!gtru{;20~o5;E&0YZo)Su3%%zf6~CD9Jj%ZF2ts@q_z+NiVHD7#}e(S3jfj zpgIxf`#9x>dG_EqKV7njS1IK!LLDsZmVFidx>7}}D>VjiKn2Q6IORKFKCoZW>rl20 z!|f_y2VN8V(=ysMUlVm=3m$hE16WYZb%zF4w{Zx0-LVr>3SyA>+hBPB=|0DQ;z=Kz z6I}sj&k22W#{(#U0;BsJcOpe@mt%+dB3*Jn&{^kP66p~6ANEhd4VCkj3U^hSuDepP zy||&0Uc-yOONDF4;I+HEDTMjd`x#VUY4V`PHg^w>+pimjhi6m`y#KNqX;aUfwAVJq zy=P#AW$#2!nOCnKS%bTu$`?C}y#>`pzRK;<;fZZFZ{>^8*LnZYbxD$|N8=+mGU(dL z8XK6@jpI-cQA*M3^Jp(^`Y2!etyP~KwHI$oHtnLfCF2K_Cg;6KV%DzbpwPZy2G{eK z!3}GmWqcD3ka4C}Hwr`JF=-P{W^vhEIwnv4-ZCp}exDYNWe9%h?tvn)M4zqlkI~l< zbJ0DyIX;DbtKQg25~z}C#KHN}*W_?;Nu1k3P@5iAYQFy%e^YN+ItNDW zQF~z2JuvFVdziT^LX_|GR83`>W7s?#+KL)OKy+8O@m>uf2;hweN2fHx`N>o0EPmq!-I)T;5%5&}%2UdRi@d2vc7j!Jrf6rQ zfKdU53%E|e2L!YWJ{Fw?5AyPyeI{3$_8BkW0z@TbuT$YVfgdm6DnWnOWhOnn6GZi% zwaP{Qh=30WI8&5AEAn#$oGkDM1>7$1cL=yjz$2o3qsV_rz&Qd>?`M%5lLg*t=b*@2 z(30cGDRdV7v(rsJ7YlwCwCpfP)U)Ka+J98kv-nu{I`b5{?V|oq1-+GDAn+>%+%Dic zL1)?9YTtqvi}E|HdLlnuKufMiaqUL-`Kf?+325QXe9k|9$K@W(V@=Cf<@U5I}Ga)HEyTs^C0CjN3Jvk5H52r_)j z>0hr<9$`VIvQFk_Q5Im!ae39onvjlw)+}2N4iVOlKR*kzX4I|Ycr74Xf-tpThCr62#-M5U7;5lJu!m^ix% za5(;XDF7*Akk;hm26hVmjZ4PAuw8~{J<2;EQ2_N;a@tFPoeT;$z7s($<8LR=esB(;WfIk^H@)4_k0QGB-i*Xu~v}LeNMOg!{5k#vbWf|xuaxT>R zP>G2%d0Pu;s3nq~`hp}b#vk#fR{Y=;;&>V9X6_@W`V)Da4BA$1nV~*N77uf2*ABK2 z=Pi(jepV|q0@}fGwcwUNeq@Xj8WrvTP5I7J7wY9GR%JR`uznK?36q9tnOCU7cax(C;;q4 zltz%3hj;>6o_w$w9D_*5!AnJ+Mg!@Qlc9slV2)R6i8LA7I~}N>)4}u!YYwqIAi%i< zxJ{^M^S#ZY!GS6MKAVQdIE|bzyNJt4qm9O!Id74TJ0Z7eZHu4E`85L@11;H#<|xaP zNqe$dIlhE7ZA5ZWjWFy+ZJP7bO2L`tNYie9q-ge_(PEBXQzPo%_QC#Z_&-QMErfXu z@)44ZWCW`K{t zp><3fk~h&9H1*8!A?;0@TNcW#Aw&D0Bqc3P>z>*dIct+&(TE_uh>Bnn*QpuS9Da)L zQjZc(b8MNsX{{sAq8WqM8S-q>kR%HMPh4B@RZ;f}zR( z+0{e5gLu;XMROs|&kWwf&`LYFQ@WhD9s+MONApc?Edz3J)iBp?^1$i%x8rj3D@jIU zb!Z(KtJ*)h{+-&&r`x4ifjpZ=J^8fR&t%b+Lce@(`CH?gY+%jZq{$@kquCudTll*Q zyeT%Jxrg#NCl@hCz3@PijXoBSTugHjuX)kuNJ`^?)`2zn(kx2e-^^D`liy8&XgCm> zGMPtEzKFls*S0e1kFNEY#mp0JiMI#Bs@xoD_v3%+1JP(CDwlcZ&-2t?J26)rYYVr; zL;h%+x>hc$ue}Ck{4;-bAQ}usI_0Q8+!C;=&tFutV771m;)$%zA647@AvqL@#pFm( zjm^^_74FAZoC38nC z5O0n^=}@G}ueL?P^n(a$q$v`TVfz>%CNxhnTch?qzuw9=74}~j$fuvwA$|ZrwEK6& z@1aPzWmWs2q@tn#Yx3~TH#2r3#@HiyxX>+(m8pz9lo#W70Vl7TwVCd0^khP zw?0#WYwfghL*W{Mzg58d1bkG$UkGT`+9&eA74Sm=?Q2YmQ36&7s0g@1z%>HiA>e%i zJ}TgD0bdjFeF2Nsn*7HLI9b4%0xl6SEZ{}~?-uY!0`>^FU%uz@G^C3jy~FXb5Qkn%UlH0VfHl2)JCpbpmb_@Gb#=B;eBm?h8UtPgq8bi!a8XX^$_)pJ|7G_W0XtTM%fO zg(xd9H?WrV^ISCGS1DVwW-&WJ7;hkiQ?^C^P&}}hogfU4N;raun8OZSeL&4&W-!iF z;pcK}DVxQa&Tb0@=J`815d5IlvOMf!#Ly;9T{O~!m*`_gD?0xag^9cAU))#bd3t;g}kYbJAJW%jzP2z3t}-#hq@>84o`#JJee z*=xh-Egaph3!K_Pi|oFDfAy!(Y_Wal=3{Ao9-8=(NB2{gv35IrYf)REbAFKIV!C~P zM<8tV!oTyJDI#MJ+68{fT`vNrtyUmV=xo6zHI_ z0vlnZu$F@QP$1C3Ru$C8W3X4VP^zP#ArNhE3u7*@OvqvdW}z?|QwQ^8GQ65u&=88j z5%@g7HWqwtqb!B8_94A>Z-G$W6T*%K4$dC1Flb-J_(rc;7~V0A{h(m6zfHvmuE&fL z3d~=*ipY8k7GqYhq@EXOkC1&27KAYNo7y|jv#*=!4%*JX&C{Vk80m{xV_VFxs%vpW z(AbE(h)6iFrcK2^*fln`1XSAT`lBr|oNYAX5&~_6SK~Y#>B!2)Ao`wRrfX~rHATX! zc{#>2@-bXlVA5+;*LDOLP6#+A9)oqwy(InETcf|Jy*U)d8BC+UJ=W65n`IkWW0RTM z#TtY3n{|x+2{NK-bX_|eZ)=3DZekHzqmcAwTVqFD?Zob9pRF+-=B0)9#!g62!m`PB z|H?>IWmnlTYEa79HFmRwh4x@av@NU#*%G9{17+8nDR5h657H20P4-|X8JDr39Xy*_ z{ZY2w-WKa@GPV1iv8@A%`53H&7M6O0|UQ>pnmY@dq6B8t-RtU=^P zZ+_7GAdP_6;ys7?obcy`fBb~r`YphhJ{IH|{wNeBIm}hOoqF7zL~;8>H<`2f%~k-M z!~B+!u`h9&3f$~euu6QV;>T(sKi&#BAN*$F#)#e@G4HSG$ol{O9Sizd-4TO4>CP#P z``T*(i9dZl>+KT4&txU2XUXmbHNB5PI?;O~=6fVWXWcQ$TyA<7!F=e zL7{T%7LL*ttO&GZBlFIcZg4|+k`8@igY+r}y$^D=a7waT2XrF|Te!v8r`}hgcB=7X z!#4DLp$tCsd^Z;;-vX`gMQMw4o62;@TIvkm!EMUzHe#C9go8a0hn)6Dy=ea;L6 ziD~b<{l0hayAI!;ea_lzuYY^(wb$O~Fva(@vslKM9wiiFY&UB9)!wxPDk+~{_#cURS$tE(CsT#|X6!|e7nm}?u%OYST+*Sl&Q3unxj zo~)5Bu`^bro6f%Wwy!A6ZHUdh^$J}~0$T{kEhq-YhOR~(kCKnVI$ZoZMwMF!2+7Ys z1Ng?xuZJ1ei!rm>k2+9)e0bH!*fP3lVQg?3>K%+_ks&63x*0nbAwIbg_uCjtx&-)d zffh=R%@UBd&c_!CM7mDmV`e3l3u~&RDm1MjCG%0BGqa#Xeezjl_d>UdI7Bo`P^O@y zMIqr5c=-$0H8l}bqV`I|!s}jG zxU!6(q%LbmAw8DT&G-lOx)(}yTs|{_x1kUo7Xr2R;dt&QcQx+$nC-v?>3zF~pj9fK zqs|3HK8D?BAiY;;2wDZaf{Bp-K6mBv2c_Q8gnoT;3zQE0@oS99y|O+z@X0NV`HpuL zIXhfk%kZ_E98fa!^Eo1Pd-o^YThZ%mNB>nI5U-RQeMdYVC6QHjxWsPXk%ub1r*+B= z_dU|#k~wfB-Y))RHKm4H(q3qtt1J;yrBaOWM1F}3xn ztv8_xtVAmp#_t%r_Zt;GM&|uY#{r%{)^yTbRMLs7vPM6W*lry(wJitnFyFVL_{#Ss z>%%Vm)RjP=X?WBJv8IJ2_AZA^PJ88ys;(r1%wt;I(SM{y4H50Rl781N^ylR9mTy(1Ke%zP-Z*3m8xZ zrW18g(yK5=uyb>X50N7!gP&apgl^E-M>>F9^N?U6-E$W`rpdb543mDSGuLq#L+?9iYWp`d z_(J{mVbsiXLYb0NXq4h|3gbP;l|l$4+lQsYetS2Dz%UyqT#U5jP@+V!#eU3RKI9$x zo%evAhN>(%GF*DXCA>!9Auxs#hg&;b{_Z$AMk z!Op1IhsXgmisTO}RcHXhP{Nl3C-U~`m07+cCf^j?w0rG46UZ)WU|*B(84Mz~d+=^t zQT%Cb-a$5&u2Bl(gR{^BV<&d=(c|-3DKrK{z-^D7ze$SSD0|K;o_M)pj0}OHh3EzZ ztx#ez7oDbOuQVek?yLjU820sbPTfCOIo z>9u_^J(hLU)z}qOKLm(Qdz_*ie(d!*Gh~3`LLz3iwx%=}_sNYB5Dd?m{ z+Q7ZQ0~S0mU-6wlV@}+88p@kAEw4cgksriV)F;zo7x)AyK)x;9|JHDSPd8?3C~-UF z;I4-SH!40v+91w^hH?8~z34Pn(h~bn2049a0CM=t&xR)u%{H1BL)F}ULWwOfVt*WU zp;5qS9qgSL{|uZS#Vh$qM%}#Kx?F5_SQmAAIObsQ!I4K*|HOLLI(h(2etQquk29ym z3riOkF7kmPXlB|KHw3A?fqdA^=^aZqJ@!3xGVSUPCHq)OhpTfG%@o3}p9+!2be?Z_ zGAyIGLLiz>Afz=ru<<~YsxJ>;|w;I7Y9yl>x7eDKHU zj`m3Z(s2Jnh<+H}@%}yN-xukB0R5qpD#QC2nFxiC;xq~ToB|gf_ABmFrH6^2L8R+; zbdBc!PvPEoPUasiKbL)E+`Dqay{;YS36>FqLDP}N5ed&?zH=LmAmV8{dIL0iJx*&6 zRHShJ@D0uZ zL?+gN|X)n~=Yu}G~*Ebh_UEcHQQPVGabiKic z(HbF{A11j6P;EUVTfqkpz|#lef85`p4BQ*u4p)=y?>b|-^Qh31fW848$k%~%3hqy? z9~`6Mr~N=UPP~L^$mPEi%oIMcsGVDcMrH3l)83Egp0N%#$90i*@&M+Y#tMU^$piL~ zt_L&hq$(Q)OIR6lK|cE%s89LrLy#tZWb*|6SBLo@LSX0is8|hw>9xB4m*r{Pw|!M}^l;KCnypz$OHXAcAIZ zEerZMZz6UOs}$9uRWMPi;^G(lBp$Y7OqhBO?o(?f%A7 z-V0MV#rij&@;45811Y#OEj{4v(OHk0zVjUzRnb4i$LpkbZ1$!BEDwWG%Y(1#Gs^1w zx_Brq!wn4hD0V3?YWm|AdDBQ2>aD2TQFoyJCqmP)5rx`Od#SyJ1?wRgp%}EwcA9dp zlkl_)+VwICPF=a{g0RW{7nA)X4DusRxfxb!Zga|>tw9lJTE523P1fC^8@F`u157e? z{}*ZJ8^b7+0-!kC)!~{rPQhK{^J#oIqq>fcMaKV@L%9d zmlR?O8lvTUb&`-T(_lo&KS6&78Yk&Lxqr;%`v2=+F6uvlzWw6%$@GU|q-gmjO@?3r zZ-40Pd@cu1k;xEGoU%4G?3f5N;xr39k2+;-s^rrAadxSbMVlz;e3E!`sbbPfr#F`- z5caT7z`&Ry>RfBPYJNVtUeR{dY}9g3%NtvPN#x*Vh?(7yInU>Mkru=&KZP0^Md_PZ zA5?0IG;>Y6+Sp{GEm1_}9`75-zH&)gVlwi5theEwmlP+A8LT4@=kZk&KaQ(>MI6^( zQ$iy~I*}Mb+OuQb=s|vF+EsCYaxY3m%`gL$QxnyF?^E7p)C#P^Dxr%c*w?+WRlJFi$Qe(MUFTw7eX^pisZOj zCrTta66KD(o62wE()-gttCk?1#RtF>i)de%ujv>2!e;T`7o)qFH&N8s1q+Finr^)c zS(%y>^aK}>!)~PWBYxU28#}O^INAwde=nK3=&C<%KY}w&TVF_8s(6g@=OZ^D=aBC5 zj>Sq3c*pd}U)#2!-8-U_=6L&JI;jIzSKduxB@x?Pw8FmQQ8zl$QY0nWqs0Ct63lSE>K&W;umPUfMUwXGeN00S zJ_)3N_2aM@Sgu*HMeP{9zgvb4l~Q5w;~)-)cNn+k^B7ffMZP^8iV0VhM`U{M{6ftRoxMG#din*Zgyi_Dn4Ej{V?4+B#!!bFB1y7w?E6DX|EyrQmrhNgj! zzj)a1BV6xj63~q#7R_K)^uX(wMM`mkQa-G^a_&jblR-M2R9ALC9i^)0vy;Xno{t9) z4VN1aBiBDX9u(R1p~N=G6frZ+b8S3m058!Y@F2Y7VthE-xW8a%#N(}_&tT5sxDT;r zBsR9oxrVba} zZM(Czd28`Ko^3$(Kh*}&V(t6Gy(nWxHqA{txu>b^e%Q73b?QQgpC;cuxOaMAA0ir_ z+nueiZ^c!S&a((-Gevjdq&kt&=y4`@K_~+~0Ree6%$|q4_*JU>Ms_#5)Hj&@_P5utEKU2WQ17;oBJYDB!7 zJ3uA@U?u?5!vM4v#-PDy+8gVCb{z&DvxX+>28=VA3rD~h2l7x1ej6YB5m8jf_Yr+Q=yX0s+j2UDrc*ea zv+>M@=d1jAOo|$Rqk}K_=t(p_;oDW|a5QBEA7US%XA7NY$*?$H$3d6}F>*|zl%Qi7s&gMHyIS3g5A;Ssp<(n84o+y)9~$6Q=O(? zefM!Z_=7cJBd4rw7kTz5_c-^R)<+?sS=~bNWE9PFTA25g0bq4I$#<#FF20-Oxptrp zUa5J57M>%|Z+QMQA9D`pyU1OnBBdf8j+w|eTv%k}K{~#t6Lzd;P~i{iP7DKzkU|gU z@{zj+js}TA6!Olm(7<+57?N}8s27;e&pe6V$NJ_sOlz#Qd%{v^cmZ`PqpYlze~J+>I%olB6I z&^lnljo&{2`dBQ9i~kIEf3&oE`{@XkL=9Xy14vKlv0{?cc!1RJqZ=L5G5M*pgij&W zsQNBch|eb#XntbNCR$;&lvs{g2TX-C>hlrn4Sl}fqI#y!_ZF(kJ|8_q1@Hu_fZV6} z=$MI?%%WsNpYMMITGi(}h3Z+^cNmp_X@cw<#06KY&-Xqqdr1y5CHUi<=gPTfWZ&-q zC!I7gv`0%MU=(x!CRS9fURL?iQ!LV&%U-5xsoB351GNpluP$0JGHLoV05~qW1V*@v zj2(O$%)?_vHO33!J()lDron=pWq#5WlI>@u%^-|(LKb-T3XE3x(f9@+pf&Pbd=1?r z&#<5I+s{xy_1n*)(g$4VCF8*SERih$7Jpqq0TS*lxYx+@7Z?J|3474HKN&AB@G!TV z1mB;G=hA}!Mb7_`NvSJaU=d>ZK}@-8yL-br@%Ty4!{Fmr(>7x!UrXp2Dvz&tCxApF z0+cI2jvwHVe;h}(M}m^rP2OQQdb49Uir(C!#dyRnO~*5Rx`0La^MM$+E!dmdegFuk z!5=6Yk(XD(*k~mU_wbR4JW1*@S0Ze+okOE?1C0$0)xHwhwrrk*T$=JhiXW}^$^Nm!Sis<{O4gnZ+AX4HwOL$ok@Ky z1LwV{4Q_B8O@Wj6(in~Bx8Bh;zJn9rk2vI+5lM=%y!NWe?)Ya+*s;yoBjLp}&X z=+I%JIvS5orl;)vppS~S`Qe>a;CDD)SIWCvn)|SG+GA>4it%n~?q>+OxWelSoIH1m zV__DYZdfW%Ck4r36GHC3l#vLQ;XV-TLlc0&m;_8{+h;9Rcu{v^MF^DDkmZ zdwUIX`4GzE!2BIi&tKtJE_3?nOXXl_cMcZjrMK=g=Lte1ziIT z$P!GiWPRG-0*cbjt%vZ=W@zrA)(@$#KP^-C43B)(vJQi-4YM(b%Jr~6ngALh0oq%t5uh25k9-l=+whBvqwMu7|v zBFspY;g=M_s29CIGjf0i9vT#p2G3)S3&&Eb`S>PvE<@N;ki1COga-JHzeKO(>*Yjh zz?cO)vnuIAn%kMCUYPsi2n0szW|)FWe;hRu2w~-5FW=vX-%p!CP09_MZ6I?{%A;rD zw<3!&h7z777>orRkFxUK;14Y2@8;Ld zH;-%^nGtP1)qaLf0SqW89_;OpqigDMP<@eO0(yHnkCX_3v|xqpzpS$aW{ym++!epi ze+^HzK!a&cC|&dEK*aUC$_Sgn61M4hm_(WyAS|Z4(mW*FkHH@5iwJUGC~*`6Blm_9 z<0;tK2k7Asb;!bPYV)zw=E2nF6XK`nBEFZ}JRpy?hKT*A15%QlOJZHjcK42J_b%Cc zC$L6NaQ{hB?0SDWyhUF{CNtxZZ<-!^q_|}5e6VW_HtCv=J49y~m^AAe1x!`kex{SF zWIr29Tm!iv43GwCU=lZ|_v&jrG`K{WYf#Bs41sCbmqm;WdDQdD&K7Wk=gti0P}=hp z>;TaaO56{#C^tNVYx17s(rrra7=hH>u}0+z`@KyO3opAFC^f^DZa}#g+fvWl5KtwQ zq@sq|TkM7Wea62ta5f)|uxtb?BEBTE&7^&QD9 z_b3I_`=iNvYt)ow2KjcLZ)HQF$w(vbpm!G+eqiKXO~CQ$T_ZLt~=1+vpLiT*-YvZsM;4B2J z@tTRS9Kg(zV9Nm8Hfti@YQl?xk#0`F*jLB#2qpp6_?0La_&!2-DdR9z!OMS>|I()o zIRs@rw06CK<>~Uh{`0g>xeluoN;e9-zQa|3(t`2|$}q~;XLq_te^0$5s?T;@ zQ=xv_Q2qzX;uWRKQ!NW?>gpIPWOw4o z>w|mHDZFz9d%)ptaMWoO)thxyO%9cT=0b~0mM?lXH{*T$L+|T z&dlajwQk8%Rd<)i;oia<95rUA%Wa-hV_vsKax|H%By&|wjT_!#p2HUdv$?$Cfd<#6 z26JPR!&BojyXk+gs-#*MAa0lBs&>_xH#*!+1ejB^a5}WST%UEdlDVn&TaGYpebdHj zcb2Lyzs`)hmbkCixf<5rUk_<$50H-pKVrx)FTT(M;s3p~z;2YVhD!?W=c5QH+ZG@L zMXg3BEha6gt1GUmZO9iV{X^mxa{992Kbr+=TE-=+lo{LJivKl534bKOo_U0^$|%?# zz4p`1q1;}ysxloXVu zDCQtg4{~tF2c{ z259MB%{nHg28$W{=ZTQj^e^IS`cUR_)opOpHKtlh8>$+cHn=4C7F*(|b6}CjN?%)D zRafe$rge`UW=m?DD5+30DlK^^tg6GmIlss34U)q>xeH6uL8dw_UAWUDHF~7QlH{&k z=aC$kK^M0cZm4osRfE)Owp_Qg&eODEc|()r_HYpjtEx9RSgCHQ+u_jIvuAb4d^85d z_>X)%e2appEDK9;MivDvtp?y1j@^%L{`7be`V zW-qGuVAc#;Qs%%MANZ>PI;+) zq_mcP$|G$<6uHDw*nvD{Vy>b%Xu=uYf{g=}IBP`~iO!N*M_9&Nt<7p`Wd7d>p@Ec7r z75M3&J2~>}`VTJ<-&oFpezS=kJH#2!J!a1_4soDUt;uwLmN;_c&3rfZo^*{;7y=e4ZH^YG*cB$ zZ(TUQt-^IRyPvH?-GJT_a2d;BS43zw^1fT3VHKy3X8bHs6{ryF;ACg~u3fK(v z3(LL)^qN4ObZP|UTHb^3!so+gE;mW$;@VQ%JUp8jr?Cn&++1?vMjsK9rgF{cC#I$X zmuw_)yavcs7bR(!Ppb9u;d*W{`UxjD+8t~YFzAXtdE6d% zYIrn4#3!sL-I3+T^&)-?VUs%i(MP}!e(CUx$;~b0flZ_+DaZS%XY8aG*Keg`4^|M9_uH)6_}g*n1qLZ#poo)DfD z-V$QPtHm^NjrdKmO?*+@BOVfeD}F3q5aTRYS+2DtTkf!Y%hGCj!!nZkaM}}Tmh^kl ztJ5D#-;w@FdO^lr84qUkWE{>opK)7eUS>(=!Lb@waj|A^#SY8tgl#qYdvW_YyGQrYW8*6S=o!SAI<(@_P*?Q zvUNGv<`m}Ko70-}{hVDnhjKp1`D@NRTdHlDt=8tY{n)nCw$JvqEg^SRZfkBw?#sFR za&>vvMTafEXyqx!Q!yEEDu_ImhV}9WcjIOpXIlf_be%?1*zXieKqy3 zsZXbEPwP+nP1@UOAEb?@olA>NpPpWjen)ykdULvx{u9{bRQjw8A>)pW2QnUmCAMY! zB;&=5JsAfx4rQFqIFq5zjL)2(nU%RH^N!5&%x`2mGhLZmG9Sr&JoBl{f6na8{GXZq znMX6HW~FB>%ep6PUDo|sTe5zb^?cTEvW{h)gx8K{eV)av2J4m9>#a9i=UH>C_gI^( zTdWUTAGdzby2sjM4OnMq&&$3oJ3YG~ds+5|Y;U%l{Y3W8>{sE(?`4l>e>KOFlM8RY z3;x`cvn8i5C&o72c9rcq+byHSiVVO`StP$>mZR>SbC1)T-UnSiTV74$yU*3Sy7XQZ{Bdh8VG&ZTrj#r6g131s?V6apmR2t2B4ys^ zv(K4i0@QnZ-#_p9JX!nfy}$drzrXj7vv-opuk?rTGk<#BUR%Xaud8Wkjkw#xq2{o!&E4n=21A;Av+52yDikDE}AoE zR*^-z$}2>L%_-&|NmOLGjR+-Yw#{B3iUA?w3?YU;gFGL}gTxohc2jN}ij-eI&*P2C z7rtbZdI{k+aoK@=@K~8yX%}Ki9*R#3v6P1ROP?Mg3jfkRz+a@PT^jPj8}Xt*)OAt` zl<~MlRl}ktpXNh(E&(1S=qwf>Wj!9TxPMXDL`=;_LlqKrT9k!^BJezmHb)|aE+(%A zmM(m{MMM9hR+FBz4QwPXMY?l3uH;*`zJ|~xe03vHzST%skHpgglCPU^*i7Qx!^+en z@xqaCBS6v@PoqFNCnE!8Jj9cQPhbDX7O2&KQ#X**ZWPJaZz3_&W8=lWs~bFGu)HYW zVdleJ$d`V@?z5eT8@>hP3ryXa_X47R*pi<4N85S)jREh?d`k zR{>tV_eCI9e(I}NvrVw~A#G-FfQ5eT>XLp4`65RisZf^G*&Boyd(Pj3`p7LFk#?;H zg_8ckT=pTx&_EB(WVn_KkJ#}lmEqi#{417}L^VWF_S_3S&&;-I^O@O|L@A)Le=Lk` z30$gtV=*cv{jpa@KusvS`bV#2>lO`7E~_b(X>tG{`PI^XuUrQB#tqf6@vX)+G)S`c z8U4ryKTI4`60g~XXzJw@WZaPodxUYHW2|7=Q6+vG8lX)IlUn-S*ti4kbdtQjsmK9% zAP&HeplZbI?!L6a7j&NFup&8%!HWutdrQ9NgnK5c#e9rCH}@ zI3RZbPVQuw9*&LYr5fQ10Ptj`Vd#H~Wp&jBFgLJ%OGA^>%G+O<<6 zJK?^W#LWlpaO!uM7R?YrbTs!?&}T!27|V|zQg-c&4{5i=4{0~W`fbX~`Q2yS+f(|R zu@7wBXRcSu-yBQPpObBmIO2z*e^B<+9zN|Iw)LHA^oYH)s6_5r6s42B`}!tO?v*j` z-I;VGI5|PO)Z5oVj@q3@DaVKI?J4O`9NO+JIp*&%9N#0tOF&4r+w>(xsqahN9^LVj zQSiOkfKAtmoIufQiX5r!Xj@jd^N}GCOFDLeI>;g>X-(0)oI&j(3&X_$spt2~436!U zjA1XsyOaEOVfr`jki+J5UlDMX_-q?M*U*)TW6@cuTfnN9ElKs%17EGjBp^qvbn$CI z74&oBgFmIyF!!~nr)=!OK#?uy(30$sK5}-O{zH~J;bveUWPoQ!FD3CFNkB4G=w%sq z9l#phkA@z-0KR?F4201o7HXkMFprs}M0rv=J8dC~qbM&cRuYFzSYbA-KMUr1-lWgC z8UlQ@4=-BjZcrSX)06#R{^K}v8+A@2#}K985~9EJp3)?;aOB67BTE;Kr<5H;a|qMEr8_}Md#Lo=%odma7iQ1)as*JiL+Gd1In6%l z5!kmhml$hGuR#_)lEs_$Onn260dLI*zORBWf$3p%m#j^`C8tN%P!4OpoALyA3aN67 zLMhp6zylRFoKJM1R37?CCZkug(^RXZ`n%6%TK{y8liuiQWO6>s!j#tA56r}A zNlA$h155XwN?mysLe+a}v>){g{SzB^4(4(elu_{!roN%ke>#(yFS*)S?NCFcj>-0y z*v?1Jr?G8LTb=Ul{sDgv{9V0coNGXG_bIT0d`>EA&5|UMc0DfRC$IbU+z)`VNt+c* z*}Avpq&G!RCmlIdsT{F~h)TQM7^8;Q2^mO%L3T!gGIeLI^}}3W(M4_=kr$8!@_fwG z%(-u4?zHN3X+NA&#prgemXgfvTLATe{Y1j$z4iJ^yp<*+2uI8 z$Qp&E>>f$Ofwd76WA=VvY1$P>Te9{Pcuo@>vsQkV&~lK2ixUEi%V@d=^fSD9vGKWB zXRd%{(9%F-L6p=xn7(@(V6j-gn*jKn7VGD%E{kc`-btCv_=WxCLbwT}{IR7AmH4AD zkWuM8UozA$FsTe+@IjAmJr5dN=8R1s?A}u1M;Hd)5mS`6q)ob|{3l?d+1`<8qj3$5 zWK_Pz?p&)fv!_4SWyfR}!C){d^ROCSFBOBfTtqDWQoq9@I7VZq#!;e~Q)4KAfAswf zB@l<~lVO40)55}T)=f)OhjMA5!bHFB#SmTt6^z=v#33cY`N;5&7_}z`z568@??Gmx z&ya+7SYQAdmBWVjAow{*P_HP;H!Al-|2)InkD3`5uS*UE;4rAf4d6-D&{bbv#I7_e zH6%W_f^MjUrnJi;2g8V833`VihF^IXdEG%Hx*wLysT;l`diWOa@YS`4^*`t^`;nQ0 ziL_pOLR+pwh`NKZGeY@mZmhP>#^%u`l*d-lyukDhkFG=!^CMyl&Zo!Vk8B&88i%BKQwOh!2=8YwN6lnTdoBc6z7 zP62TJ3BC5XrQijKAUmG~Q|FFO#q6qCid z8uj$F8RRxoQGlk%4Qu%f9Z6rZTgmIsca&+cCZsRMIXD3^BrHz>fML)k}TW z?8!O(fD-=!8j`!7rY&nQJ?+}b(pGaoH&6iyF%o+T#|R@5sgz6GXowO@d-T_J13eNr+X9judA^1QYy8RmO(8ro zf`OlQ1+W54+fO7hnZ{0>On>dH>B)O{j6VzGruJjE(AxrM>fSi^o^tBqtebWXbANyx z%0O(}3ETD%MzG`LMi0KG9EQhXl+Hyn@s7wSQsUpizR4(am^Q)opdEDxeQR_!m*~5d z_`?8@$T<+n?&!n{C|TqgGt8Yu10pQ-<}F&#uIug+!bhg!0Xwa?LJt zj&Xjakc6h3AIgD-O=oPvu6s{xuZ;~jfJJK2cNwm*_c@4@+J1#8BIiCJfJo|1$bYd# zt`Vphy^};A`C&N+V3gfRd;}ojxBU@gH&23@CfJ0J$~SsE0Nj zKtSdIFjGnJ48o7GJ(j?1hz%0)7DlgD_LSL^5m;lwtHk%2U{#6lMafz%Q$?4XQ|A+q z$RGQnh+c)3&)iRwYxMX)qJM7+9#qPYCTsD%tsjwxBp>`R={-E??J0)=U>J^PH|)sq zOW#c{f>u4Bh5~%r!pAAd5+m)ZkP})lXYp?V8Mt!0gNSexo%{^NISLFpfVloE) z*g0AAL+aQ^l5t7;ejOjiCTn21mH6M12VV>(CC+^k;%^@^7^ozj`W7PjJdv?p%q@BY zMiHqSK-WKrlI6Z)#7Zid&m#i}3$&CC7@1(5sa-y*1G#4^TBBPTslOk67z|;B0$8CW zZO&k$hd{zy{|3vP)O9akgWeMuxnfj;5!puo{K0%We|j6m}<&iuqoyZ9+*4rWXbmhRL1wXtU5^lzn$dFWr7b&8IPKKb1rva@^fqDV-cWH)wIM+ zC4PWHVtkD5Ljm$U0e498EsuPWUV@Prf{1mPi5B`XZYgjS@&!WfbHv6LXW4NE5q3+2 zXpyq#*yt;a3)3e0yQBXDR=2vn>5h$U9NRxd{vUsh^~56yY%)2_kvwO_Qj2E}jQyQe zIp~h1HlqIhtOF%;mD@4C0D>CPJRB>@mAweFL$b>Top+?^PUW!`bgICq8K;Zos3em- z4oN0XP|V~>2JhA)XKb(l8#Sryy>qy=aX@a&a##->g9%n__)^k$ogzNxp{J!cI5*{c zU@PQ#yl)~cDhQA;awMh`G8vN5fdpB%97y~VvhN;9yo;>iK;noAJdP}-A21THgIjF8 zfSb_+iT??x??B>L$ad?ApCL=GD$o;0QIKjKNcST#g zC@0PW3YZ1m#ON`#ddcLQXK5{EA2GGe>=F?$np`WaBW7y40|1G;)5Ptgu}8lR=2`CI zc@Vbq_*z65T@jne(JtK!Ux=Z7c|z`|j9PB7v59udts+9|eSF|g;h;Z`4+{)e7p;kd zk2`ZZN@At?9vp{ivj%fz0R*v^gQtsz{PIT-g3Z?DzpFOeF?UsnMeB5+3SHfhSDQA|I>d1 z`KRqy&AQdW^j}dgZ;?1yq395FlqW=|v@W=VUxKMNI9=ip;aMQy*s_<`Ppm)5`rIPj zdot-gNsA?Mm<0o-?nW!p5(S1IY;ATcDr=_d_P`|2n9`T;1 z@I9OlyywYBR`{OBi7X%cY6drbKElqJGR)1dJH84S7UG^n8l&tZ+t%w^2s z-(&m^&pguKt9uJPS?(@f`&-XzgWeGkwMOgiLoH@6Aj<@)M9 zVQj?eIV_6hPih*X>I3*QN4ZJ;oG+zy?xv9k`K|PR61yI9m0n<>Wz6F4pihED?N|n5)%&TPx1Ed#WzqCupVGn)UAuxvwDRZp4P6B{M zGBxDDcR=Qm@)Y*wQE+Z0uS^fiO0tT_4Y++p144DQ_bmv6_W8XB&4PO{mmofpIv2me zTsHE7MCLIF=<|Lt-Li1IaR*QIL>*4@FOo4H|d!ke#FGb?VI9{zQKt8E98i_^{6znHi5)!z&|wR-!34dL0m)lbQ%M^%x>q)jJ$EOuN-6jT2<C?*)Zxpzf^6FA*9dnxu%PA@F%OK;9vF};e36sBnGsz$O4=6Lo z65(R@aAoXqNW0RoFFP8YHMYudF8hV%*7q4J+4~RY>upgpRy*~3Dl%+F)5q*C!mzLx+$)m<} zz}VaTzO&qp*;^t)7Hcy-bsk|F>{+YVk?HZ1p0@x4yr)O9zzgzWyNVky+zOpkUbrl4 zs+1jNdmfHmPd;X6TYBD7**`t6+{&M|@@K64MJund@+vFeWaV)yf8NRuT6vF^KVjw1 zS^0OZ{7EbCxAMJK{E7>;_rW#v^?e#*)x z%pB_zZHP?f8GC-3f3ht=XVd3=3b5{AUK3ouU%(Rm$=3W+?P!6vNJnFM>EcCAfq+^5 zLRMLmU{N1YwffdzhcD3DRNwBy?{=%28m@2g1)BnDScnyCtJmCqn~7T{)`_)9wW3D; z>h=oMs>K>%!tWGrF;7JBi^Oj5c8i6g9eK3~3BS-pr|^leP{k%uDOyD%$|2yIZJR(L zB--%f6G730zE!fl5%>{_1wq6L5e1!)SdTo2Iw9Ho-D0!Yg1+~F3MmIsUX45?DJ~Ze z0^WpL7jh3UeWFFSE`l`Vz6g-0xC{9#u>qXJpwfyK(kTJ-cCi?xV(|sMtI^&LX;k#v zEO8eAdK)lGkmdv1jh|IgV|7w@Qf@&n4Snwvx8jHTk_#mWN(nL&QmO|*dlBF&dfbY> zv(B@Nd(r}K;6%{F2g)iqNAUg{v{BL9w4xhasL4i|-ws*b7FXITAbYh-t2g8AgI*Ev zCeP_I(yy&zE4=Q;o3$wN+Kb>lfVMXAfLIO+)Hw{>*5Stu7-J*TUqyc}Y7vP;DVrzT zG_=*o9)578l*PcCFR76GbcsosQPvC`a-#I~10|k`AJQflA1DPR+>LURjF0nUiIL_8 zZi}?c^w^`dgHqZplN_Rw10(ZdTp0Onpx9win;y4*Y4^6jh#&305O$|tQD{Z42f%?b zK;1Z&8X-NUVOvHjRch=8rXSEo^bO<3)W>a63Qua)HjO?bLzQF992J|viCi86rDAct z)G#PzXt6X>0iBQ55bEw}kwD8w0BZt8KgvEm)H;2arKveM}oNnm7hcJ*VhUd(-9_3r(#Z)BR6LsikS%^L;U8ZN?Qx1oa{o^G2yt6RbJw zyx65jNz)u#CT*^9j4aL=Tw@s7)R2+|04LREBn^GPh&m&J-ZIB-W(;31SJMitvg?6! ztGIhwbdV}dJ8@l6|K=5A`T2=q9+3sH)nyFimO3L)Hi9Nu*hnfgjLP=h}z&o!BA(=t%k$E z5~9A%r?sF`JpWGjya@jaZ(gK$Uh@L?yyp3CcX3$Nu&paa1!}JdH%HslpyqB0wfTT4 zX}F8a*Hx}qvv&Sea|AL*1HN#pw#8jsz2-}36YDzEus;y$bccPxW;N4w-3IUaRTb+t z&KET;_%lE?5)Ei>{E46z3b`X~zCZva`3Hh(I~u`SZE_dat}V~>5~3!g`2y}hC=zjp z{O(9=bFkIlioYyuRNcij73-?2!Mn;A)>{88R+WW?KZ(PI$1O;!ky?_Edsf8Lt0XOUwAz)t9B1YWD_1WC2LGy>z z3~ocCM|3xM{v&($Kq%P!V4IYRl3n<8Wwo_DEJQJarY8&5@O7MR1Ge>RQ++kS7DR-I zXTb`$;%@)}b2I3yrx>tdz%KS21^l;26G*c(AwGx1XEE}-kZO=NBWXxINY5hmBOOJ0 zA87(KNKYaC2g}>QET7+~LQVr6B zNF7LzBJugAl|PC6Z;@U`+J|%q=>*aQl6?#QJQV4(NJU7Ck(MHPknTaMLHa7vgGe1n z+mRke>Op!6=^3OSA-#;$kMt9ycacVr-bXrxbPmZ0-xnack#0s>g2d-8EC1AA|8M#0 zQQLa8xeW8B`ek*Ccudy9s!wCp+qF@AlaO*XfG_zCzCcvnD1JbQoaH2dNa7{Js#R?Y z;uVtUYWeLix<#2Jx~esxR{Gl8F_EHIEDO3(e8;L)g+q;)Bhmi61*r*z0xfEw{r20c zgTD4iO9*3nzc7U=!|H;0K2WVTa^4ib6ke_jcrvR8fjS*jA-GgL@o0*wy0vA zZB;0YwXYH^ea%o$RNGdCRn=N*MVIYMs#X?iYl9*ctwA94nUG*46i}-hff`&H_50QE znxH=<#0#=D8V+NTvNREYcOh&|6KqDxCT2|w%+my*c#}|Xu%k5`3UZCzfaTM-IRL)z zv4sY##uV@E62D`;!WYrJ<`RCE^~z9FG@ue|ElyvsQTWq^Q0RDW3Tk=?nDg!EPmx!c zixlQQLaH^an$dP@w?aH1AzLDv+E#=0Z$*ThSQ0I4G1Vvw!2|rG^@xPEHa=7q!g?vh z0sERDyoKYTht=~PWXq{geH|BoMNZE2jn9niIy6yGhx_R=F*gUXwV_q*T<51;VpYz% zb~Tvs!acHPiip3G&9UIO-?pkX9MLw4`==mlAwFm~=g2Ux!j6S8g1v-P1V>r_J!d`k zC~aznT7M3LA9^z;%CvC6G-_D$9_QJsgs|G6d0y7!?P^rp z8Pm`O5iLp`)d5v)7iEs>XassSSM0?$_5u-OFbtQJLVXQDea|AF(8{699fp^VvJGZ>YWjUY*QNBskw?=%LwnZ$( z4i{&Vp`hB;s^MFxzP?%2xGVLAn@WhlFmcw`BZ|t-d{~bz(pBM_YX~C|hr|*LOm0iYabFxfM2l5c~2P zyKi$StceCYVjP%|+HP{~visY^twGH%zK#;;p>3;K0_E*?KL>>PhTY#u6T<^`&~I$< zg~j*mt&z?~Q{xxx++36H9($YG*4PgFzhMVCiTRG5`)D6Nfd0h}m%#Kw{Ht9Hbz=XH zfE52`@5l&&aoX7!k^See1au)>Ih~e_c9AuO)`ai@(E;zuPfPA0v(k$44J)d6N?cx1 z!F@#m+Wvn?U-qtF>#fL~j$^+Hz2%v^b+*17_5T}Fwv1Jfv3j+)f@)aqqYFQMJ|Py! z&-YAf(tz^F%~N%rDdZ!4`g}4ia8-{-%tfled4jw9Q$K$)z5MZ7P5tjJoBUT)h1lq^ zM>gDs`r?bg@!NFr-#y*;G|m+4>}5V9_9ZB6z%6J!-d>zk^A?zQ!}ZA5;@)$eMEnQG z@6X}4M@0@2pG+<*^5w8vrXY7oKGir=4P&>*+l^K7v|9z`puBm&d5OeXjQ>luSS&+Y zB34p<+!THe^vZCG%)4asP750=oQeNu?_6;%BxX<2