From 60df33bb235891d3bd4e2fe9ef275e0a08b30798 Mon Sep 17 00:00:00 2001 From: BullyWiiPlaza Date: Tue, 30 May 2017 11:03:23 +0200 Subject: [PATCH] Add the old memory search back to support the VC injection tool again --- src/common/cafe.h | 106 +++++++++++++ src/common/fs_defs.h | 3 +- src/common/kernel_types.h | 10 ++ src/common/os_defs.h | 100 ------------ src/common/retain_vars.c | 2 + src/common/retain_vars.h | 1 + src/dynamic_libs/aoc_functions.c | 1 + src/dynamic_libs/os_functions.c | 6 + src/dynamic_libs/os_functions.h | 5 + src/game/memory_area_table.c | 2 +- src/kernel/syscalls.c | 1 - src/pygecko.c | 123 ++++++++++++--- src/system/exception_handler.c | 4 +- src/system/exception_handler.h | 1 + src/system/software_breakpoints.h | 249 ++++++++++++++++++++++++++++++ src/system/stack.h | 30 ++++ src/system/utilities.h | 24 +++ tcpgecko.elf | Bin 141988 -> 146828 bytes 18 files changed, 537 insertions(+), 131 deletions(-) create mode 100644 src/common/cafe.h create mode 100644 src/common/kernel_types.h create mode 100644 src/system/software_breakpoints.h create mode 100644 src/system/stack.h create mode 100644 src/system/utilities.h diff --git a/src/common/cafe.h b/src/common/cafe.h new file mode 100644 index 0000000..b6b6d1c --- /dev/null +++ b/src/common/cafe.h @@ -0,0 +1,106 @@ +#ifndef TCPGECKO_STACK_H +#define TCPGECKO_STACK_H + +#include "kernel_types.h" + +typedef struct OSThread; + +typedef struct OSThreadLink { + OSThread *next; + OSThread *prev; +} OSThreadLink; + +typedef struct OSThreadQueue { + OSThread *head; + OSThread *tail; + void *parentStruct; + u32 reserved; +} OSThreadQueue; + +typedef struct OSMessage { + u32 message; + u32 data0; + u32 data1; + u32 data2; +} OSMessage; + +typedef struct OSMessageQueue { + u32 tag; + char *name; + u32 reserved; + + OSThreadQueue sendQueue; + OSThreadQueue recvQueue; + OSMessage *messages; + int msgCount; + int firstIndex; + int usedCount; +} OSMessageQueue; + +typedef struct OSContext { + char tag[8]; + + u32 gpr[32]; + + u32 cr; + u32 lr; + u32 ctr; + u32 xer; + + u32 srr0; + u32 srr1; + + u32 ex0; + u32 ex1; + + u32 exception_type; + u32 reserved; + + double fpscr; + double fpr[32]; + + u16 spinLockCount; + u16 state; + + u32 gqr[8]; + u32 pir; + double psf[32]; + + u64 coretime[3]; + u64 starttime; + + u32 error; + u32 attributes; + + u32 pmc1; + u32 pmc2; + u32 pmc3; + u32 pmc4; + u32 mmcr0; + u32 mmcr1; +} OSContext; + +typedef int (*ThreadFunc)(int argc, void *argv); + +typedef struct OSThread { + OSContext context; + + u32 txtTag; + u8 state; + u8 attr; + + short threadId; + int suspend; + int priority; + + char _[0x394 - 0x330]; + + void *stackBase; + void *stackEnd; + + ThreadFunc entryPoint; + + char _3A0[0x6A0 - 0x3A0]; +} OSThread; + +#endif \ No newline at end of file diff --git a/src/common/fs_defs.h b/src/common/fs_defs.h index 0b71bb3..c5aa881 100644 --- a/src/common/fs_defs.h +++ b/src/common/fs_defs.h @@ -2,12 +2,12 @@ #define FS_DEFS_H #include "types.h" +#include "kernel_types.h" #ifdef __cplusplus extern "C" { #endif - /* FS defines and types */ #define FS_MAX_LOCALPATH_SIZE 511 #define FS_MAX_MOUNTPATH_SIZE 128 @@ -31,7 +31,6 @@ extern "C" { #define FS_SOURCETYPE_EXTERNAL 0 #define FS_SOURCETYPE_HFIO 1 -#define FS_SOURCETYPE_HFIO 1 /* FS data buffer alignment */ #define FS_IO_BUFFER_ALIGN 64 diff --git a/src/common/kernel_types.h b/src/common/kernel_types.h new file mode 100644 index 0000000..3b04fb0 --- /dev/null +++ b/src/common/kernel_types.h @@ -0,0 +1,10 @@ +#pragma once + +typedef unsigned char u8; +// typedef unsigned int uint8_t; +typedef unsigned short u16; +// typedef unsigned int uint16_t; +typedef unsigned int u32; +typedef unsigned int uint32_t; +// typedef unsigned long uint64_t; +typedef unsigned long long u64; \ No newline at end of file diff --git a/src/common/os_defs.h b/src/common/os_defs.h index d171c3d..48a4c8f 100644 --- a/src/common/os_defs.h +++ b/src/common/os_defs.h @@ -5,106 +5,6 @@ extern "C" { #endif -/*struct OSThread; - -struct OSThreadLink { - OSThread *next; - OSThread *prev; -}; - -struct OSThreadQueue { - OSThread *head; - OSThread *tail; - void *parentStruct; - u32 reserved; -}; - -struct OSMessage { - u32 message; - u32 data0; - u32 data1; - u32 data2; -}; - -struct OSMessageQueue { - u32 tag; - char *name; - u32 reserved; - - OSThreadQueue sendQueue; - OSThreadQueue recvQueue; - OSMessage *messages; - int msgCount; - int firstIndex; - int usedCount; -}; - -struct OSContext { - char tag[8]; - - u32 gpr[32]; - - u32 cr; - u32 lr; - u32 ctr; - u32 xer; - - u32 srr0; - u32 srr1; - - u32 ex0; - u32 ex1; - - u32 exception_type; - u32 reserved; - - double fpscr; - double fpr[32]; - - u16 spinLockCount; - u16 state; - - u32 gqr[8]; - u32 pir; - double psf[32]; - - u64 coretime[3]; - u64 starttime; - - u32 error; - u32 attributes; - - u32 pmc1; - u32 pmc2; - u32 pmc3; - u32 pmc4; - u32 mmcr0; - u32 mmcr1; -}; - -typedef int (*ThreadFunc)(int argc, void *argv); - -struct OSThread { - OSContext context; - - u32 txtTag; - u8 state; - u8 attr; - - short threadId; - int suspend; - int priority; - - char _[0x394 - 0x330]; - - void *stackBase; - void *stackEnd; - - ThreadFunc entryPoint; - - char _3A0[0x6A0 - 0x3A0]; -};*/ - typedef struct _OsSpecifics { unsigned int addr_OSDynLoad_Acquire; diff --git a/src/common/retain_vars.c b/src/common/retain_vars.c index 36e1192..05585b2 100644 --- a/src/common/retain_vars.c +++ b/src/common/retain_vars.c @@ -1,4 +1,6 @@ #include +#include "fs_defs.h" + u8 gSettingLaunchPyGecko __attribute__((section(".data"))) = 0; u8 gSettingUseUpdatepath __attribute__((section(".data"))) = 0; u8 gSettingPadconMode __attribute__((section(".data"))) = 0; diff --git a/src/common/retain_vars.h b/src/common/retain_vars.h index 9a50978..e8c353f 100644 --- a/src/common/retain_vars.h +++ b/src/common/retain_vars.h @@ -1,6 +1,7 @@ #ifndef RETAINS_VARS_H_ #define RETAINS_VARS_H_ #include +#include "kernel_types.h" extern u8 gSettingLaunchPyGecko; extern u8 gSettingUseUpdatepath; diff --git a/src/dynamic_libs/aoc_functions.c b/src/dynamic_libs/aoc_functions.c index e3449e7..a9faeec 100644 --- a/src/dynamic_libs/aoc_functions.c +++ b/src/dynamic_libs/aoc_functions.c @@ -22,6 +22,7 @@ * distribution. ***************************************************************************/ #include "os_functions.h" +#include "../common/fs_defs.h" EXPORT_DECL(s32, AOC_Initialize, void); EXPORT_DECL(s32, AOC_Finalize, void); diff --git a/src/dynamic_libs/os_functions.c b/src/dynamic_libs/os_functions.c index ea07ea6..04f90bd 100644 --- a/src/dynamic_libs/os_functions.c +++ b/src/dynamic_libs/os_functions.c @@ -62,6 +62,8 @@ EXPORT_DECL(int, OSIsDebuggerPresent, void); EXPORT_DECL(void, OSRestoreInterrupts, void); +// EXPORT_DECL(bool, OSSendMessage, struct OSMessageQueue* mq, struct OSMessage* msg, s32 flags); + EXPORT_DECL(void, OSSetDABR, int, int, int, int); EXPORT_DECL(void, OSSetIABR, int, int); @@ -119,6 +121,8 @@ EXPORT_DECL(void, OSFatal, const char *msg); EXPORT_DECL(void, OSSetExceptionCallback, u8 exceptionType, exception_callback callback); +EXPORT_DECL(void, OSSetExceptionCallbackEx, u8 exceptionMode, u8 exceptionType, exception_callback callback); + EXPORT_DECL(void, __OSSetInterruptHandler, u8 exceptionType, exception_callback callback); EXPORT_DECL(void, DCFlushRange, const void *addr, u32 @@ -265,6 +269,7 @@ void InitOSFunctionPointers(void) { OS_FIND_EXPORT(coreinit_handle, OSFatal); OS_FIND_EXPORT(coreinit_handle, OSGetTitleID); OS_FIND_EXPORT(coreinit_handle, OSSetExceptionCallback); + OS_FIND_EXPORT(coreinit_handle, OSSetExceptionCallbackEx); OS_FIND_EXPORT(coreinit_handle, __OSSetInterruptHandler); OS_FIND_EXPORT(coreinit_handle, DCFlushRange); OS_FIND_EXPORT(coreinit_handle, ICInvalidateRange); @@ -300,6 +305,7 @@ void InitOSFunctionPointers(void) { OS_FIND_EXPORT(coreinit_handle, OSIsInterruptEnabled); OS_FIND_EXPORT(coreinit_handle, OSIsDebuggerPresent); OS_FIND_EXPORT(coreinit_handle, OSRestoreInterrupts); + // OS_FIND_EXPORT(coreinit_handle, OSSendMessage); OS_FIND_EXPORT(coreinit_handle, OSSetDABR); OS_FIND_EXPORT(coreinit_handle, OSSetIABR); OS_FIND_EXPORT(coreinit_handle, OSGetCurrentThread); diff --git a/src/dynamic_libs/os_functions.h b/src/dynamic_libs/os_functions.h index 6383b80..77d3d8c 100644 --- a/src/dynamic_libs/os_functions.h +++ b/src/dynamic_libs/os_functions.h @@ -26,6 +26,7 @@ #include #include "common/os_defs.h" +// #include "../cafe.h" #ifdef __cplusplus extern "C" { @@ -148,6 +149,8 @@ extern int (*OSIsInterruptEnabled)(void); extern int (*OSIsDebuggerPresent)(void); +// extern bool (*OSSendMessage)(struct OSMessageQueue*, struct OSMessage*, s32); + extern void (*OSRestoreInterrupts)(void); extern void (*OSSetDABR)(int, int, int, int); @@ -232,6 +235,8 @@ typedef unsigned char (*exception_callback)(void *interruptedContext); extern void (*OSSetExceptionCallback)(u8 exceptionType, exception_callback callback); +extern void (*OSSetExceptionCallbackEx)(u8 exceptionMode, u8 exceptionType, exception_callback callback); + extern void (*__OSSetInterruptHandler)(u8 interruptType, exception_callback callback); extern int (*OSAllocFromSystem)(unsigned int size, unsigned int align); diff --git a/src/game/memory_area_table.c b/src/game/memory_area_table.c index 20ea351..5978ab6 100644 --- a/src/game/memory_area_table.c +++ b/src/game/memory_area_table.c @@ -17,7 +17,7 @@ #include #include #include -#include "common/common.h" +#include "../common/common.h" #include "memory_area_table.h" typedef struct _memory_values_t diff --git a/src/kernel/syscalls.c b/src/kernel/syscalls.c index 474238f..03bcca6 100644 --- a/src/kernel/syscalls.c +++ b/src/kernel/syscalls.c @@ -2,7 +2,6 @@ #include "../common/kernel_defs.h" #include "../common/common.h" #include "../dynamic_libs/os_functions.h" -#include "../utils/utils.h" #include "syscalls.h" extern void my_PrepareTitle_hook(void); diff --git a/src/pygecko.c b/src/pygecko.c index dc4df3f..cbed7b7 100644 --- a/src/pygecko.c +++ b/src/pygecko.c @@ -12,16 +12,12 @@ #include "dynamic_libs/gx2_functions.h" #include "kernel/syscalls.h" #include "dynamic_libs/fs_functions.h" -#include "system/exception_handler.h" #include "utils/logger.h" #include "system/memory.h" -#include "system/breakpoints.h" -#include "system/threads.h" +#include "system/hardware_breakpoints.h" #include "utils/linked_list.h" -#include "assertions.h" -#include "kernel.h" #include "address.h" -#include "disassembler.h" +#include "system/stack.h" void *client; void *commandBlock; @@ -58,7 +54,8 @@ struct pygecko_bss_t { #define COMMAND_FOLLOW_POINTER 0x60 #define COMMAND_REMOTE_PROCEDURE_CALL 0x70 #define COMMAND_GET_SYMBOL 0x71 -#define COMMAND_MEMORY_SEARCH 0x73 +#define COMMAND_MEMORY_SEARCH 0x72 +#define COMMAND_ADVANCED_MEMORY_SEARCH 0x73 #define COMMAND_EXECUTE_ASSEMBLY 0x81 #define COMMAND_PAUSE_CONSOLE 0x82 #define COMMAND_RESUME_CONSOLE 0x83 @@ -67,6 +64,10 @@ struct pygecko_bss_t { #define COMMAND_GET_OS_VERSION 0x9A #define COMMAND_SET_DATA_BREAKPOINT 0xA0 #define COMMAND_SET_INSTRUCTION_BREAKPOINT 0xA2 +#define COMMAND_TOGGLE_BREAKPOINT 0xA5 +#define COMMAND_REMOVE_ALL_BREAKPOINTS 0xA6 +#define COMMAND_POKE_REGISTERS 0xA7 +#define COMMAND_GET_STACK_TRACE 0xA8 #define COMMAND_RUN_KERNEL_COPY_SERVICE 0xCD #define COMMAND_IOSU_HAX_READ_FILE 0xD0 #define COMMAND_GET_VERSION_HASH 0xE0 @@ -104,7 +105,8 @@ unsigned char *memcpy_buffer[DATA_BUFFER_SIZE]; void pygecko_memcpy(unsigned char *destinationBuffer, unsigned char *sourceBuffer, unsigned int length) { memcpy(memcpy_buffer, sourceBuffer, length); - SC0x25_KernelCopyData((unsigned int) OSEffectiveToPhysical(destinationBuffer), (unsigned int) &memcpy_buffer, length); + SC0x25_KernelCopyData((unsigned int) OSEffectiveToPhysical(destinationBuffer), (unsigned int) &memcpy_buffer, + length); DCFlushRange(destinationBuffer, (u32) length); } @@ -121,7 +123,7 @@ unsigned long getConsoleStatePatchAddress() { // Acquire the RPL and function log_print("Acquiring...\n"); unsigned int avm_handle; - OSDynLoad_Acquire("avm.rpl", (u32 * ) & avm_handle); + OSDynLoad_Acquire("avm.rpl", (u32 *) &avm_handle); ASSERT_ALLOCATED(avm_handle, "avm.rpl") OSDynLoad_FindExport((u32) avm_handle, 0, "AVMGetDRCScanMode", &AVMGetDRCScanMode); ASSERT_ALLOCATED(AVMGetDRCScanMode, "AVMGetDRCScanMode") @@ -405,7 +407,6 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { ret = sendByte(bss, clientfd, ONLY_ZEROS_READ); CHECK_ERROR(ret < 0) } else { - // TODO Compression of ptr, sending of status, compressed size and data, length: 1 + 4 + len(data) buffer[0] = NON_ZEROS_READ; if (useKernRead) { @@ -968,7 +969,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (thread count)"); // Send the thread addresses and data - struct node* currentThread = threads; + struct node *currentThread = threads; while (currentThread != NULL) { int data = (int) currentThread->data; log_printf("Thread data: %08x\n", data); @@ -1018,16 +1019,16 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { break; } - /*case COMMAND_WRITE_SCREEN: { - char message[WRITE_SCREEN_MESSAGE_BUFFER_SIZE]; - ret = recvwait(bss, clientfd, buffer, 4); - ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (write screen seconds)") - int seconds = ((int *) buffer)[0]; - receiveString(bss, clientfd, message, WRITE_SCREEN_MESSAGE_BUFFER_SIZE); - writeScreen(message, seconds); + /*case COMMAND_WRITE_SCREEN: { + char message[WRITE_SCREEN_MESSAGE_BUFFER_SIZE]; + ret = recvwait(bss, clientfd, buffer, 4); + ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (write screen seconds)") + int seconds = ((int *) buffer)[0]; + receiveString(bss, clientfd, message, WRITE_SCREEN_MESSAGE_BUFFER_SIZE); + writeScreen(message, seconds); - break; - }*/ + break; + }*/ case COMMAND_FOLLOW_POINTER: { ret = recvwait(bss, clientfd, buffer, 8); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (Pointer address and offsets count)") @@ -1130,6 +1131,27 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { break; } case COMMAND_MEMORY_SEARCH: { + ret = recvwait(bss, clientfd, buffer, sizeof(int) * 3); + CHECK_ERROR(ret < 0); + int address = ((int *) buffer)[0]; + int value = ((int *) buffer)[1]; + int length = ((int *) buffer)[2]; + int index; + int foundAddress = 0; + for (index = address; index < address + length; index += sizeof(int)) { + if (*(int *) index == value) { + foundAddress = index; + break; + } + } + + ((int *) buffer)[0] = foundAddress; + ret = sendwait(bss, clientfd, buffer, sizeof(int)); + CHECK_ERROR(ret < 0) + + break; + } + case COMMAND_ADVANCED_MEMORY_SEARCH: { // Receive the initial data ret = recvwait(bss, clientfd, buffer, 4 * 6); ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (memory search information)") @@ -1263,6 +1285,57 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { setInstructionAddressBreakPointRegister(address); break; + } + case COMMAND_TOGGLE_BREAKPOINT: { + // Read the address + ret = recvwait(bss, clientfd, buffer, sizeof(int)); + ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (toggle breakpoint)"); + u32 address = ((unsigned int *) buffer)[0]; + + struct Breakpoint *breakpoint = getBreakpoint(address, GENERAL_BREAKPOINTS_COUNT); + + if (breakpoint != NULL) { + breakpoint = removeBreakpoint(breakpoint); + } else { + breakpoint = allocateBreakpoint(); + + if (breakpoint != NULL) { + breakpoint = setBreakpoint(breakpoint, address); + } + } + + break; + } + case COMMAND_REMOVE_ALL_BREAKPOINTS: { + removeAllBreakpoints(); + break; + } + case COMMAND_GET_STACK_TRACE: { + log_print("Getting stack trace...\n"); + struct node *stackTrace = getStackTrace(NULL); + int stackTraceLength = length(stackTrace); + + // Let the client know the length beforehand + int bufferIndex = 0; + ((int *) buffer)[bufferIndex++] = stackTraceLength; + + struct node *currentStackTraceElement = stackTrace; + while (currentStackTraceElement != NULL) { + int address = (int) currentStackTraceElement->data; + log_printf("Stack trace element address: %08x\n", address); + ((int *) buffer)[bufferIndex++] = (int) currentStackTraceElement->data; + + currentStackTraceElement = currentStackTraceElement->next; + } + + log_printf("Sending stack trace with length %i\n", stackTraceLength); + ret = sendwait(bss, clientfd, buffer, sizeof(int) + stackTraceLength); + ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (stack trace)"); + + break; + } + case COMMAND_POKE_REGISTERS: { + } case COMMAND_RUN_KERNEL_COPY_SERVICE: { if (!kernelCopyServiceStarted) { @@ -1290,9 +1363,9 @@ struct sockaddr_in socketAddress; struct pygecko_bss_t *bss; static int runTCPGeckoServer(int argc, void *argv) { - bss = argv; + bss = (struct pygecko_bss_t *) argv; - // setup_os_exceptions(); + setup_os_exceptions(); socket_lib_init(); log_init(COMPUTER_IP_ADDRESS); @@ -1307,17 +1380,17 @@ static int runTCPGeckoServer(int argc, void *argv) { CHECK_ERROR(sockfd == -1) log_printf("bind()...\n"); - ret = bind(sockfd, (void *) &socketAddress, 16); + ret = bind(sockfd, (struct sockaddr *) &socketAddress, (s32) 16); CHECK_ERROR(ret < 0) log_printf("listen()...\n"); - ret = listen(sockfd, 20); + ret = listen(sockfd, (s32) 20); CHECK_ERROR(ret < 0) while (true) { log_printf("accept()...\n"); len = 16; - clientfd = accept(sockfd, (void *) &socketAddress, &len); + clientfd = accept(sockfd, (struct sockaddr *) &socketAddress, (s32 * ) & len); CHECK_ERROR(clientfd == -1) log_printf("commands()...\n"); ret = processCommands(bss, clientfd); diff --git a/src/system/exception_handler.c b/src/system/exception_handler.c index 9c7bee0..d3998bd 100644 --- a/src/system/exception_handler.c +++ b/src/system/exception_handler.c @@ -133,12 +133,12 @@ static unsigned char isi_exception_cb(void *context) { return exceptionCallback(context, 1); } -static unsigned char program_exception_cb(void *context) { +unsigned char program_exception_cb(void *context) { return exceptionCallback(context, 2); } void setup_os_exceptions() { OSSetExceptionCallback(OS_EXCEPTION_DSI, &dsi_exception_cb); OSSetExceptionCallback(OS_EXCEPTION_ISI, &isi_exception_cb); - OSSetExceptionCallback(OS_EXCEPTION_PROGRAM, &program_exception_cb); + // OSSetExceptionCallback(OS_EXCEPTION_PROGRAM, &program_exception_cb); } \ No newline at end of file diff --git a/src/system/exception_handler.h b/src/system/exception_handler.h index a464522..d1dac63 100644 --- a/src/system/exception_handler.h +++ b/src/system/exception_handler.h @@ -6,6 +6,7 @@ extern "C" { #endif #include "../utils/stringify.h" +#include "../common/kernel_types.h" #define OS_EXCEPTION_DSI 2 #define OS_EXCEPTION_ISI 3 diff --git a/src/system/software_breakpoints.h b/src/system/software_breakpoints.h new file mode 100644 index 0000000..28c5834 --- /dev/null +++ b/src/system/software_breakpoints.h @@ -0,0 +1,249 @@ +// https://github.com/Kinnay/DiiBugger/blob/master/wiiu_code/server.cpp + +#ifndef TCPGECKO_BREAKPOINT_EXECUTE_H +#define TCPGECKO_BREAKPOINT_EXECUTE_H + +#include "utilities.h" +#include "exception_handler.h" + +#define OS_EXCEPTION_DSI 2 +#define OS_EXCEPTION_ISI 3 +#define OS_EXCEPTION_PROGRAM 6 + +#define OS_EXCEPTION_MODE_THREAD 1 +#define OS_EXCEPTION_MODE_GLOBAL 2 +#define OS_EXCEPTION_MODE_THREAD_ALL_CORES 3 +#define OS_EXCEPTION_MODE_GLOBAL_ALL_CORES 4 + +struct Breakpoint { + u32 address; + u32 instruction; +}; + +#define INSTRUCTION_TRAP 0x7FE00008 // https://stackoverflow.com/a/10286705/3764804 +#define INSTRUCTION_NOP 0x60000000 + +#define STEP1 10 +#define STEP2 11 + +#define GENERAL_BREAKPOINTS_COUNT 10 +#define STEP_BREAKPOINTS_COUNT 2 + +unsigned char ProgramHandler_Debug(void *interruptedContext) { + OSFatal("Hi"); + + return 0; +} + +void installBreakpointHandler() { + OSSetExceptionCallbackEx((u8) OS_EXCEPTION_MODE_GLOBAL_ALL_CORES, (u8) OS_EXCEPTION_PROGRAM, ProgramHandler_Debug); +} + +struct Breakpoint breakpoints[GENERAL_BREAKPOINTS_COUNT + STEP_BREAKPOINTS_COUNT]; + +struct Breakpoint *removeBreakpoint(struct Breakpoint *breakpoint) { + writeCode(breakpoint->address, breakpoint->instruction); + breakpoint->address = 0; + breakpoint->instruction = 0; + + return breakpoint; +} + +void removeAllBreakpoints() { + for (int index = 0; index < GENERAL_BREAKPOINTS_COUNT; index++) { + struct Breakpoint *breakpoint = &breakpoints[index]; + if (breakpoint->address != 0) { + removeBreakpoint(breakpoint); + } + } +} + +struct Breakpoint *setBreakpoint(struct Breakpoint *breakpoint, u32 address) { + breakpoint->address = address; + breakpoint->instruction = *(u32 *) address; + writeCode(address, (u32) INSTRUCTION_TRAP); + + return breakpoint; +} + +struct Breakpoint *getBreakpoint(u32 address, int size) { + for (int index = 0; index < GENERAL_BREAKPOINTS_COUNT; index++) { + if (breakpoints[index].address == address) { + return &breakpoints[index]; + } + } + + return NULL; +} + +void pokeRegisters(OSContext context, uint32_t gpr[32], double fpr[32]) { + memcpy(&context.gpr, &gpr, sizeof(gpr)); + // memcpy(&context.fpr, &fpr, sizeof(fpr)); +} + +struct Breakpoint *allocateBreakpoint() { + for (int breakpointsIndex = 0; breakpointsIndex < GENERAL_BREAKPOINTS_COUNT; breakpointsIndex++) { + if (breakpoints[breakpointsIndex].address == 0) { + return &breakpoints[breakpointsIndex]; + } + } + + return NULL; +} + +// TODO +u32 stepSource; + +void RestoreStepInstructions() { + writeCode(breakpoints[STEP1].address, breakpoints[STEP1].instruction); + breakpoints[STEP1].address = 0; + breakpoints[STEP1].instruction = 0; + if (breakpoints[STEP2].address) { + writeCode(breakpoints[STEP2].address, breakpoints[STEP2].instruction); + breakpoints[STEP2].address = 0; + breakpoints[STEP2].instruction = 0; + } + + struct Breakpoint *breakpoint = getBreakpoint(stepSource, GENERAL_BREAKPOINTS_COUNT); + if (breakpoint) { + writeCode(breakpoint->address, (u32) INSTRUCTION_TRAP); + } +} + +u32 getInstruction(u32 address) { + struct Breakpoint *breakpoint = getBreakpoint(address, GENERAL_BREAKPOINTS_COUNT + STEP_BREAKPOINTS_COUNT); + if (breakpoint != NULL) { + return breakpoint->instruction; + } + + return *(u32 *) address; +} + +struct Breakpoint *getBreakpointRange(u32 address, u32 length, struct Breakpoint *previousBreakpoint) { + unsigned long startingIndex = 0; + if (previousBreakpoint) { + startingIndex = (previousBreakpoint - breakpoints) + 1; + } + + for (unsigned long index = startingIndex; index < GENERAL_BREAKPOINTS_COUNT + STEP_BREAKPOINTS_COUNT; index++) { + struct Breakpoint breakpoint = breakpoints[index]; + + if (breakpoint.address >= address && breakpoint.address < address + length) { + return &breakpoints[index]; + } + } + + return NULL; +} + +// TODO +OSContext crashContext; + +void predictStepAddresses(bool stepOver) { + u32 currentAddress = crashContext.srr0; + u32 instruction = getInstruction(currentAddress); + + struct Breakpoint *step1 = &breakpoints[STEP1]; + struct Breakpoint *step2 = &breakpoints[STEP2]; + step1->address = currentAddress + 4; + step2->address = 0; + + u8 opcode = instruction >> 26; + if (opcode == 19) { + u16 XO = (instruction >> 1) & 0x3FF; + bool LK = instruction & 1; + if (!LK || !stepOver) { + if (XO == 16) step2->address = crashContext.lr; // bclr + if (XO == 528) step2->address = crashContext.ctr; // bcctr + } + } else if (opcode == 18) { //b + bool AA = instruction & 2; + bool LK = instruction & 1; + u32 LI = instruction & 0x3FFFFFC; + if (!LK || !stepOver) { + if (AA) step1->address = LI; + else { + if (LI & 0x2000000) LI -= 0x4000000; + step1->address = currentAddress + LI; + } + } + } else if (opcode == 16) { //bc + bool AA = instruction & 2; + bool LK = instruction & 1; + u32 BD = instruction & 0xFFFC; + if (!LK || !stepOver) { + if (AA) step2->address = BD; + else { + if (BD & 0x8000) BD -= 0x10000; + step2->address = currentAddress + BD; + } + } + } +} + +void ReportCrash(u32 msg) { + /*crashState = CRASH_STATE_UNRECOVERABLE; + + struct OSMessage messageStruct; + messageStruct.message = msg; + messageStruct.data0 = (u32) & crashContext; + messageStruct.data1 = sizeof(crashContext); + OSSendMessage(&serverQueue, &messageStruct, OS_MESSAGE_BLOCK); + while (true) { + OSSleepTicks((u64) 1000000); + }*/ +} + +/*void HandleProgram(OSContext crashContext) { + //Check if the exception was caused by a breakpoint + if (!(crashContext.srr1 & 0x20000)) { + ReportCrash(SERVER_MESSAGE_PROGRAM); + } + + // Special case, the twu instruction at the start + if (crashContext.srr0 == (u32) entryPoint + 0x48) { + writeCode(crashContext.srr0, (u32) INSTRUCTION_NOP); + } + + if (stepState == STEP_STATE_RUNNING || stepState == STEP_STATE_STEPPING) { + crashState = CRASH_STATE_BREAKPOINT; + + OSMessage message; + message.message = SERVER_MESSAGE_PROGRAM; + message.data0 = (u32) & crashContext; + message.data1 = sizeof(crashContext); + OSSendMessage(&serverQueue, &message, OS_MESSAGE_BLOCK); + OSReceiveMessage(&clientQueue, &message, OS_MESSAGE_BLOCK); + + if (stepState == STEP_STATE_STEPPING) { + RestoreStepInstructions(); + } + + Breakpoint *breakpoint = getBreakpoint(crashContext.srr0); + if (breakpoint != NULL) { + writeCode(breakpoint->address, breakpoint->instruction); + } + + PredictStepAddresses((u32) message.message == CLIENT_MESSAGE_STEP_OVER); + breakpoints[STEP1].instruction = *(u32 * )(breakpoints[STEP1].address); + writeCode(breakpoints[STEP1].address, TRAP); + if (breakpoints[STEP2].address) { + breakpoints[STEP2].instruction = *(u32 * )(breakpoints[STEP2].address); + writeCode(breakpoints[STEP2].address, TRAP); + } + + stepSource = crashContext.srr0; + + if ((u32) message.message == CLIENT_MESSAGE_CONTINUE) stepState = STEP_STATE_CONTINUE; + else stepState = STEP_STATE_STEPPING; + } else if (stepState == STEP_STATE_CONTINUE) { + RestoreStepInstructions(); + stepState = STEP_STATE_RUNNING; + crashState = CRASH_STATE_NONE; + } + + // Resume execution + OSLoadContext(&crashContext); +}*/ + +#endif \ No newline at end of file diff --git a/src/system/stack.h b/src/system/stack.h new file mode 100644 index 0000000..426b1b1 --- /dev/null +++ b/src/system/stack.h @@ -0,0 +1,30 @@ +#ifndef TCPGECKO_STACK_H +#define TCPGECKO_STACK_H + +#include "../utils/linked_list.h" +#include "../common/cafe.h" + +bool isValidStackPointer(u32 stackPointer) { + return stackPointer >= 0x10000000 && stackPointer < 0x20000000; +} + +struct node *getStackTrace(OSContext *context) { + struct node *stackTrace = NULL; + u32 stackPointer = context->gpr[1]; + u32 stackPointersCount = 0; + + while (isValidStackPointer(stackPointer)) { + stackPointer = *(u32 *) stackPointer; + if (!isValidStackPointer(stackPointer)) { + break; + } + + int data = *(u32 * )(stackPointer + 4); + stackTrace = insert(stackTrace, (void *) data); + stackPointersCount++; + } + + return stackTrace; +} + +#endif diff --git a/src/system/utilities.h b/src/system/utilities.h new file mode 100644 index 0000000..1f74cdf --- /dev/null +++ b/src/system/utilities.h @@ -0,0 +1,24 @@ +#ifndef TCPGECKO_UTILITIES_H +#define TCPGECKO_UTILITIES_H + +#include "../dynamic_libs/os_functions.h" +#include "../utils/logger.h" +#include "../kernel.h" +#include "../common/kernel_types.h" + +void writeCode(u32 address, u32 instruction) { + u32 *pointer = (u32 *) (address + 0xA0000000); + *pointer = instruction; + DCFlushRange(pointer, 4); + ICInvalidateRange(pointer, 4); +} + +void patchFunction(void *function, char *patchBytes, unsigned int patchBytesSize, int functionOffset) { + log_print("Patching function...\n"); + void *patchAddress = function + functionOffset; + log_printf("Patch address: %p\n", patchAddress); + kernelCopy((unsigned char *) patchAddress, (unsigned char *) patchBytes, patchBytesSize); + log_print("Successfully patched!\n"); +} + +#endif \ No newline at end of file diff --git a/tcpgecko.elf b/tcpgecko.elf index 71a64bf5b217a7a950bd6412dcf07252faa8083c..423b2e3dcaffb60c01624ef154b6da411aaa7c13 100644 GIT binary patch delta 30860 zcmbuo4_s7b);Rv$VMIjKQ4z;+#F3B%LIl?IGBdzXv^$9vARyUfLttT%QBpgtBlixl z@2b%*`cOkT5^dO8xy5ew!)>X?&3bj$Hg>aKcO#{5*lJ;7YEdEcJLkFg&Og!G_pQ%( z&wZYA&U2pgoadbLJomYmuJ@x_zKd#2P?FH zOE{Bfm4xDYN-Mh~lRQk7e&e!|kMvOyFv1Y?=MjB!OyR?^Oi@7ao6rDff; zc|xk{1fks%k|IwKkaCFjLkV1>B2yFiY%Bqu6ZuY}LaF%uWH0iU`!qyLBapRkGRNEE9ah)hantO&50j%*k^=qzJpEISz@Hr&%C6SHRgVW*XJ zPg?vGGoM~7dG31Zn7Agjtjk_yA*KOIRmi~%>f)PwtbFGpr2e zqlFe@pygP)pF~;7%L`=g(969z$`)R{FUCq<{HIfx`*40E@xnNO@}P|S@gdfUb>*?9 z<0J~YE(&j+bMVa%*U{aR(wDyYoXn~p*glG=TT*Pv@=Peex;!uI$Y|aGHw|#snT~-4 z#YCq^eg@O;0-()6TA&-IqtU6+S((x;m}ti2INfJF z|Mq7U!oR zlZJr1`dBh1PLGnJ;kdwv=;kTcMlWWD;z|L9*OkUae%1$i7^cHh;&f$Vu!Km6bHkNV zuBC>l>CuZPDZNI*T&BRzD=829ca#rNT>2})EaDi8-f$N5=+u(v^hJJ5FpJ2~WB9i} zH`AhNajG(r)=W!Zy5ljYbFKpgw2~cL7Vm_>q}P)CCw+a3cQ7Yn!Kngu_kwm5yh^Ik zB0_~SN< z$!D|3a%J#OkpdhYl#fQuF#3D@!a{#2ATy<*M?Nv~{MN`|b8wamJK6!7R`r2>&e)kHoGTbPOJ7$bN}Y zI%0nk2@|AhsGd4wd3CmdAjjzBe1)UmeGL^L(@Pi>%}PgE#956~7gZxs>0dO)?35Gr zR1>F*FJobn8OT*EnTgErSUeyeFRjToaLLpfw^da>NRPysxI{dG zW-piqCekRB*5rz$(fG>gG}<;hDgLTWFfE8>CutX+`~;=~(Fo6+EEZPlVlA#kJT01U zpaTok0`DbT_AdM5;(B%%iDHtN;J%_v8FsV5$wd3to1iW#Yb z>V+`u45=R~fftHmuw5&cxKIQgZ5rI@;Gu2as}fA{=-9q{a+`F#4SX8)|csD3hJ2p>u<%c~>sv zgP&}42nFxaA=wtt!_sw9 z-V25U(q)~uxQIt^f^q!M%aX-44HP$gjTT3m6+JWz{)wK^8PJmF>V$;M91y~lRo2ae z!HBz05fHq(A?Xo^@Y~Os>+~DgkI}$Z4lA7!R{twjc&v})@AM^O$7K!Lc`s-VG#H{i z43VPIfp`sXz6?>Vte5ejyXMXj7CbS&VSMch7=}?qTG=o%&>)y$94{@!kwRV}2GN*S z{{!s7A*}t-!P67Q>B2HG+$wShV1O*}vy;ZA+e>SVUzhT^oiI2hr_bkl;kx0A7}qiw zB(xYDB(xalH_C#2N8^+-+DtR=<*H4cMBNH`q_8bulMRWW=UtzJ4DaiwXJ$CjLo{Sc z#$@W`svEXF!f5Wt#+je)5qTU;GX4-zzID-;Wr=4`eF zh2g!@w4^Obx>?2+*4FK%rOKp0=dd_wws6g6)(ALhoX>2I!jPj0usLf1KR>^;#^eP_ zpISMLW`Jg5%sdm72X)3*z8D6x=rp!{(Pu3ALR{Rz0>EZhI%FcvpEy`H0@}P0g(ejJm*@bjHl%6aZK5G{)DnQ>mN@eBr1D; zq{~doz(ADWadm<4`sWVI&T9y|2Rq3W^x> ziv41MDJf=!F5{B81Y!ioCDFHX*!56pJx;w~--bR7d|L(cYwIEwvh_Smt;NIBWCO|Zf}X85a)`9jzy!OY=^2!|nmM(%&%m-Xne9)QMV>$=p+`ko z$t_YPN%!R1IYa2c-M~>WXt;Z@6A3d?%{n2;@2$k8>`Rjgb;6!z-)bP>9~3iz!{4L#^`Ev1H8;=H^6L(01XrYY{l~Yje?AR~hB=<=ik#gsm1!{?5$qrsoLB z$$|SV?7man2@b;!`)9;G1x*O=0p^-s13U}RF9&*tUu3cD^a+Ew`T~T7=&H~zTVeQ3 zPmw5CcBL^Ppd19zNH+S!ji3?Ibd0DUGZQs)ZymmVOi$64#k)?$%6JQxy++iQr-xa} zi6({S6lw><6|P)bl9D1>mN5G)G&N-aFXsw_`Hh;yxoYG2`FuDY%4qtT!b}iix zv%Yc@ZoqEH?kx!isWikshiX1A4#EK{Z+@To=7R|w1*v|qzqrH3<{MQ|jY z&`|T4W`5V1LH>A23yvxfe-zZ%$v^|{Z&(8!GAKe$i5ZH7Kk1bK{;)a-|B#H|CJIG* z1DcDK&|7c~AW1)L5UQo{3_aROEopH=rY#SYV_H3&0lJJ8j~D5MWB)H!_^)9Y%uI{u zgLXR!=RB5CluckfcYGNu$uMyjTdrUCo&&>>G)C_1#U4<|PG2v|E8#^ia8~=9x5;?Z zrMe0CI!%E}naSf9tekpR5K*f{^!P`GzArM+ketTJ4wIKeQNvZssCz*v_glz2<8r~n zakDraavW+LcCD;QugKRHyM0BbaONh>=67kb8X&4LruPD*vl4Wa{lgG0IMA%#OnO_fX`5! zm9(C~G}8G1PLl0^R0a|Uk*^v)AoaEFtUO*vkvWXv`|f@cg$?5JgEdw(KVfyeT3P>L zYO#1bdKJ>8q!76xq!xLlH4V2=hw+26F{b$>#`yJFL#`dting;$F!RJ52eW5WXJi+$ zmYXl58S~?!HD?<>`4dP%xH+HgEvd8IbVKc<1B4dC(uAToko zXWIa|LPoBTkW;%ukOR10z-5UuFlIYI`5mblwCB7?%CB7-$C=j+w>kL^5 zDSGS+8*!nefd;#zNn}C!uBXokfIga&c4t?lQqHqIX_=Bs~N>+8#PMFDp*7GYo4j!%Bs7ym>DqUd&Nxlka=Ow~JsP~6Ky88+MVgqH zq)J^uS0w6GlP}WRg>xpubE3sNXZrFx5=GjQn1H(}-HEuhf-Oz;?{g|+F^*Fh8Mh_N zLfwo{Gf?HQ#&rl2-h@efJR6!!$i2!b9Q#MC&~cQ&UdRDRBS&)xH%(c6R7P3IV{-$} zAv92(@CM8zHRj@#&1ASsGLy3&TC_A>HER#ulddzKQ0Ea?Ds+N1YHfC$^TN*$7A@XU z1ls{KMoo4wl!gx-!cKXg2-9-hk#Ws9xqb1~b2>x!R!+p=V41umk=A7=sp3XyVn!<4 zlFCR|4XR;3=IZIip(KL^bVX*WptS~jyF3eN+$z8h_v#+))~Co60m4J$?U=4@U5Mtz zSDo93B3sELilZJezVdm2;<9j_89(^k23>2;GeFlI9&f`2($~;N`pP#<8^gIN^XV&Z zn>J<@$ksnfW>K!@2Vl{1ytFQx5awBmKJ`v5j%~OY$aK%ZHxu#=8 zkHT5WmP1P8(6#`o-1?U2E3HETkYVeH$BP0WQ)pBzH+sL^s9!*EHKOU*kA{L8zaNLo zN;YqjRQ&m7XiR=OTGq+}l$}H>yd@0^Wo@_u2h`Xpld13{iz{t8st;3^l$VX6*hvqz^&8Qk0(;YvPCmFb@8%) z5e{}>yf{ydN5N~^mzk#D7dq39$I?B^(p8!vU>{3|mqn|NS?TC9UHRm}8)INqWr29% zg+TlVd<;6J6fA`kI$#s6WAYK_#xJ34!)?+|sPClk@^!p72T?LJ7!w@S(8~M3 zAE{x*nWQ?#x*|-|1}nDd4Joy0JPwWna{9(Mhz9pig`M5R;^77nZiqWcnjn|SC8Tlt zQ>C@JFc7(R(rDQ>^C)I{COd3-W|SQ;ep+@AT1^xp-gKB?y4(VfBrzCCl;w!4~#ZFjoDG)Lf6>u~qeJRk*sv^ch-qAWfRo{AEF z`kp@nb+KL0z%n|2+gL#(^pOYOgqcRG54P>)^=?BOu6O?x*s*JVM%wLz0aPq|`|N4* z(wf_klO%@(HZU;o@&2XtP_GnOKDC1ZFXnh!ZC+^NxO{Ub?5Rpy9h3N77hwOD@K)0M z7TXj44Y2_S*u#SdJvhc3HyvBS^oEL7>G|W(Q958|PxeO4S=T;)8d8XdEsZlQ$ka-D z)=Kx&kGS$mI+uY<-IfRYa!(&UkNHnXQiVH7Pao~4-bCCkfMA|V*b|>C^oa%XLf_y% zw8^BY&R|m$1<13WB4C*Jeq87~nkYv-HAcOqQ$ws0QJFX}0+LAgF2a?pG7@Z)>*@dT zf@q2GEaPY&?afF^pH!jD_D7DxuFF&4_p3OaB7~ zwJiOPm_Y>*eUB>-&DPFNXMCj{>mx{SAxXLRbKAxF|Pzg#flBnLf^6WMLG~@ z%lSkT1OeC-E`aFXZ36ex?kk!YgIR@?iU_J2#W^Xdh^6D4x6t?ZVLV}xzw>kyt{!n0 zT?%?%Ujq+%x-d^uhE`C&$?Cy~uYsq-9<0Lz6YKHe9ZM#k=(AWlhV}s8b_g7YTJMLA z%D=;dq&(P$;-$y|PaZHk7`X_s?~+H+nDpUd3~?~MMy4Q$Nc-~QqEFNp`c4cYUpkzp z6Gk4xNQ=rrMJW#r{88@UDfN{iZK3Z%3qI5ai?s?ju!D_|fe`Lm%b2*yH2k8p1>l4F zFS}Q*)))cb;D68n4;^^H3r|rUUUY69KHQ2zDD>`8|g zH>XNk#Al@^@PH#dBn>)i0CPU%Nk^gYUkAXs;1Pm#=(&Z!!}Px9utqjvBf@UBYg~;3 zq$JdMm?7jbe^?_;Z=gp0xjDyxKA`k~GWdnQce~`q+vTv4BR|RSi{b?`2`xdm9+wd3 z{|P(Jy%MsDhQ^q8`>`C78~?5y`HJN!XxeNfnPQ}T2$do|Z25A<_`7!3&W`x4vw?n* zuPynC?YCT+&G~W9a-hKky7}!;u!QT;QZFcO^hy$Tk0)U$>k}s>rKndK2gv<_(fgYy zH+hJP1v=rvT1oj=QpoW6@pddioBLQ9un$AW;GI!P3@$AGzca|nm%%xdFoMI%#s)q@ zHHkqfh5Q87^7K1t{%2?NZXxGc7nJeDLBZrN@JQ)2TcwVtF~kcD#^H8FGq?_LI_Ve7 zwd>Da2-lB76fl_6v6$Q{4n)Qm6+Cn*g}!&Z-`}mnkPj6kMUNd-&nxIJqA2gsKmvlNGmt1Cbc~WsfXzQJBOUx%~Rj^1qD8<)*N5b{Go%fHZxDvu}OB zO3xdYl!Ya~af|+6E@R)@8;Fm+o4~=36z!Pe~ z+#NLWrzLP~u(UZcR|GfDLqJ9iV_J?$Hm2k(vgH-UV45Dft~dzi$}+ANn3E59i9^ZQ zFjE;kHfjA7)`oDH3LcHtVgLe{J7Tin^|TuD5)Pu(J{Ev~ewmXTFy$XjU_+EjKCyOp-_ifWees`r1bODPADQn-Y&cAV$GD93;D{5J!h|1K%?9jO_IS6T=! z0X&{8+9jDLjtnqLhF=;m(~QF`F@)Lo^D+RhL`bamDZC&RhJ;ai%B}wonQ4KU9+=q` zWSZmmhsKm>LkMe~e z^#g*^5c&Y7emH~x%wQ|w9~I*WR-oYjz;gi>;8g&;0yb6{P(RMx|D=~ZFoSah|6qO( z+|MC;a(R5o=l=7|N|aZEE3lWdw$laQAL0MIR`zNV+s|Ihk*D7OjqP76{A@MLb$5JL z9L`obc^HuPi{$a7Rq&+n_wA8v!_A|n&LVhofiX0r3E?=pJ~|#-1y7Rxpb|ltu7#67AdmXO_Wf-CqDP!U-(TB6nYcA5FYr!2rfB0* z{|pABH**)LGLXiSAgi-U~`Ipxai*%r%(|gBPP;dZyi!T&>duQQ+ z$wJ>-PTr0OcGmt;Ie$~=``baxpOxW3tFV$!{TWyI@L)aQUbgX2t2)mk-&$$hu~(no z`vZD4u3d#rwm*jj*H33Dd;udML*DhhpnI4$XA|3QF1mf*Jym+IrhE`|N&_3=eu z9U2r-!`2mV*P=o|A7Ch`j}!i+z}%@EOOZwo@Yri0Fv`uBb|iR&|Ex9JAt9H}iL5|e z!AkC&10#D9vcE}XIa)r)0tg)^(}jl5V&UM#yknzq1wqEngFqnh zsTR0`#HX6!3OzaHgewSmsvfQ&;Hg@;f`F&0;R*ttvPnmNc0b6@`n>*bDeQZ!fVaB3 z*;`#MV#yQXy+e;}iD@GIgYOm5(W0cR3pLv-i9Q#GXgj>xl?yoApC*a9opAk_q!~5k zhv7}Ij68U-O&5?QChblKEiF!iA-t72k-Py+H_H#e$q$nKxALQ=jkYvQPmLQcw3%MB zS&ePw116tMZG7cImuYo*pE2{oUUcbb6Zr0}DZBK5l~leX%zo2hgk30DPB>gJ)Y)W0 z^|nc{??@mwuQ`>5c4H+~JK^yLS={8KXNs3iJ(aNyo*T$D#dJwYx~ggctuBGrBISdm zcbul}CFwIxB}+(myn>dNy}Z1i9xX|odWQ?1sSweLYX9SHRI@TwcgGE`?7#+RL)8xX zWYHa&v~=Y%;Qgs*SR>!_?A|irK@bS*ljZtaxn3*3iP^1m8(-&QL+~+KVI{ZA zuTOpjJ2v($yd7VjHzTFKV5jRap+g)$w!QFrT&@*8#RRRgmqzfqT1dTsRaZxn;{ zzft#<72l{!ha4W>D^k`Ch^4$yr<6&OeN~Op(7w{7=;P~xnZ;p{_=>3&hfl5&Xg5yULd0rN0c``E8+5o*^`)4fi3%otch$_PKn%_e zi-1W#9bT0hQuK8c6wRi26``14^VD7uhFQljUkb-;qkZ9+y$o~yEnz%iVRcI=&k=Y+ z4z2$>40FUxyTdVSp=dVsg=4l+!|E`e2N>pkt3z8KIZs=|FuzG-n8(8~zp37SYbeWa zb~D7AZw+PnO+T#*!~9myFy9En{I-A&gk#o2(QLZd7RvKmI5uPpZT$-i!+a_nbC7z& zFu&6<%xSlU^8C(9Gj0pz`CT2u6v8n(X=ga*L56uI4D)m=)vO8Sd3rAu*UYB3uL*5^ zl!xL_%+YFw`ED5IX&W65!~9|e!_2!ql;@c!YQH^<=Xr+tQW)k%1MRzAdUfz(2G!e* z>@C8toYZF5Nnj^+Dqt`5DBwxjD}!HaX}}(0&E%qN56UgzE2*_b%?fCtHU)H2rvmPy9tAv4du8z3WZJKQ5bP9C zr1~m}@3-)^2n7Ur6>yk3W$?R1>QTT_+N*#f?N`7(bW{P4Qhl|=_q$PQmcdaiwJBf$ zbt+&z^(bH)?Nz|Nv|j;_&`}w@NT_}-xK0PPtkuo9SOlRX2BR)ISNPC@lZsEqExlL) zA2^6%MGHub^!#VazC14^ZRj3zOGVi|;zj{}bpagdMJGM{q%L8K$8@Yzl+gTMG#G6+ zqS2{$#wzX--%#Ckv5#il={G!9G!_L#v#D^W-$bS2CA2dfvzlR^3CC=sn!Cby9-!h~ zFqQ5K<2ee&p_slzhWTz7rVpl4IHreT=GBDq^bJvaO(;(|SBR~@6ov_@Q(rh{3&Whh zE|e!ZKrQRSc#bg4Ux#7ByS3e6m|O)Efu|40+)NF3hw|i(GR*t#4sBhfrLAF@Dv9Uu zaLmr__k^-kon$O;z9*Dr#1dK;h8a=MFy9Er+(QS#G2u0ud%)AzhVoSFX;E!x>*`X5 z`BXS&GxdgJ9%h)+?hWNRA%5%2 z&-J0LN7g}c80H>^`EEGoARP|FoCxRffM?!)p*$zrsQtcBo)g;`=1bw2M`&LdW>gHr zoL?8pGs;XYb)h_?P;^K{!ZAB(cR1z|C<5~g!<-aL4fm%`k+Uu40VdIx>a_H}`$HR_ zhv>iqxh(EYCUnR% zm*#Dl&2BH+scnPg$1iTCP6gacJqkETdu4Demi8-P0UcGqdaAFNIE-~svjQHaHW~Cq zQKtg()T4m)v{wOpX}=7@ryoWYa0%5vDDfpV)U1G=)TV$(s8a^HDC$u_p7ttW4eeLJ z-E>p|hpGM{iLWYynia5~+7z&tI%P0IO+5+-5nKT`(|!f?(oqE*q57XmeAU2L0jsG^ z0U_g1z(MMf!3hT1tAIAzuYevps(=t_9TMM24K*v^3TjipX6jTxFZC$k28gR4@-O}o}^|OjM7t^0a0PWN=a%9aX?Os{gsfcTzVs zD)Tw}0>QO+E_9~#4_WwLLVR9k_e*;GKZFyx5lMix&r->d{aIWRBZQlW_ zCiBLGcv#>-kmq+zULrjEIeRY?-sWL%2j+KZK;;{C3AhHDT#Jpvwr*by3@`?P;ijA;crPk^qOrZvswIsBks=ZjYZXb_I=Tp38Ly=q}?DCZ7bly#cyz>IAxd0lLBQ<(%9NbiIDM zS&4KA=)8WqS@z4&Ie_j!fUcF=eleFj7@#|R89EEl9S+cq(!)U4=ckKfTc(%lWpWhI z9r4q}l~VJ6&4pZFvFo_Y=q0)j==uY6@a7fJ4Fu@=$Cop@9q0!AbhBfq{+DyPlYY9{ zOD;oa2fCpEosG8qQd=G5N;rPk>~`6eoCn-t#g)LJTE*S4uLT0$UT`-fSExS>^`lCC zK(xyY^(2@A9Px{$O{BKXIu)n(6Kb8~_h}N$0Z5{hxufl+hd0lq!p7MwC~7Y=<0ioj z;52@!IZLQ{OK?z}bD3Z>31$GN4bXMbJwT_&f^>|obNbObP*S$W&xdf%P6J1s%rOwL zG9^~+ngkoAw{r$su=TpkZah$b^;T6p76 zfD)q#s|!+O416}KMW>2sr?wWIG3H1p=CCHfWr}|@9&lE$7`RM1 z5XMCeU`|zs)p>&ImQcf^I@MHIDLe}Al6t~0hl4QtLhH`P1nQ<~Xe)4;W~F<8%e3Y& zOnVUK!LYg@S*DF{|CLUq;i>*tI-@3F$E@pTgGAA=j3O{LUj~Vy8KHH+W%?4@0$irE znG}Q>B+B%zP*J{g2E?2Wdl0~72Bhb}WkyXHW?ukvMmU$Dpt_^9=+`>cOhRjatuxNd z2<39l62P2U9ad)xs)G-SA(sQRAGwT#VRi>FV_Ad<^!D80pgJ4PXw|7=Tj`2coiWxM z%H_Nv2y-;7&K#(l#nVpUGOLdE0+(4~eLvqCgvp{uK$i1+0(EgQRI^Q|isNb8Hk~n! z#nAxf1rmhW5-Q6DeNY|5X5<2&b_Fi8wPBcb0nFK!u)3C@I(SU8O;S);Iw_zzFnu96GNTbb;dd2T!IE}PMEX#Drn&5?4y0#b;6use?#Ec z3w>X!f4Fm8Q&;F4DUk3F41yDso$s4R0G6Gv`Y6D%Grgt$5@$L)g<1eX_NG}L#hbdt z2VphWO((BKo?K-LsSWUqiV5&+#&+en&>;p`<@5p!r#ill{UP#ELtBCXpI&gmp6h;+ zVe-PsK8~1CHSpQmC47~RXN%tfdFMN)%)x?}OIiR!J^Kb8Zfz^;7o_EP>55=$Tr0vu zZf1y=*4v>zmet=bZ>fhdr~?Ge6&P&Bz4!2X6cz=5@o!i!qysJlGcR>Vdor~Cl5wWqU9 zIDI>9ZI`x8bltRD0Z-CC8Jr8xOBIl(nkOXOxptbNfX%c>0paCe1w24o6%anPAcJ}} z?Nh)+I;?X=tY1NMTOfU1=*k0u`JEXlzJv{l`A&EFIjSeWFeY?jm z?7ZG_g`Ibh)_CIMR>fTUTxi}f^#J{<80z&TM8nB}s(GWPV{kj<$&!RPLKAl;;n&eu z?9{1ty#I2|P8Bypy9tjJuya10_?>mSjygLYQfWCjO`wGrnr9EbtafeG~=0a)j>7g{LIhcND%T?+@#o1zMIW` zIK{(P*$h@P<0D9e@z`@0;x!#B->u-nStd2UV2`gT0^TchMOS>h&A>O?*r%{i3?xM~ zab5XtKylE`U0FZ*wg1>94jCrjFq+l;u6P!V@GwRA0f+Dd7OoVVZTqe)m92s<+0~$m zH`Bel60YqyY5Y1q+4NeO$F#bv(?kf#wZkjnBK)8o_OT9-6pq|j;lnh)z@t1(9lnvK zT;F+12gk#)cgM@xXR|nP0_^(*aEwH(hi$)#-@Y0NEMF_@q}tu(!l)fPh5F4-fsdjP5=4m9712uf zN3?HuMbvG7I--92#}Q7tAflP(M{K4~N3=oDC8VUXLQ3$P4QKEZ6_N=_&YX0cfM~xIo(RgH}!RNlSi* zDiN6j;u&OWf)^}gf~WD25)!~^ID(Ii7d;N|Iha(>7GR-i%k5kp3WAo30?53|*c+PLx!S9D!iOP*4o<(g9p5(PI$6&W|LVZT@jEm! z!9pZx0wGZd(`xvu1FsPdg@9MU+mNQoN80%vsy?KXXttg;ZB$+KYvDKu55@~xR0}8I z>)`X@16v>vBzH0L*G;zWM`Tcy#@PTy=OeL50Ek*13=0tT&bxIA^T+r~e3=E#AHmE4 zPZDxqN`#r=f|&uwusB?SHo}Z(gc-5P;3ELf?lez!z?Y5t zc@YmIes>VAa4POQFaG%e^dn}GxS;nc{1XCs1E8V2vR1zNeGlYlv5K#S)DqNR)@oh` z$W4HVssf@2L;&PA_(L2T2=xYFI)F@}j=&R)*t||)m{abc&AXH0%JG;M`~I`S*OgZ; zQt$56+;aS;8&QgLV4zypt4OP{eyE2kvLnKZ2_Z>9s!#pRZ_ioz8>-(RbnwqEHb`^ZkQ$(M6u@uzk;wR-Lx6a4cmo65dS}_?CCoMYv z^3Z43s;b-KF7P)LpRSh{BY446lYNqC$4rDU14hYNvCbO;n{fyDR2!tMR&b4%Z0R z9$v@&T*V6X06;~?JnmZ6D!aRW9+w_pz7HbMX0%NnI*PLihZg^kN2>rh-g#V{s@&^7 zI*&_Tv&s$~5V1wDIy;!#Ud5)f;j@R^@>bcQlRiAl&FmZa@l`kZXDsrAI*a1*aN-BN z9kgYS=i-(F&l{voScNCd?pVLGV-8NTX>r{uJIZ>aN6K_jF5rC<`e6atyl!v2)KB$% z4nF+mbtli~ma~4(=ZsTh+1D@e;JcN?-stX~&!sPoohO~5C-5O3i0A@-4`wEOFdUL4 z`2K-Y(7|_AW^TcDkUz9@gSZLgHW;{L!1wV5-1bPO7O%T~0r&r~DQ5z=MkyOe;Qowd zT~~2NxvXdKD(>4Dl`Y0SkO70HzQJwI)K}5T%Bp+zAV5gvD%& zDr(2aD^mJXv#Z6Rwa=NE?IhO>ai9Lgb+!vf`;C=Uz8@boxZ@tCIKBONx zvVA}C!XCpOh>TG%)Rb}q>60o@< zLr-rBldP!6TFO0(m_5T~Fry-iG}#4k6H~$cg~RbbdJC7Uvc>jjS96c6kzw~5WN?f7 z$Qqc%#e3Zqw{yR`w%Czlxh@0LYsev_@lZ{1`$f^7jBuWXG#-p9Ze`^hG3=VaUCF?vuF@Bgc>mqSEf+-+ z4;fR-{?*0x?z%g;5~MqD2bVCTm~GRU#p^QQ1qAK>DkvYhgL_g{yxZMY1zj($b04V! z)fH8^)zvT+N(S8aYA#g;d|IlxT+rA4>SEE|SIs3v7K?{|1-cul=6*6kRlK>!ayPe~ zlO(HSlJ(yMx+~uNcrEuO2#7r#okX(o8&!u<&}>3f*~_Kg-OMd?+wSGEu$|U>!7icQ z?t8&)KwIbUMfqFY*7e-0NyQ=33_7{+hQ4;aM{wIs;aVmm7GP1QkA8R@JKJIPZ ztpiHfg((V~V#Hv!td+X5pUftExe?RxOTD7vTr|Ti^ z$;i00-$~}jWxyA0H2CF}Lf=b^+@+g1qw1xP-L0FrHL90f?%_?`eX|xIs-@9K7%F^~ ztk8G$S$BN{_XQeMwZOf)OxV=!8@n$Ge%0{v`o`*FePj36`NkgT@{M8LhBV*UgXbmq zXCB|!&jx*C#YNIR*8d-W!vB@Q|B+shdT{r?|9wwN+I(Xr!@jYVrM|JU0pHjv;B~j& zH&zAOsXp*Md<8(NJo-IIfO91^-{bI&Ibg^hCcd$|TCa>7&WDXZ7TyU-$X#aN*sU4x z+w-3>31>Rqy{+CicAMoAz5?r6tz7Z&;Q)dY1i5X%H?{_ZSrb$~9`pZwnLPgg8wMzt zLT5JwV5RH_U2l-bwrB{F9uD#zSeAdr6W35H{B{%9`D*w%;nxYj1Mur7u5&ixy5N9c zGyMACH*8%;NYB8lT#U+{@fug)9_rW>(<|8*ic_tb&sL`fy%12>FMdw zWM$yqu(o#Xy=&Lk8!D@-A6UC#gVC@sgLgy|vU2VE>M&Zv&(_t~7;4w9zY8u4*G11J z8vscXAgGCyo=@N6*38!3ymIB*`s*E4YwPOQ-M9X_%G%mHK#c1hgt&DdaSLLucNo@f zFx*tOVg>Z}x{tUwrbqq_+^ns_9d(BLN#x%$04{ZxBK(dMV4J)13|AnK#)Jy^+2CjQ zb2rpKT({P+aD(B#s;UPccwlYy0_Z~6e?KiM$;iO&RTvQD{b1%&KbWqxV;|9U7!slCHlYWMQ0IZZL*SVX0KEn~Iey-Q&i|?1< z`591N{rx|MPuNxPH?CC7t^wu+5_9FM)ZsP0PpU(Bc5DC+#)&%|D4$m!fP--)SO~2& zv;fmuNdRn|31F4V6yo{A5{&`=2*zy4?hHdd-+Z%FHXm>iFD^*Pc|&O0!T$xhnVw(I zQ+*TZ9)6Tly9ds|KP`!>sU^wHq%m<1AsiS07Z2#uNaG?H)gQIVq*58)G%jkD%VxrL zBK$=|pL`E>tHJNBK1dpG<zKoq!V?_NYGHO@N-*IRK^*r zYDgpeT;*os+M*$iMYZmh6xE%n?pS|wgn*5*|E9ygAUaVISC5u(ZulEQo&#=uswzHf vfp)SA-+LdBgJ5?11m;4Y+GlRq(fm9q!gtRqX!{?x2O> delta 28602 zcmbt-4O~=J+W5KG5eGy>MWlgnL?lE~WXXmZK8BClL9EEwUUJFCq{4lvsHh!vNB*NT0!g>AK_H@9qG+NI6)Wfg@KjY<>0Dbf3Xp8GK$N7VNJ+HaaW z=lML(dCv1Z=iGBIO&7Io|IoS;__;eaBqoH+HIQ-t9=>78*L*8S4cvY{e-Dk~llc7I zG>c!8zx3dOdP2;bNtA;l;f7g6YuAo@%>qppV!P2oHr!_+5B?;}O6m+N+bb>PKmVv) z+bm@JOBPb{86DwM`1NOL;RYI0!ea+!trzZayVzq_M5*_;;KA?rF9xGpV!Y7a6iWq;AC z;VjMXrVZm$_rf-S&vg26+0)XMMy*@67!J^@;3 zNK#1BXA+?!M2Cc}@U?F{?Fxwx+qd1ZifAR^KuFSji4_4>Q;?0s4mvBbG8Slp)P~MH`^`bcTZ$X z!;#;$Q5zp!WG)s(gTX3Zg6~opQmCgH+LWC3%3Kerb8#e&enFxv3z+rN9sM+?Cle*3!ovr+l^H2l<8 z*tPs?&9lT|nM->mNDK$bQS<|%wUBMua{GaBV0R-J!)$Oo4%eP@b|H3kULEnmIDqk> zjhk~+>cpyoG*ds(Lf0L_hi7bthH5))51qfr^Jj%sDzJSWQPPi*s&42GvK*7iP`5!VE#2Bohx`%e-KLUXY5RLNTrWv+MKh z3zv9K40Mj(h^1lrB9#_hDWtC)I?_Z-CP(x69$Gnh{-TXVc6+Q1`(Pm(vlnb4W87U+g z`in~C09+(-2CfYX*K#1=5WqEt#!ZQcj`vvr#}-(@cpm7W<|#TfLHU$~^c#H>NWy^e zLM(Mk2p^rfa^7`JNmz76@5=nW+q9`(EGkug+YMVtAr24fN3~OpzTR%i^YylLYSOYF z)Z0pbZ(F~B=v0W-@jFZ0I|@t5m9Zm%-5s8qkRG=p2;+xzw{Y5GzEDG5)7;#)jnkuz zv9|=Wz#t-gtPGFLt^$NY)H=AG^>k{Bx;+Hu(8_~eYL7B#GW7&GhUxBCIr>80K^HQ; zf`%!bN90gwo1&gKKRDP{QEl1yc^@fbT;c zwM9$hit`o+jzz9nN@F$X5{{RP$|U@c&h;3?($Y*Kr$p1TqB5Mpm^#g|bt$c?zRh;f z(@Hd`tI%@9M$3%}i3_8ImA!WhF}kQcr?}U+WoZ-2ajcfoY*XzoUbQ)1rr+KWGx3(D zy8flDNWYPuxMPlxmL(BFUy%@r3|2kl=KQ_TL9^;5(0QmYZfRWbB91IaaPV>9a{wQB z5#u{~_8c=f!c*V~3l9zDIG}@16H|A=%Q6}@oTq% z0LT0?vy-$4aUa@hhyNtIPW`~$wUOvLkcV=hI6ku0R;#I}TjHm0jGYMH86K+>qKcg9 z`P~NLo?+0rqV}OzY_&6@-~U7*l)#H4ue+| zQCG+NrKKvalqMYurNOH`@+U}S`MoNvn7$r6U6}UH*n@w;=^y-b7s^ovzXS(e8vOp! zQp3O)gI;&jlBe&)!OKjqk0&;|oDwYuZAzI4-$h1A`al`Jtj3oS?$9M{K!PQdcaV zdqZeAVl#NsONf-9xyc)xrhIRfb+VWXMnYlMl^xPz#^3>*-I(Q@*F$OFMH^Goqo>?2 z)BDKhAUW+x)rB~?9W2jrp7aX@^YSz1|}`AeH| z-{CUTOcHLqaJULxL7Jw`haEILMVGK)l`|7lV~9r>_n~cFVzF&QE|#hh3LOTA?FtsH{3QoH_ZeSss{LLa7zo3>fOS z9qusnmzJYTIde9klJHbY9<0dv97fZkv4_ zJv1xEIO$OZ+h1`jQ~?t9V5C!Lfy$k_l2df^%g{M72n(-%1N$Cfp9!=7$5t%%zd`pV zr^olNmgltCXVQxzcACP-OC6UyZJ}PD=a_AlJWWXu&RLK^jZkOieGYt1dujR2>2%*s zQ|OkPZBXY z`3Zc|9=aF4U^0x%hYAIxKEj3l080%PjwLm49Hj1LLic8&nTBnF;)}(voFM)N`sspD zVa{&h(BGqk&5%A?#oe$N8HE9XNfcNQgnlq~N;vT=e@aLWObJXqkQ)y5K$%((2p8pY z1?vJ66kt;RR||yZwM1*K2q66C~IbEsT!~$=Ro?MACkGhP z2TH3je${V*?BgXGHB1@bfeaKmP?`%g#^!-6$VSJFPYuKw7xs5A*o>71X>i^Nqfz*| zOZo)iU)EQtIEoXIR)YY|h(s!xAiR^EThG_a@=JZ*1mGiG9MF z0}v3Q%KdOysd8gn&iQhw0-Dpm&5RR#8lkp~R=1&6IE90>fUA3B)(c!oDa8Ab3_GXm$uCao`eAr#PnLN>0J85-Q}X z3>3SM4kSejiG{gl$f(P{fPGik;qNbr6Am4Y5`Hmkh6$AH5J9eoaP~uKB@law{gu;E zvJPigNV_ftF#REq$t6<#xdp9Yk4@rE)K}5YJP}TJ(Scj$8>3RCcnB+SOhNSz5N&-f@O^oYIq$tM!*dB_ z2oJbKOi>MH17Xdck_E4nK*WzMp~us7d}J1N<)lpIw*)V{zl@^QhIw4s##<61^1J+I zJC;S$Zb{*1m}&Vf3DJR&qXW1GfT)+aF*Ave-a~5^reNjZSva3RS_*6Xc~eS)WgKm$ zSvg7brw0ec_m}-(7+@l7I%1R3(tCItAUa0vpxGH64%*I}av#G;L&MWkglT<& z1r&zU=iVpbj)(V`=M2DgKbd5_&^KHM0xZOSDGS=rI*v#7TXXfK!L)jP4Ne{lS@)^z zHU&|&?~tC_eWd5!cP3v(8{E5Z@?~@}P$0{YVf-!cq8uq zab3I%+5(7RQ-7UOLKQ@+hDCuYVnVd0i$>1Y9V{u2g#q z=ofMMe0z9zL?}G)ddjry3H3~(68*W%%qbiS+O_6pVN;AMjQ4zg}l*AI(b#jVh#atXT4ny1mQSGv<$rGEucsz|^4ti4&$T@UdXy{9IV^`4(X zW3c>TIHbeA_B6Du+A8mQibG0!3v4Xy zY7vPxZ)0eNc|F{b0(u4aX*zmqye_0g)krGJu%^acTxW=5n`@hKTV#JEEiuW}!VUAtnN{fGS1%V1Y2|X^ zD8X`p4{f5ura7_X^jAottTjRn+m1yUyu@H3Z$3)XvZhZYr~im-ahrvfWJSm09mhk` zUIGLvf^iZjk%Ho(4O!{=tuH{;wAx%Z(STenAN@33xX?o!XKki~Gj%e=MMk?Vy) zghEK|mLS~#=&ghzAz5P>Ah6M(OF8wyb^0akF+jalhOQYup(oJY90Ek>Ed(rI0Wp#OG%b--@Zi;kiFQ0g7F z-mHV-c>Q-4oLI%;ca<>VI!sW&w1_3)(mZc3n0<>?Ep61uVBS13SS;>q!<(6Tn@;Y) zavPVHvWI;;9`;|o8w;aDo*EB#Ub<*pp)UPo84xUi(fQa_4gS>=uyO12ualoj8}sN- zuqjWg3Uxy8zE^!F(JXig*Gv5w5c=9AcB>h81=mEEwUCpm0^Jo{wFF((a20n|U0M#S z-+Su?SoI2GuU06MP8Pw0CtSYya^pJF@SE}mpa{$23w{5#EYb*fZA!1YzJ|3oD9YhF z8`syk&PE}UUpc-U28EP4hEwF*cLH}E1k_k6214p>fj90T8wG7U6pLTBg93Oz?vdc2 z_YX58;3v`sLVLgl0!&_McN}gEGMflOkPzJ0C>D=Zp;N)Jib1s!C>Hlx72z)i3*Woj zAdpZjt1v)W$Tz=-6f}hN^{~kUJmlLaV3~m12R`fMIT9vGMWnD2wk-nhp@^U3b_RGW z*+%WR>x?IqS~TFOffAvh^Xk^>=$IRyJ-5%`BqqWMPVU$>nY4_>B-nQj^(n?!5NwRk z?Q%Q6k!i1x`XWHczyyM8Sl2OmHwte+qBF~}==cg&L2Or@TE01a$cllI4Q(o(?A&`J z)m<3e2sA;Vi*YPc3M9%#pn2=xVYL&m$8)1VzIh$RXL;b`m4hm>^&7ZfN(p1xCs_Pv zUd2BD6>NvUCyu3-s+AV^C(Tm# z*KPJ%T6=p;>DNENuZ~s)PE17+X1o5|-NB57uYZ8A4GvwO@i*PFEqP|v_o%*B9&Fb);fU-&0By z+3KIS_!;9&I{cF>8DHUhWNf~EmyRnI|C**SR>I8}K|v!2&rTkxU<(Gd6S8i9?_gL+ zm4Ldel3Ao(hXt#JuX`Bu1k~d2$hBE~)=@0(oPsw)VKGz%*W=~uPphzOQmu#1Xdwgg zdFFt#r(-y&zm~(hgNnI5Dx%!=|J`0J{%Mme=r8s$1pNTF?H_ZP0_DIkopuG=c))k& z)o-BgYa6Hs*W!wAq6_t3dBjzTc7UgZ#Y1EIesK8u3xr!=#*!GXR*V&J&cNrJ($Pp* zXZKvGtluw-#lNHm7&BcSwSILFe0)_SQS*Hvl9OD?Rz;D=YhP5BRXry2MGLB6ArG7= z7W;^QD*}Vs=f~x<4ZmL{9cp;*vk}Nm!*EEtLp_%c+p7a`8rq!efaCnFMV56Fp#;FA zwO2k%SbkHyjOW${xBhTtr9S}uVz)w4*3DQ0~G&03aPnG1;!4iLp;{C!V z@u_1L)2^a&V{^eM>WX^e(@bLM)E0n|M$>@(+u< zt88Qm7}AdQUNH#*M1gg>0aq>sq1{6E&qeVVSbc?zf*-(hNa7iEDBZR^fzRJgTbD1w zuf5At;A`Up@VM0Wx04^>xrhVpFF-1X^JT3N$*i=)I)mP2ov6!`CdGzX(%Ec+V8gaA zEh@_RK2s|^mf^BShauoVj{-=EfS;f?{sep%)vuUAMeDTS=AF>YU>=t+A72rSIh06U z4CatU!t7@;t2aIv%yOtjLcHrihUFew&0-GIfd^;M_ZiHu^w1p4^DB#lxnN~5&#!E> zWF>>yE@5tAF$buZ!TcIr2Bc4}2_bk#$bN4n+~&>qY`HB!@)d{CsOOf!P1YHN|@p|FlRmz%(I_R^CJw;7mLr>CpX&K^nEnh)b@OU#6)c1CEA4mN`F+^BKbj%xQ%CjtI-}3*ijB zI+*@~9N7Hytqk1kqZecW-3&sR4?%(t5e8n(z*8A`BLinU;8Ed%+`}O|!=Ot=P~hzh zyoZ7BW#H0oq2GdEF&!8o3caA1_$#IdBQe1OzA6hAFp|c=8yUETf$wDCRv9j+WBk=w z2Ei80;A_T5MqCWMilJ|3;EV-Fyb@gSImy?341?Za2H*G|I+LAnWSD_x1k-<~W#Huu zT#s@27U!jR5A!<41-1nUd_OF z(5T0trf8v_S9K}hYM@99$AT6sBzW>zI;M?n$i)FAiUG@*ZQq7d`(yFZc^<5_p{xkU zW<&>28tr&2p3n2pp2wm?q?U4MiKP=B_r+V}v+dgkYJGeL&1IW+LbESm%a}tF=35Nr z$r#$hV4k!{m@}(`MV+)!b9FG!lkf~%H6#`mvzK--n8T40rpRCpmr(r^!90iI*(2cj z_!Gg>hkL1u!91mvF#8$IQ|66N2D3bcnnMluWH8H9+i5k6*+&PSgt~{pJPr528O+mJ z66S)Zf_a{X2OFOXmVUZh!ra1Oo(ZR32J=j*ggLn;nCF>RnpP9c^URQhDKMC4lc|Tn zJZq(qPea|qVnXTibTH4eJrd>vPX|jsr=<=C^PEA#{2Pnupu;StSHjGFhT%CvtZ z!J1&6-)m{fnqbWDOC-!KEM_D1vY0z1%*ksRp2un0+F+g+<0MRh!Ms>bJuIe&y4q&a z3u|Xe#o@&rG|rwb6^GwO1;ku9i*q_HIOcwv#lV*^@W7ak^h{d(wuPa`v?%8Uxj6iG z4}-v@#cu<%5(;3_;>l!&o=J-*%NRJMMOgu*I6N7cS5W|y7EkUBW^jf{izkmWa3(Dd zGYXzDD+;K^;c#Gm4GNrL(&F$AMgWr*hldz=quN8cx;+)gz&$eDR~()yWe~a;28|57 zhk>&e8e-s#h0YQQE?Cv-^faU4*_dDkr^|yCIGe`618X<1;8_a;_niuW;u7Ijga;Id zXP7ZMYYS#@h8d!>E(YGkDA*nhKO49}Kn1)24=fJPW-$zU83r{Be3*f64~C!9GH}KM z=k!c*crJ!vU2iXgZkULXvDCkR2j5W~O+7Je9X?Hil4HiB&*`EXd|61u%X7b2mQ(X3}PVb|;WW5Sq8_>aXT#CF)Ewl;flKphOSD~u{x>O(C zWq833(7_5%(ev`vXlyb_IV-T?$ z=n8#wVg+5&7|U7wbZu9W*93G+5G@~vOT3C1BC4Ga5Do6s%f10_Qv?SL`$;K~mpEX1 z99_aEjYv~w3@yAbH++ImH39SN^zcPBHhwcQr3LhO8K6dXW zp;anEPWaK-b`>yNbN?+o7#bS^(#BQ0-=<(mC;SMkp9 z8lY?O(+$&ZplkKfY4l_FFFXtAJU%*23C;RdEZ63zlWM^$?HR5Cx^_QZ7u^nY+x>J` zi4qgLfv&?xH$INWJs->M@X?Kz(*Bk5CN=|Im!Hl>n}BYopRRlC{!J_gIg zV(1cAEZ66wn_wBce_?i@8}QTF=uV&;^wW8+LRScMLx?6|beXW{Drm_8(S zG`9YsFfBNZk1jNsR&5MSf}z%{(808j^Q)8uLp`)_V=R5EVTP1CL$4BxLt#>IQ9h-@ zBFAQ~9Rjo%M4OMc53`^fA*z(T^B{S{fa}z3{mCy@%-sY>uJE;Wc%6lI$e_bCNf6uMQeku11=nM-fdlqp zi_HE)lOO*)yr08|KLXc^*oXTZ97%=Wl4yK==8CzkUihtuH;vXiNeukXsCO?+=cduf z7sBE%N+DM*bc)534gTJaX3**vbo{85Hoc%Tj!H=+0JAp$bGOgpP%Z(Nvwqkm4IKb3 zm&|k&xLgV>^Z|3&5`fte%w@PD0Ct>~yr|^t+6^9uVyB~!F)~e9WQvnK+N`@{#LDEJ&ZXHhs z=}x!K$Vv5u-{hwQ6yz#`xttDAkn_;U79G#^($p56k(Xvp0GAfOo_q zzn8WGmvIc2Gw{w%by|!o4CZnsF#uLYhk?tuRvP{iaOq(%ZGKD*VPMSxurz9YNylp{ zs2#Xyqbk(eV@D zfYK{E-(OqufmV-#xJ*s9}0VSBVy zCxl9=**^^D1BNJ!aq9~KLljo-Qxjr0tnri}`9lKW^6Fx7#3}{MyXNj5gh;0lf4CQ} zl?bcpg=-~Z-dA&F#H40`D-kojRgIYLv~az1&q+H~bm$Z8w0oU;;sVk26)sp;C%p(bQD~j!C62EXBnWfr~tGl%8Mv?IzYszIDEIlVOsE_HETkmlNaYsCrpS<9?&GI%t-9*+$FN%Wi5{FNbNX zav2^;cdD0Zv`@V(p`+?$HH~{!<{R#!S?c8uTCQI9QTwav&Ew^h^mwEA`Ztd!rJ{o< zF(6#tMB${Dnc)h1Lr!7Lm3NgVHNZV&T?jc2k7U4YU**p7Bv_j~rlXIwMX5*9R^XlG zNyp*NOwM>bhZ8|#?Q!&E+YH%!B{Y1S+@VP=G*!Lqrc1W@dJ&QQ!+H^sab+(^D{e0- zy-2>Y7ZK&CZv;t%_>u@cN#2kHPwjWK#fM3{R$>#}MT({Oye2CgVW!ouCB?(*%i$H% za0C+GXatY$Z6R@Rjxr9mui#wzi%-10<29a}I_)SPX2b{D;eit~yov2Iy63mg@Y7bo zIYu2nZOPl^?Jthg6{ca`d&hf3ubj>t$TSo3Qc+oHx1MZ>4R2NXaQ^htBwNBdX?FRhZ$-E@*}WtUi^UISLDSQBM$y@XnOz zF`LMb8fjHmysp57OB{u-G*tHb+4zFyCfbHrO|-jf zDPI6*LH`H=x`~?qxEz0Z{xsSbuq(3*Y7ruPh$KX=XY!xsC34*BCN+ zuW47LhMs-*cRYM~{&bp$gWAnIQ>GSX<+TzkPD4qN#f4F{XJ>j?!LY%JuYWAeqT##J zmlkG$ioUW0XJ#h6^{yV0B(9Y<wtbjZ#N@9NKzoVk_g;UM{d^tSC4 z5c;gpT~iUfqGnf8NKuw)S3w2cxoduWQI>_&FT@kf@bGavOq}M8KCVQQriWDtd$#GSb96d>f#PA{`)A_{J^cP6Eg#uKllT0LpW5|y^PXiqeh>fU86KM}KV61DcYper)SS`tSO_+^ z?K5KYz`lFO@sUw)8}>iO;dlFizjAzRB()v<86O+>_Ta(G8tMD+yW;^&JwwmKPn!1C zUjgaAzJ4ttWubnJ3?b)-tHO6=hFl6 zFJ}egUrvQr^=L_xbvU%M6MFo4oZyV9a4bW4Zr#Qs;9($KiXwTazdhIt&wBeAva1+s@ zH1Ntk_s)4-3c#U94r~LbwL`mawS=Au9%_ePi-1!u1!mxHSZQik=M(Y=m>E2E7(EqZ z2mbwl+yRgQw2(@o6t~jjJXtrp*ydaeGL(3dIYALZ9S=mg9jRPMe4!nczXRMK8KPpq zd)iTWt}Gly7>W8TF;T+!22zK zY~D1jF~AI794rt5WaV6X_K0B>bb2qn$)5*l<&EsrNj!19B z%Ulev2Ky-6)>*ogi=My_{;{*_J}wDJ3p<;aao=+M7hBy8<=lJm<#X)M;4>B>BO5;; zUMR`8LkiP^UE!gSfj_&g_j9LtWV4+69;nmYC06buKHK1qS;5Wc%VxPPD_~%=&7D;% zB+aTjM;_!}L(EQ31sBbSWT#~o!jGK~bARE`#*LL+4queoxu=qQQG*Pvk0OKB?uJKU z?&R!sYaipD&(CSeHW#3o_1T2feNvgzb;)5(#_L=&soSH?>5$qvqW|FCoaReSWkzW8 zW^%-J$x@m8$z#B`_c3k}Zzi3Qk8>$pw3*norElidh3!GV{>j`4IiLK_JmfB^1|7|N z+&kBDiSDLqZYrPC>h@G~31KnZ_#|{a$K`H#5=@tE zbMJT(+|5kfkxy|cJn+eSipv3eeNd6(aXX&k5<+r3pF9t?Yk!KneLSDj-Z^aJHgd9N z)skjSYru9n?e2~>+-cAdyEqz3((~&0ZqziB5I%FU+`YTFQ|@p(myUwWc5qA(Y_-GW z1YvDkDaQleoRO^u^Uoq_qh#EZAH1B?jI#j@l9m}GijwHwb=*ff%ZQo> z9jl6o+j?`=_e4?TqhC<72a%9ET-;q zC;p227CkEJdG6g?h5u+4FWdC+v5S{$2gJ)l2K*fimw4H^TfB_0x+UV}XJh2+UwGkL zD_*t|`8UG^rYOxVHJOW0~tKri}mf5wBFNCeq(lqjcb3=2N8f-~sp>J%qCo z_~$&>2cNUVc?iDSD~a={20n4{NhZ#|Uifwq=U_W=9!rIf0X_}zY3p=-!iDqhf&bwa zP0~O4&?Bp!s(I+ihbm@;xnKDo?zYb4Zmve#neh*t*it zklllDUFjZz>#*Hva1HMtBE0*?0WRKsugE31y*$71vBNf^f&aAdaU3dx|Bok;@Jqf9 zA$s^vzScl{Bys$c05no=SFRld{*$f&mm|_OUhx{`n&Y9Sl&(9y;Zp)1h?=og@NvKgtg+wQSsuZ^s237#qBy5f`Gf~tt8L2n%pCwDGJNK7QJk%n zuVcGur1c?zYfIFjriMVdHmObIP$d+ z3{30-rY*7pC|pvYMs8Dy6XRr>#0+RJgzFfvMy1>~8*mX%7ocQ9jGp<2K8yc~?!3M8 zsWkpvh`Z`me(Igm+ezJ&9zr-6WiB4zlS!Qp#xkl)!X`v+!yk3J9ZK6Y_?`fN*hiFp z*ZsJf)ctq>Ub$G}Ua^^*Gp^3m<(}o?e%|@Gfv4k=_IV|@K-aj&$DN$T8>be6tH8%; zDI?A`R(Ro8le;F1f0%EqaQ9{L@#!$pxJEA_EzQLLPpe$s0N>CJGLF>c!MvDN299lT ir)Kl