From 19b8e017ef2a647a28173e72d49c04a90385855d Mon Sep 17 00:00:00 2001 From: BullyWiiPlaza Date: Sat, 3 Jun 2017 16:02:59 +0200 Subject: [PATCH] Update and commit missing files --- .idea/markdown-exported-files.xml | 8 ++ meta/meta.xml | 29 +++-- src/address.c | 16 +++ src/address.h | 12 ++ src/assertions.h | 53 +++++++++ src/disassembler.c | 37 +++++++ src/disassembler.h | 6 + src/entry.c | 2 +- src/kernel.h | 113 +++++++++++++++++++ src/kernel/kernel_functions.c | 7 +- src/main.c | 13 ++- src/patcher/function_patcher_gx2.c | 8 +- src/pause.h | 50 +++++++++ src/system/exception_handler.h | 26 ++++- src/system/hardware_breakpoints.h | 154 ++++++++++++++++++++++++++ src/system/threads.c | 38 +++++++ src/system/threads.h | 10 ++ src/system/utilities.h | 2 +- src/{pygecko.c => tcp_gecko.c} | 172 ++++++++++++++++++----------- src/{pygecko.h => tcp_gecko.h} | 10 +- src/title.c | 17 +++ src/title.h | 11 ++ src/utils/linked_list.c | 51 +++++++++ src/utils/linked_list.h | 17 +++ src/utils/stringify.h | 8 ++ tcpgecko.elf | Bin 148748 -> 148876 bytes 26 files changed, 767 insertions(+), 103 deletions(-) create mode 100644 .idea/markdown-exported-files.xml create mode 100644 src/address.c create mode 100644 src/address.h create mode 100644 src/assertions.h create mode 100644 src/disassembler.c create mode 100644 src/disassembler.h create mode 100644 src/kernel.h create mode 100644 src/pause.h create mode 100644 src/system/hardware_breakpoints.h create mode 100644 src/system/threads.c create mode 100644 src/system/threads.h rename src/{pygecko.c => tcp_gecko.c} (91%) rename src/{pygecko.h => tcp_gecko.h} (58%) create mode 100644 src/title.c create mode 100644 src/title.h create mode 100644 src/utils/linked_list.c create mode 100644 src/utils/linked_list.h create mode 100644 src/utils/stringify.h diff --git a/.idea/markdown-exported-files.xml b/.idea/markdown-exported-files.xml new file mode 100644 index 0000000..5d1f129 --- /dev/null +++ b/.idea/markdown-exported-files.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/meta/meta.xml b/meta/meta.xml index dd25833..ead026f 100644 --- a/meta/meta.xml +++ b/meta/meta.xml @@ -1,13 +1,18 @@ - - TCPgecko - wj44, BullyWiiPlaza - 2.2 - WiiU RAM Hacking - A memory editor that does magical things to your games. In order to apply Cafe Codes (real-time cheats) use JGecko U. -Special Thanks to: -Chadderz, Marionumber1 - TCPGecko codehandler -pwsincd - icon and xml -CosmoCortney - codehandler for cheat codes, xml - - + + + TCP Gecko + BullyWiiPlaza, wj44, dimok, Chadderz, Marionumber1 + 2.3 + WiiU RAM Hacking + A memory editor that does magical things to your games. In order to develop and apply real-time + cheats use JGecko U. + + Special thanks to: + Chadderz, Marionumber1 - Original TCP Gecko Installer + dimok - Homebrew Launcher + kinnay - Diibugger + pwsincd - Icon and XML + CosmoCortney - Cheat code handler + + \ No newline at end of file diff --git a/src/address.c b/src/address.c new file mode 100644 index 0000000..2f8a7b9 --- /dev/null +++ b/src/address.c @@ -0,0 +1,16 @@ +#include "address.h" +#include "dynamic_libs/os_functions.h" + +int validateAddressRange(int starting_address, int ending_address) { + return __OSValidateAddressSpaceRange(1, (void *) starting_address, ending_address - starting_address + 1); +} + +bool isValidDataAddress(int address) { + return OSIsAddressValid((const void *) address) + && address >= 0x10000000 + && address < 0x50000000; +} + +int roundUpToAligned(int number) { + return (number + 3) & ~0x03; +} \ No newline at end of file diff --git a/src/address.h b/src/address.h new file mode 100644 index 0000000..7a38497 --- /dev/null +++ b/src/address.h @@ -0,0 +1,12 @@ +#ifndef TCPGECKO_ADDRESS_H +#define TCPGECKO_ADDRESS_H + +#include + +int validateAddressRange(int starting_address, int ending_address); + +bool isValidDataAddress(int address); + +int roundUpToAligned(int number); + +#endif \ No newline at end of file diff --git a/src/assertions.h b/src/assertions.h new file mode 100644 index 0000000..d79fa26 --- /dev/null +++ b/src/assertions.h @@ -0,0 +1,53 @@ +#ifndef TCPGECKO_ASSERTIONS_H +#define TCPGECKO_ASSERTIONS_H + +#define ASSERT_MINIMUM_HOLDS(actual, minimum, variableName) \ +if(actual < minimum) { \ + char buffer[100] = {0}; \ + __os_snprintf(buffer, 100, "%s: Limit exceeded (minimum: %i, actual: %i)", variableName, minimum, actual); \ + OSFatal(buffer); \ +} \ + +#define ASSERT_MAXIMUM_HOLDS(maximum, actual, variableName) \ +if(actual > maximum) { \ + char buffer[100] = {0}; \ + __os_snprintf(buffer, 100, "%s: Limit exceeded (maximum: %i, actual: %i)", variableName, maximum, actual); \ + OSFatal(buffer); \ +} \ + +#define ASSERT_FUNCTION_SUCCEEDED(returnValue, functionName) \ + if (returnValue < 0) { \ + char buffer[100] = {0}; \ + __os_snprintf(buffer, 100, "%s failed with return value: %i", functionName, returnValue); \ + OSFatal(buffer); \ + } \ + +#define ASSERT_VALID_EFFECTIVE_ADDRESS(effectiveAddress, message) \ + if(!OSIsAddressValid((void *) effectiveAddress)) { \ + char buffer[100] = {0}; \ + __os_snprintf(buffer, 100, "Address %04x invalid: %s", effectiveAddress, message); \ + OSFatal(buffer); \ + } + +#define ASSERT_INTEGER(actual, expected, name) \ + if(actual != expected) { \ + char buffer[50] = {0}; \ + __os_snprintf(buffer, 50, "%s assertion failed: %i == %i", name, actual, expected); \ + OSFatal(buffer); \ + } + +#define ASSERT_STRING(actual, expected) \ + if(strcmp(actual, expected) != 0) { \ + char buffer[50] = {0}; \ + __os_snprintf(buffer, 50, "String assertion failed: \"%s\" == \"%s\"", actual, expected); \ + OSFatal(buffer); \ + } + +#define ASSERT_ALLOCATED(variable, name) \ + if(variable == 0) { \ + char buffer[50] = {0}; \ + __os_snprintf(buffer, 50, "%s allocation failed", name); \ + OSFatal(buffer); \ + } + +#endif \ No newline at end of file diff --git a/src/disassembler.c b/src/disassembler.c new file mode 100644 index 0000000..a7725f1 --- /dev/null +++ b/src/disassembler.c @@ -0,0 +1,37 @@ +#include "disassembler.h" +#include "assertions.h" +#include "dynamic_libs/os_functions.h" +#include +#include +#include +#include + +char *disassemblerBuffer; +void *disassemblerBufferPointer; + +#define DISASSEMBLER_BUFFER_SIZE 0x1024 + +void formatDisassembled(char *format, ...) { + if (!disassemblerBuffer) { + disassemblerBuffer = malloc(DISASSEMBLER_BUFFER_SIZE); + ASSERT_ALLOCATED(disassemblerBuffer, "Disassembler buffer") + disassemblerBufferPointer = disassemblerBuffer; + } + + va_list variableArguments; + va_start(variableArguments, format); + char *temporaryBuffer; + int printedBytesCount = vasprintf(&temporaryBuffer, format, variableArguments); + ASSERT_ALLOCATED(temporaryBuffer, "Temporary buffer") + ASSERT_MINIMUM_HOLDS(printedBytesCount, 1, "Printed bytes count") + va_end(variableArguments); + + // Do not smash the buffer + long projectedSize = (void *) disassemblerBuffer - disassemblerBufferPointer + printedBytesCount; + if (projectedSize < DISASSEMBLER_BUFFER_SIZE) { + memcpy(disassemblerBuffer, temporaryBuffer, printedBytesCount); + disassemblerBuffer += printedBytesCount; + } + + free(temporaryBuffer); +} \ No newline at end of file diff --git a/src/disassembler.h b/src/disassembler.h new file mode 100644 index 0000000..f0efed7 --- /dev/null +++ b/src/disassembler.h @@ -0,0 +1,6 @@ +#ifndef TCPGECKO_DISASSEMBLER_H +#define TCPGECKO_DISASSEMBLER_H + +void formatDisassembled(char *format, ...); + +#endif \ No newline at end of file diff --git a/src/entry.c b/src/entry.c index 683742c..7712e90 100644 --- a/src/entry.c +++ b/src/entry.c @@ -2,7 +2,7 @@ #include "dynamic_libs/gx2_functions.h" #include "dynamic_libs/socket_functions.h" #include "common/common.h" -#include "pygecko.h" +#include "tcp_gecko.h" #include "main.h" #include "utils/logger.h" #include "title.h" diff --git a/src/kernel.h b/src/kernel.h new file mode 100644 index 0000000..ef8e112 --- /dev/null +++ b/src/kernel.h @@ -0,0 +1,113 @@ +#pragma once + +#include "kernel/syscalls.h" +#include "assertions.h" +#include "dynamic_libs/os_functions.h" +#include "tcp_gecko.h" +#include "utils/logger.h" + +unsigned char *kernelCopyBuffer[sizeof(int)]; + +// TODO Variable size, not hard-coded +unsigned char *kernelCopyBufferOld[DATA_BUFFER_SIZE]; + +void kernelCopyData(unsigned char *destinationBuffer, unsigned char *sourceBuffer, unsigned int length) { + if (length > DATA_BUFFER_SIZE) { + OSFatal("Kernel copy buffer size exceeded"); + } + + memcpy(kernelCopyBufferOld, sourceBuffer, length); + SC0x25_KernelCopyData((unsigned int) OSEffectiveToPhysical(destinationBuffer), (unsigned int) &kernelCopyBufferOld, length); + DCFlushRange(destinationBuffer, (u32) length); +} + +void kernelCopyInt(unsigned char *destinationBuffer, unsigned char *sourceBuffer, unsigned int length) { + memcpy(kernelCopyBuffer, sourceBuffer, length); + unsigned int destinationAddress = (unsigned int) OSEffectiveToPhysical(destinationBuffer); + SC0x25_KernelCopyData(destinationAddress, (unsigned int) &kernelCopyBuffer, length); + DCFlushRange(destinationBuffer, (u32) length); +} + +void writeKernelMemory(const void *address, uint32_t value) { + ((int *) kernelCopyBuffer)[0] = value; + kernelCopyInt((unsigned char *) address, (unsigned char *) kernelCopyBuffer, sizeof(int)); +} + +int readKernelMemory(const void *address) { + // For addresses in that range use Chadderz' function to avoid crashing + if (address > (const void *) 0xF0000000) { + log_print("Using Chadderz' kern_read()...\n"); + return kern_read(address); + } + + log_print("Using dimok's kernelCopy()...\n"); + unsigned char *readBuffer[sizeof(int)]; + kernelCopyInt((unsigned char *) readBuffer, (unsigned char *) address, sizeof(int)); + + return ((int *) readBuffer)[0]; +} + +#define KERNEL_COPY_SOURCE_ADDRESS 0x10100000 + +int kernelCopyService(int argc, void *argv) { + while (true) { + // Read the destination address from the source address + int destinationAddress = *(int *) KERNEL_COPY_SOURCE_ADDRESS; + + // Avoid crashing + if (OSIsAddressValid((const void *) destinationAddress)) { + // Perform memory copy + unsigned char *valueBuffer = (unsigned char *) (KERNEL_COPY_SOURCE_ADDRESS + 4); + kernelCopyInt((unsigned char *) destinationAddress, valueBuffer, 4); + + // "Consume" address and value for synchronization with the code handler for instance + *(int *) KERNEL_COPY_SOURCE_ADDRESS = 0; + *(((int *) KERNEL_COPY_SOURCE_ADDRESS) + 1) = 0; + } + } +} + +void startKernelCopyService() { + unsigned int stack = (unsigned int) memalign(0x40, 0x100); + ASSERT_ALLOCATED(stack, "Kernel copy thread stack") + stack += 0x100; + void *thread = memalign(0x40, 0x1000); + ASSERT_ALLOCATED(thread, "Kernel copy thread") + + int status = OSCreateThread(thread, kernelCopyService, 1, NULL, (u32) stack + sizeof(stack), sizeof(stack), 31, + OS_THREAD_ATTR_AFFINITY_CORE1 | OS_THREAD_ATTR_PINNED_AFFINITY | OS_THREAD_ATTR_DETACH); + ASSERT_INTEGER(status, 1, "Creating kernel copy thread") + // OSSetThreadName(thread, "Kernel Copier"); + OSResumeThread(thread); +} + +#define MINIMUM_KERNEL_COMPARE_LENGTH 4 +#define KERNEL_MEMORY_COMPARE_STEP_SIZE 1 + +int kernelMemoryCompare(const void *sourceBuffer, + const void *destinationBuffer, + int length) { + if (length < MINIMUM_KERNEL_COMPARE_LENGTH) { + ASSERT_MINIMUM_HOLDS(length, MINIMUM_KERNEL_COMPARE_LENGTH, "length"); + } + + bool loopEntered = false; + + while (kern_read(sourceBuffer) == kern_read(destinationBuffer)) { + loopEntered = true; + sourceBuffer = (char *) sourceBuffer + KERNEL_MEMORY_COMPARE_STEP_SIZE; + destinationBuffer = (char *) destinationBuffer + KERNEL_MEMORY_COMPARE_STEP_SIZE; + length -= KERNEL_MEMORY_COMPARE_STEP_SIZE; + + if (length <= MINIMUM_KERNEL_COMPARE_LENGTH - 1) { + break; + } + } + + if (loopEntered) { + sourceBuffer -= KERNEL_MEMORY_COMPARE_STEP_SIZE; + destinationBuffer -= KERNEL_MEMORY_COMPARE_STEP_SIZE; + } + + return kern_read(sourceBuffer) - kern_read(destinationBuffer); +} \ No newline at end of file diff --git a/src/kernel/kernel_functions.c b/src/kernel/kernel_functions.c index ea23be5..6d2843a 100644 --- a/src/kernel/kernel_functions.c +++ b/src/kernel/kernel_functions.c @@ -1,9 +1,6 @@ #include -#include "common/common.h" -#include "common/kernel_defs.h" -#include "kernel/kernel_functions.h" -#include "kernel/syscalls.h" -#include "pygecko.h" +#include "../common/kernel_defs.h" +#include "../kernel/kernel_functions.h" /* our retain data */ ReducedCosAppXmlInfo cosAppXmlInfoStruct; diff --git a/src/main.c b/src/main.c index f25f6b5..028a7ce 100644 --- a/src/main.c +++ b/src/main.c @@ -44,10 +44,6 @@ int Menu_Main(void) { InitVPadFunctionPointers(); InitSysFunctionPointers(); - log_init(COMPUTER_IP_ADDRESS); - log_print("Patching functions\n"); - applyFunctionPatches(); - if (strcasecmp("men.rpx", cosAppXmlInfoStruct.rpx_name) == 0) { return EXIT_RELAUNCH_ON_LOAD; } else if (strlen(cosAppXmlInfoStruct.rpx_name) > 0 && @@ -141,15 +137,20 @@ int Menu_Main(void) { break; } - // A Button + // A Button pressed if (pressedButtons & VPAD_BUTTON_A) { unsigned int physicalCodeHandlerAddress = (unsigned int) OSEffectiveToPhysical( (void *) CODE_HANDLER_INSTALL_ADDRESS); - SC0x25_KernelCopyData((u32) physicalCodeHandlerAddress, (int) codeHandler, codeHandlerLength); + SC0x25_KernelCopyData((u32) physicalCodeHandlerAddress, (unsigned int) codeHandler, codeHandlerLength); DCFlushRange((const void *) CODE_HANDLER_INSTALL_ADDRESS, (u32) codeHandlerLength); isCodeHandlerInstalled = true; launchMethod = TCP_GECKO; + + log_init(COMPUTER_IP_ADDRESS); + log_print("Patching functions\n"); + applyFunctionPatches(); + break; } diff --git a/src/patcher/function_patcher_gx2.c b/src/patcher/function_patcher_gx2.c index 8674bdc..7ab346f 100644 --- a/src/patcher/function_patcher_gx2.c +++ b/src/patcher/function_patcher_gx2.c @@ -24,8 +24,7 @@ static volatile int executionCounter = 0; -declareFunctionHook(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, s32 - scan_target) { +declareFunctionHook(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, s32 scan_target) { if (executionCounter > 120) { GX2Surface surface = colorBuffer->surface; /*s32 format = surface.format; @@ -53,8 +52,7 @@ declareFunctionHook(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer * jpeg.img_id = 0; }*/ - log_printf("GX2CopyColorBufferToScanBuffer {surface width:%d, height:%d, image size:%d, image data:%x}\n", - surface.width, surface.height, surface.image_size, surface.image_data); + log_printf("GX2CopyColorBufferToScanBuffer {surface width:%d, height:%d, image size:%d, image data:%x}\n", surface.width, surface.height, surface.image_size, surface.image_data); executionCounter = 0; } @@ -65,7 +63,7 @@ declareFunctionHook(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer * } FunctionHook method_hooks_gx2[] __attribute__((section(".data"))) = { - makeFunctionHook(GX2CopyColorBufferToScanBuffer, LIB_GX2, STATIC_FUNCTION) + // makeFunctionHook(GX2CopyColorBufferToScanBuffer, LIB_GX2, STATIC_FUNCTION) }; u32 method_hooks_size_gx2 __attribute__((section(".data"))) = sizeof(method_hooks_gx2) / sizeof(FunctionHook); diff --git a/src/pause.h b/src/pause.h new file mode 100644 index 0000000..bc0e21b --- /dev/null +++ b/src/pause.h @@ -0,0 +1,50 @@ +#pragma once + +#include "utils/logger.h" +#include "assertions.h" +#include "dynamic_libs/os_functions.h" +#include "common/fs_defs.h" +#include "kernel.h" + +int (*AVMGetDRCScanMode)(int); + +unsigned long getConsoleStatePatchAddress() { + if (AVMGetDRCScanMode) { + log_print("Already acquired!\n"); + } else { + // Acquire the RPL and function + log_print("Acquiring...\n"); + unsigned int avm_handle; + OSDynLoad_Acquire("avm.rpl", &avm_handle); + ASSERT_ALLOCATED(avm_handle, "avm.rpl") + OSDynLoad_FindExport((u32) avm_handle, 0, "AVMGetDRCScanMode", &AVMGetDRCScanMode); + ASSERT_ALLOCATED(AVMGetDRCScanMode, "AVMGetDRCScanMode") + log_print("Acquired!\n"); + } + + return (unsigned long) (AVMGetDRCScanMode + 0x44); +} + +typedef enum { + PAUSED = 0x38000001, + RUNNING = 0x38000000 +} ConsoleState; + +void writeConsoleState(ConsoleState state) { + // Get the value to write + int patchValue = state; + log_printf("Patch value: %x\n", patchValue); + + // Write the value + unsigned int patchAddress = getConsoleStatePatchAddress(); + log_printf("Patch address: %x\n", patchAddress); + kernelCopyData((unsigned char *) patchAddress, (unsigned char *) &patchValue, 4); +} + +bool isConsolePaused() { + unsigned int patchAddress = getConsoleStatePatchAddress(); + log_printf("Patch address: %x\n", patchAddress); + int value = *(unsigned int *) patchAddress; + + return value == PAUSED; +} \ No newline at end of file diff --git a/src/system/exception_handler.h b/src/system/exception_handler.h index f15f2a4..4f1d8ce 100644 --- a/src/system/exception_handler.h +++ b/src/system/exception_handler.h @@ -35,7 +35,31 @@ typedef struct OSContext { uint32_t exception_specific0; uint32_t exception_specific1; - /* There is actually a lot more here but we don't need the rest*/ + 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; #define CPU_STACK_TRACE_DEPTH 10 diff --git a/src/system/hardware_breakpoints.h b/src/system/hardware_breakpoints.h new file mode 100644 index 0000000..a61d550 --- /dev/null +++ b/src/system/hardware_breakpoints.h @@ -0,0 +1,154 @@ +#include "../utils/stringify.h" +#include "../dynamic_libs/os_functions.h" +#include "threads.h" +#include "../utils/logger.h" +#include "../main.h" +#include "utilities.h" +#include "software_breakpoints.h" +#include "../common/kernel_types.h" + +#ifndef TCPGECKO_BREAKPOINTS_H +#define TCPGECKO_BREAKPOINTS_H + +// Special purpose registers +#define IABR 0x3F2 +#define DABR 0x3F5 + +// http://www.ds.ewi.tudelft.nl/vakken/in1006/instruction-set/mtspr.html +#define mtspr(spr, value) \ + __asm__ __volatile__ ("mtspr %0, %1" : : "K" (spr), "r" (value)) \ + + +// https://www.ibm.com/support/knowledgecenter/en/ssw_aix_71/com.ibm.aix.alangref/idalangref_isync_ics_instrs.htm +static inline void isync() { + __asm__ __volatile__ ("isync" : : : "memory"); +} + +// https://www.ibm.com/support/knowledgecenter/en/ssw_aix_61/com.ibm.aix.alangref/idalangref_eieio_instrs.htm +static inline void eieio() { + __asm__ __volatile__ ("eieio" : : : "memory"); +} + +// https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.alangref/idalangref_rfi_retfinter_instrs.htm +static inline void rfi() { + __asm__ __volatile__ ("rfi" : : : "memory"); +} + +// https://www.manualslib.com/manual/606065/Ibm-Powerpc-750gx.html?page=64 +static inline void setIABR(unsigned int address) { + mtspr(IABR, address); + eieio(); + isync(); +} + +static inline int getIABRAddress() { + return mfspr(IABR); +} + +static inline int getDABRAddress(void *interruptedContext) { + OSContext *context = (OSContext *) interruptedContext; + return (int) context->srr0; // Offset 0xA4 +} + +static inline int getIABRMatch(void *interruptedContext) { + OSContext *context = (OSContext *) interruptedContext; + return (int) context->exception_specific1; // Offset 0x98 +} + +unsigned char breakPointHandler(void *interruptedContext); + +void registerBreakPointHandler() { + log_init(COMPUTER_IP_ADDRESS); + log_print("Registering breakpoint handler...\n"); + // TODO Not working, never called? + // OSSetExceptionCallback((u8) OS_EXCEPTION_DSI, &breakPointHandler); + // OSSetExceptionCallback((u8) OS_EXCEPTION_ISI, &breakPointHandler); + // OSSetExceptionCallback((u8) OS_EXCEPTION_PROGRAM, &breakPointHandler); + OSSetExceptionCallbackEx((u8) OS_EXCEPTION_MODE_GLOBAL_ALL_CORES, (u8) OS_EXCEPTION_PROGRAM, &breakPointHandler); + // __OSSetInterruptHandler((u8) OS_EXCEPTION_PROGRAM, &breakPointHandler); + log_print("Breakpoint handler(s) registered!\n"); +} + +/*void forceDebuggerInitialized() { + unsigned char patchBytes[] = {0x38, 0x60, 0x00, 0x01}; + patchFunction(OSIsDebuggerInitialized, (char *) patchBytes, sizeof(patchBytes), 0x1C); +} + +void forceDebuggerPresent() { + unsigned char patchBytes[] = {0x38, 0x60, 0x00, 0x01, 0x60, 0x00, 0x00, 0x00}; + patchFunction(OSIsDebuggerPresent, (char *) patchBytes, sizeof(patchBytes), 0x0); +}*/ + +static inline void setupBreakpointSupport() { + log_init(COMPUTER_IP_ADDRESS); + /*log_print("Clear and enable...\n"); + __OSClearAndEnableInterrupt(); + log_print("Restore...\n"); + OSRestoreInterrupts(); + log_print("Enable...\n"); + OSEnableInterrupts(); + forceDebuggerPresent(); + forceDebuggerInitialized();*/ + + registerBreakPointHandler(); +} + +void setDataBreakpoint(int address, bool read, bool write) { + setupBreakpointSupport(); + log_init(COMPUTER_IP_ADDRESS); + log_print("Setting DABR...\n"); + OSSetDABR(1, address, read, write); + log_print("DABR set\n"); + int enabled = OSIsInterruptEnabled(); + log_printf("Interrupts enabled: %i\n", enabled); +} + +void setInstructionBreakpoint(unsigned int address) { + setupBreakpointSupport(); + + // int returnedAddress; + + log_print("Setting IABR #1...\n"); + // OSSetIABR(1, address); + setIABR(address); + log_print("IABR set #1...\n"); + /* + // TODO Causes crash + returnedAddress = getIABRAddress(); + log_printf("IABR spr value: %08x\n", returnedAddress); + + log_print("Setting IABR #2...\n"); + setIABR(address); + log_print("IABR set #2...\n"); + returnedAddress = mfspr(IABR); + log_printf("IABR spr value: %08x\n", returnedAddress);*/ +} + +unsigned char breakPointHandler(void *interruptedContext) { + log_init(COMPUTER_IP_ADDRESS); + + // Check for data breakpoints + int dataAddress = getDABRAddress(interruptedContext); + if (OSIsAddressValid((const void *) dataAddress)) { + log_printf("Data breakpoint address: %x08\n", dataAddress); + } else { + log_printf("Data breakpoint invalid address: %x08\n", dataAddress); + + // Check for instruction breakpoints + int instructionAddress = getIABRMatch(interruptedContext); + if (OSIsAddressValid((const void *) instructionAddress)) { + log_printf("Instruction breakpoint address: %x08\n", dataAddress); + } else { + log_print("Instruction breakpoint failed!\n"); + } + } + + setDataBreakpoint(0, false, false); + setInstructionBreakpoint(0); + + rfi(); + + return 0; +} + +#endif \ No newline at end of file diff --git a/src/system/threads.c b/src/system/threads.c new file mode 100644 index 0000000..ad50584 --- /dev/null +++ b/src/system/threads.c @@ -0,0 +1,38 @@ +#include "threads.h" +#include "../utils/linked_list.h" +#include "../dynamic_libs/os_functions.h" +#include "../utils/logger.h" +#include "../main.h" + +struct node *getAllThreads() { + log_init(COMPUTER_IP_ADDRESS); + + struct node *threads = NULL; + int currentThreadAddress = OSGetCurrentThread(); + log_printf("Thread address: %08x\n", currentThreadAddress); + int iterationThreadAddress = currentThreadAddress; + int temporaryThreadAddress; + + // Follow "previous thread" pointers back to the beginning + while ((temporaryThreadAddress = *(int *) (iterationThreadAddress + PREVIOUS_THREAD)) != 0) { + log_printf("Temporary thread address going backwards: %08x\n", temporaryThreadAddress); + iterationThreadAddress = temporaryThreadAddress; + } + + // Now iterate over all threads + while ((temporaryThreadAddress = *(int *) (iterationThreadAddress + NEXT_THREAD)) != 0) { + // Grab the thread's address + log_printf("Temporary thread address going forward: %08x\n", temporaryThreadAddress); + threads = insert(threads, (void *) iterationThreadAddress); + log_printf("Inserted: %08x\n", iterationThreadAddress); + iterationThreadAddress = temporaryThreadAddress; + } + + // The previous while would skip the last thread so add it as well + threads = insert(threads, (void *) iterationThreadAddress); + log_printf("Inserted: %08x\n", iterationThreadAddress); + + reverse(&threads); + + return threads; +} \ No newline at end of file diff --git a/src/system/threads.h b/src/system/threads.h new file mode 100644 index 0000000..286551d --- /dev/null +++ b/src/system/threads.h @@ -0,0 +1,10 @@ +#ifndef TCPGECKO_THREADS_H +#define TCPGECKO_THREADS_H + +#define THREAD_SIZE 0x6A0 +#define PREVIOUS_THREAD 0x390 +#define NEXT_THREAD 0x38C + +struct node *getAllThreads(); + +#endif \ No newline at end of file diff --git a/src/system/utilities.h b/src/system/utilities.h index 1f74cdf..df6f255 100644 --- a/src/system/utilities.h +++ b/src/system/utilities.h @@ -17,7 +17,7 @@ void patchFunction(void *function, char *patchBytes, unsigned int patchBytesSize log_print("Patching function...\n"); void *patchAddress = function + functionOffset; log_printf("Patch address: %p\n", patchAddress); - kernelCopy((unsigned char *) patchAddress, (unsigned char *) patchBytes, patchBytesSize); + kernelCopyInt((unsigned char *) patchAddress, (unsigned char *) patchBytes, patchBytesSize); log_print("Successfully patched!\n"); } diff --git a/src/pygecko.c b/src/tcp_gecko.c similarity index 91% rename from src/pygecko.c rename to src/tcp_gecko.c index a0e9854..2280f32 100644 --- a/src/pygecko.c +++ b/src/tcp_gecko.c @@ -1,3 +1,4 @@ +#include "tcp_gecko.h" #include #include #include @@ -10,7 +11,6 @@ #include "main.h" #include "dynamic_libs/socket_functions.h" #include "dynamic_libs/gx2_functions.h" -#include "kernel/syscalls.h" #include "dynamic_libs/fs_functions.h" #include "utils/logger.h" #include "system/memory.h" @@ -18,6 +18,7 @@ #include "utils/linked_list.h" #include "address.h" #include "system/stack.h" +#include "pause.h" void *client; void *commandBlock; @@ -68,6 +69,7 @@ struct pygecko_bss_t { #define COMMAND_REMOVE_ALL_BREAKPOINTS 0xA6 #define COMMAND_POKE_REGISTERS 0xA7 #define COMMAND_GET_STACK_TRACE 0xA8 +#define COMMAND_GET_ENTRY_POINT_ADDRESS 0xB1 #define COMMAND_RUN_KERNEL_COPY_SERVICE 0xCD #define COMMAND_IOSU_HAX_READ_FILE 0xD0 #define COMMAND_GET_VERSION_HASH 0xE0 @@ -76,13 +78,12 @@ struct pygecko_bss_t { #define errno (*__gh_errno_ptr()) #define MSG_DONT_WAIT 32 #define EWOULDBLOCK 6 -#define DATA_BUFFER_SIZE 0x5000 // #define WRITE_SCREEN_MESSAGE_BUFFER_SIZE 100 -#define SERVER_VERSION "06/02/2017" +#define SERVER_VERSION "06/03/2017" #define ONLY_ZEROS_READ 0xB0 #define NON_ZEROS_READ 0xBD -#define VERSION_HASH 0x39C9444B +#define VERSION_HASH 0x3AC9444B ZEXTERN int ZEXPORT deflateEnd OF((z_streamp @@ -100,61 +101,11 @@ int flush // ########## Being kernel_copy.h ############ -// TODO Variable size, not hard-coded -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); - DCFlushRange(destinationBuffer, (u32) length); -} - // ########## End kernel_copy.h ############ -// ########## Being pause.h ############ +// ########## Begin pause.h ############ -int (*AVMGetDRCScanMode)(int); -unsigned long getConsoleStatePatchAddress() { - if (AVMGetDRCScanMode) { - log_print("Already acquired!\n"); - } else { - // Acquire the RPL and function - log_print("Acquiring...\n"); - unsigned int 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") - log_print("Acquired!\n"); - } - - return (unsigned long) (AVMGetDRCScanMode + 0x44); -} - -typedef enum { - PAUSED = 0x38000001, RUNNING = 0x38000000 -} ConsoleState; - -void writeConsoleState(ConsoleState state) { - // Get the value to write - int patchValue = state; - log_printf("Patch value: %x\n", patchValue); - - // Write the value - unsigned int patchAddress = getConsoleStatePatchAddress(); - log_printf("Patch address: %x\n", patchAddress); - pygecko_memcpy((unsigned char *) patchAddress, (unsigned char *) &patchValue, 4); -} - -bool isConsolePaused() { - unsigned int patchAddress = getConsoleStatePatchAddress(); - log_printf("Patch address: %x\n", patchAddress); - int value = *(unsigned int *) patchAddress; - - return value == PAUSED; -} // ########## End pause.h ############ @@ -327,7 +278,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { destinationAddress = ((int *) buffer)[0]; value = ((int *) buffer)[1]; - pygecko_memcpy((unsigned char *) destinationAddress, (unsigned char *) &value, 4); + kernelCopyData((unsigned char *) destinationAddress, (unsigned char *) &value, 4); break; } case COMMAND_READ_MEMORY: { @@ -374,15 +325,18 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { startingAddress += length; } + break; } case COMMAND_READ_MEMORY_KERNEL: { const unsigned char *startingAddress, *endingAddress, *useKernRead; ret = recvwait(bss, clientfd, buffer, 3 * sizeof(int)); - CHECK_ERROR(ret < 0) - startingAddress = ((const unsigned char **) buffer)[0]; - endingAddress = ((const unsigned char **) buffer)[1]; - useKernRead = ((const unsigned char **) buffer)[2]; + ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (receiving data)") + + int bufferIndex = 0; + startingAddress = ((const unsigned char **) buffer)[bufferIndex++]; + endingAddress = ((const unsigned char **) buffer)[bufferIndex++]; + useKernRead = ((const unsigned char **) buffer)[bufferIndex]; while (startingAddress != endingAddress) { int length = (int) (endingAddress - startingAddress); @@ -407,12 +361,11 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { ret = sendByte(bss, clientfd, ONLY_ZEROS_READ); CHECK_ERROR(ret < 0) } else { - // Send the real bytes now buffer[0] = NON_ZEROS_READ; if (useKernRead) { - for (int offset = 0; offset < length; offset += sizeof(int)) { - *((int *) (buffer + 1) + offset / sizeof(int)) = readKernelMemory(startingAddress + offset); + for (int offset = 0; offset < length; offset += 4) { + *((int *) (buffer + 1) + offset / 4) = readKernelMemory(startingAddress + offset); } } else { memcpy(buffer + 1, startingAddress, length); @@ -430,6 +383,77 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { startingAddress += length; } break; + +/* const unsigned char *startingAddress, *endingAddress, *useKernRead; + ret = recvwait(bss, clientfd, buffer, 3 * sizeof(int)); + ASSERT_FUNCTION_SUCCEEDED(ret, "recvwait (receiving data)") + + int bufferIndex = 0; + startingAddress = ((const unsigned char **) buffer)[bufferIndex++]; + endingAddress = ((const unsigned char **) buffer)[bufferIndex++]; + useKernRead = ((const unsigned char **) buffer)[bufferIndex]; + + while (startingAddress != endingAddress) { + log_printf("Reading memory from %08x to %08x with kernel %i\n", startingAddress, endingAddress, + useKernRead); + + unsigned int length = (unsigned int) (endingAddress - startingAddress); + + // Do not smash the buffer + if (length > DATA_BUFFER_SIZE) { + length = DATA_BUFFER_SIZE; + } + + // Figure out if all bytes are zero to possibly avoid sending them + log_print("Checking for all zero bytes...\n"); + unsigned int rangeIterationIndex = 0; + for (; rangeIterationIndex < length; rangeIterationIndex++) { + int character = useKernRead ? readKernelMemory(startingAddress + rangeIterationIndex) + : startingAddress[rangeIterationIndex]; + if (character != 0) { + break; + } + } + + log_print("Preparing to send...\n"); + if (rangeIterationIndex == length) { + // No need to send all zero bytes for performance + log_print("All zero...\n"); + ret = sendByte(bss, clientfd, ONLY_ZEROS_READ); + ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (only zero bytes read byte)") + log_print("Sent!\n"); + } else { + // Send the real bytes now + log_print("Real bytes...\n"); + buffer[0] = NON_ZEROS_READ; + + if (useKernRead) { + // kernelCopy(buffer + 1, (unsigned char *) startingAddress, length); + for (unsigned int offset = 0; offset < length; offset += sizeof(int)) { + *((int *) (buffer + 1) + offset / sizeof(int)) = readKernelMemory( + startingAddress + offset); + log_printf("Offset: %x\n", offset); + } + + log_print("Done kernel reading!\n"); + } else { + log_print("Memory copying...\n"); + memcpy(buffer + 1, startingAddress, length); + log_print("Done copying!\n"); + } + + log_print("Sending everything...\n"); + ret = sendwait(bss, clientfd, buffer, length + 1); + ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (read bytes buffer)") + log_print("Sent!\n"); + } + + startingAddress += length; + } + + log_print("Done reading...\n"); + + break;*/ } case COMMAND_VALIDATE_ADDRESS_RANGE: { ret = recvwait(bss, clientfd, buffer, 8); @@ -671,7 +695,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { ret = recvwait(bss, clientfd, buffer, length); CHECK_ERROR(ret < 0) - pygecko_memcpy(current_address, buffer, (unsigned int) length); + kernelCopyData(current_address, buffer, (unsigned int) length); current_address += length; } @@ -1209,7 +1233,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { // Write the assembly to an executable code region int destinationAddress = 0x10000000 - DATA_BUFFER_SIZE; - pygecko_memcpy((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); + kernelCopyData((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); // Execute the assembly from there void (*function)() = (void (*)()) destinationAddress; @@ -1217,7 +1241,7 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { // Clear the memory contents again memset((void *) buffer, 0, DATA_BUFFER_SIZE); - pygecko_memcpy((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); + kernelCopyData((unsigned char *) destinationAddress, buffer, DATA_BUFFER_SIZE); break; } @@ -1336,7 +1360,23 @@ static int processCommands(struct pygecko_bss_t *bss, int clientfd) { break; } case COMMAND_POKE_REGISTERS: { + log_print("Receiving poke registers data...\n"); + int gprSize = 4 * 32; + int fprSize = 8 * 32; + ret = recvwait(bss, clientfd, buffer, gprSize + fprSize); + log_print("Poking registers...\n"); + memcpy((void *) crashContext.gpr, (const void *) buffer, gprSize); + memcpy((void *) crashContext.fpr, (const void *) buffer, fprSize); + break; + } + case COMMAND_GET_ENTRY_POINT_ADDRESS: { + u32 *entryPointAddress = (u32 *) *((u32 *) OS_SPECIFICS->addr_OSTitle_main_entry); + ((u32 *) buffer)[0] = (u32) entryPointAddress; + ret = sendwait(bss, clientfd, buffer, sizeof(int)); + ASSERT_FUNCTION_SUCCEEDED(ret, "sendwait (Entry point address)"); + + break; } case COMMAND_RUN_KERNEL_COPY_SERVICE: { if (!kernelCopyServiceStarted) { diff --git a/src/pygecko.h b/src/tcp_gecko.h similarity index 58% rename from src/pygecko.h rename to src/tcp_gecko.h index d483633..4d38b28 100644 --- a/src/pygecko.h +++ b/src/tcp_gecko.h @@ -1,15 +1,13 @@ -#ifndef _PYGECKO_H_ -#define _PYGECKO_H_ +#pragma once -/* Main */ #ifdef __cplusplus extern "C" { #endif +#define DATA_BUFFER_SIZE 0x5000 + void startTCPGecko(void); #ifdef __cplusplus } -#endif - -#endif +#endif \ No newline at end of file diff --git a/src/title.c b/src/title.c new file mode 100644 index 0000000..f833de0 --- /dev/null +++ b/src/title.c @@ -0,0 +1,17 @@ +#include "dynamic_libs/os_functions.h" +#include "title.h" + +bool isRunningTitleID(unsigned long long int japaneseTitleID) { + unsigned long long int currentTitleID = (unsigned long long int) OSGetTitleID(); + return currentTitleID == japaneseTitleID // JAP + || currentTitleID == japaneseTitleID + 0x100 // USA + || currentTitleID == japaneseTitleID + 0x200; // EUR +} + +bool isRunningAllowedTitleID() { + return OSGetTitleID != 0 + && !isRunningTitleID(TITLE_ID_MII_VERSE) + && !isRunningTitleID(TITLE_ID_MII_MAKER) + && !isRunningTitleID(TITLE_ID_BAYONETTA_2) + && !isRunningTitleID(TITLE_ID_INTERNET_BROWSER); +} \ No newline at end of file diff --git a/src/title.h b/src/title.h new file mode 100644 index 0000000..1274bbb --- /dev/null +++ b/src/title.h @@ -0,0 +1,11 @@ +#ifndef TCPGECKO_TITLE_H +#define TCPGECKO_TITLE_H + +#define TITLE_ID_MII_VERSE 0x000500301001600A +#define TITLE_ID_MII_MAKER 0x000500101004A000 +#define TITLE_ID_BAYONETTA_2 0x0005000010172500 +#define TITLE_ID_INTERNET_BROWSER 0x000500301001200A + +bool isRunningAllowedTitleID(); + +#endif \ No newline at end of file diff --git a/src/utils/linked_list.c b/src/utils/linked_list.c new file mode 100644 index 0000000..2cd25c9 --- /dev/null +++ b/src/utils/linked_list.c @@ -0,0 +1,51 @@ +#include "linked_list.h" + +#include +#include + +void destroy(struct node *list) { + struct node *currentNode = list; + + while (currentNode != NULL) { + struct node *previousNode = currentNode; + currentNode = currentNode->next; + free(previousNode); + } +} + +struct node *insert(struct node *list, void *data) { + size_t structureSize = sizeof(struct node); + struct node *addedNode = (struct node *) malloc(structureSize); + + addedNode->data = data; + addedNode->next = list; + list = addedNode; + + return list; +} + +int length(struct node *list) { + int length = 0; + struct node *current; + + for (current = list; current != NULL; current = current->next) { + length++; + } + + return length; +} + +void reverse(struct node **list) { + struct node *previous = NULL; + struct node *current = *list; + struct node *next; + + while (current != NULL) { + next = current->next; + current->next = previous; + previous = current; + current = next; + } + + *list = previous; +} \ No newline at end of file diff --git a/src/utils/linked_list.h b/src/utils/linked_list.h new file mode 100644 index 0000000..c794d92 --- /dev/null +++ b/src/utils/linked_list.h @@ -0,0 +1,17 @@ +#ifndef LINKED_LIST_LINKED_DATA_LIST_H +#define LINKED_LIST_LINKED_DATA_LIST_H + +struct node { + void *data; + struct node *next; +}; + +void destroy(struct node *list); + +struct node *insert(struct node *list, void *data); + +int length(struct node *list); + +void reverse(struct node **list); + +#endif \ No newline at end of file diff --git a/src/utils/stringify.h b/src/utils/stringify.h new file mode 100644 index 0000000..f205831 --- /dev/null +++ b/src/utils/stringify.h @@ -0,0 +1,8 @@ +#ifndef TCPGECKO_STRINGIFY_H +#define TCPGECKO_STRINGIFY_H + +// http://elixir.free-electrons.com/linux/v2.6.24/source/include/linux/stringify.h#L9 +#define __stringify_1(x) #x +#define __stringify(x) __stringify_1(x) + +#endif \ No newline at end of file diff --git a/tcpgecko.elf b/tcpgecko.elf index 0696c2ab3166faace9d6425d5bba8f2eb09c2b17..07946c62631a476e468b7061aa5273b6d153d4a7 100644 GIT binary patch delta 29817 zcmb__4_s7L_V|6nh+v4Qh~t1bC?XgE8rp;5&rsBN3Mw)xF#e1&7#3Tk>x{ww7D&bjYBnD^9jf8Vctrgz^x z=bn4+x#ym9?w@z5{k3oHGro0mxTX!^v%S+z#A|)dhRknuFHfdC_n9ugnFi`2bop+Y zq zEzOSuS5;Q>hx}v{sd7aX>S?ro@%;S5sZcEO&2L&0Klx^8ZIsu`cR2N7uGIV^yp0gg zcUr3t^?p6qm6YF2TlGPU^SdiHo(cpk4zW_5q{`e$ICE1Whyy4j=l4V9oQewM(P8}^ zx&jN$9JhF3eh;#z!nk2Hk5NTRR?>LL`H?-#m7U*0caOW4>!!Wq=8fwH9{Z@D_k0~Z zc<=eM%`-3uD30gdM29@_`^avjF1M(UR(nTy-|!qO*y?S>f`{SJA5Z(eLnrjdJ68}N z1`HS<5yMCkAT=4uFmljTMk+DCSH(2EQ~UUZQ~t2YN_LKm-6pjO6s(`Nj9)6`H`$6T z#M~ zTe74t{h3zMJ`-Wi^@V|$2fYt?g$8~{VW5?~u{}c&MIRoNHA56lQbh&B3UN6f27vD6 z`Y>@&BKp=K{et+_aU_uLBR*F0rw^t40bsW6b!fVUcRuQ8C9mzM5Q5jFRgq2}`_T<~ z+zo439V-g_%*TiiG}P%MymQ*YS3eO?kNCtae(iCY77VtwWW-$`Wsi(qkIGKc5tiDC z4H&Sc_HvT|XNLJGs0;5K<2_ITtb9RBeIr5(0+bd&t4ff2qy#N+x}xp}_4J(``n%$a z6~T^ZFG4n?+WE99E)Y?8U=&b#>7Z|@@pn-kDCvL_hbRE;6d^!ECtcPQOs$yKrRR zN_IbmS`f;jJ2Ap;0h5-ErUq2i3g#wGlK+Wn@QQDNPtS(IW1(?V3?T@R?9hou^U)J6fbf*e zYZ>jD9Fdq`4%w|d3|`@{ryWe&j@d0JNmr`fBAc^nnf`t#KG%c(ylJBi$6*ds7UJ;U z7zi%OL2IVWSuko%7#xN?;wWZ7IB@z>>GVvQt+QJ;P6aVSbAG>IooKhn)%Wq+NV=py zf0#?-rzTEO8nyG|(MCC^Ml8LmMwN^;YUkr%ebZ40;3j1Y7JaKsvgI;O3kjUF;EW1> zHBBoEyiHfoOFIG^?paMg1$&-A^r)wD#da$<|Bxq9G@Gd3OkNV69pnr4doWO|hDe7F zPUC6sviWOzH(+G%tUroEPSrLkK9A zciPsN4(GA#5-=v`HbfA8f{@+myoTsq&xkDA!Ju^AadCL#(_n1YxlFWa`t8A;iHC$? zFr^?G+ky0q`X1tAb`w4L)*h-i#QP&bzLzxCeNFUr%jqo!lMWz<=a}?q&e$4SYls13 z{ujf%(792uTOd;2LK=n5UxF1S0m4iDcL__4ffiTv&W$s|Gx<&+vx<en5`g}$it=Db-Ezq&1D{KRhO$N);{~aZZoj5boW!9{ z;Dszdl#rF1`=L|FE~(V7bMR0r->DPKDd1PdAAFsac3uk2liRTeQ!N-VVWUx(XQzhX zS)7~l!7;Zdn1rJ4yM!iVV46!jf_3sX69h5&Xnrh;c2xWlJwkF%?DMR5hEv?6Dg}50 z@+9%iyzCSkMEOfj(p|wf=oZ$}fnZC#(&rUxrB1@W$BsgI^1|JNR&LH$$po5reSQnR zJ6n$TQCkQzQWYVyb$L0|9b)u1w<1rKU;YIgMgDGjFeLP$Ik#V4eCM7T6Q9|F#bohC zqS%=Dj%8=_y(+QEQj6-ig4E`5+0-#}_T;>{lul^ls+0RudT~Z`lD;-`o?l!y-^tlg z73z-BqcbCQae>q))EdXMbR}r%KRmP)8i@k1a$$c3?US?=8xC4ZouS61U?AXC5jhw? zy_GaPhkRA642Hqrz8#$o8c4})57kR%_r<3qqdC-qj9AI`6%8-|QPpq|VefC8fg>%d znr2-)fAUa|c^2_YJU29$0;Ce_&(LSC4W7OI->w84N#1V3>mA41@C?U>RE!xzpF-I~ z<$EL|bK~g1wV_M5zMdhTiv|^eBA%*ky7lsXGvpfU8$R5lZ#1f%+qZF6kkOuQc1n7S zJ`eS}{z>9vsnXTbxLLD}*GCHLdhZgR9Mf6n8VtPiN@(e<(D{oUNT2VN)fm*d4*T6x zou5Q?=7(tOtogau_cLk+iCPL2W!eq0NpZ?r-U-ZXyL%8dfc5@@Q{AZqwcu1|;;K_+ ziS?)4iAzpp#cpMzqttDH&4eVFpnqm%=OkFfC~@ryX&Hcpna(+Gvn*Z+N^6vkwQL_UL0_9Z zIc`kcwxkvX$V>>D5)dM`qH^6!u&6O}T(XH&p%BqpVnB-diXiy|r~0fUJU*FllY}27 zU$xA~LY@mA^Hrn%1~kjE{dBb}g-MToG-swFwML8(84oIRmIZUukY8x)M~jc0h&=EI zhRTIseaWP$_#ngwK0$(6eq+P_)DZHY3P1k9xo8;D{xx5dgb8K=mJhrM!SU?6h3L0O z5T8bI$@9>a6E@6RQo8{L^@hSFb+4x`sjm}>&xS%0@Ommtvh%_v-vo$1!F{mna@-^5 ztOWFCBNd3P>g} zs>g2JkS{F?$?!&m^Uj455Jw4)h7CXrewi8KUV(^x53;<6NIUQ5V1;h_ z%}@=WWCN8dI(Hb#KF$VY6!A;yoFv)~h+^`sgGBgsoF#3`0{gmG2}w?d_x11|yk$YQ za|^g`8!Yva23Slae)taQS7}K^sL=2}nmrtbb)caz?}<-^daya>C(rdc0fy67$y04Q z2{I(>j-et6%6TusPLpwxumfbAY0R@UaY;!|dBLyxbT+@^ z^ltvxl3G+|D7(j_u6Bw31Gw(e@F}aHiS#+qSVP<;74Ut|qrj0dh8kX#@#~#JRtOIx z$VzJO!N6W-Y*4T(+S9O>+9HF5I6f7GXI?r4`Us2c$Ff3%qyGvNo*uMVw#0=vMX1(I zxQe53SqTIb^MzA^5)0#>Vzc|3K0<$kWJ3tlOYb+*}#TypBeN{m^U1?=V0hBJoTj`)kdl^#DDLT zMsOBkYe?MG1eZC#DakfT1T$BK7R6T7%9fdVcNWP;;lZl)0$2lwCEZCP@62M$xeAgr zq+3bFA<4}O>(D%85fCnzf@$N_l}&jcE2CL4M(>70NRpPt1nG{~(D0~*x>@^aPE=l= z61-eKVnE*)$b2vQTxEzUJ`HM>J;>Qi&p;;GB>6rXFn_-Bx_)G;@~gi%Br#83Am`$RHf~2WqgOX-lZ!)DhXZoDljU9FqG~$ zPSiJG&6n|(A@)86yv?AuFYp!S?i7xG7AS0Wle8vrKSXB3*g_nW(8*=|wrm?;bS4tJ zLJ3@4Df#6|ZtzG#whi;3;|agxOgw)qJCD_!nw=+k&xk-zT*PDeJI>evH;3UCrDij~ zp0;(K`wGZ)fSfH8X3K7XYq|n%7k}*TK?(QnL5YdENwhaQadvnMw!w_yi?F2jQZMs$ zz*U-d{|hJ}Mf-6ic$#+pz?t)sX+KqqLQHbJbLgWibijfbR&; z5Wkp+EmlmwIH~9EHCQ%259SytMPCX*_z*##c)pca3@Pp z1SkqyHQ~lY@te0Y#G`+`yeNJXwk4^PXJHqL!*+mhia6>H*>gZikI<||^L0T6x<7ul ze}289ni=AkhZ$sAGc_&JK*|_oI-wPdLjA{t3(GzGX_h%AG^mzMsO0!-9mFE*Qb8^+ z9JHjp{jbAxXi-uCEGe{_k7fBaidlY%=V44+7SFzSGPos(*{Z~D;mLml3Y)qS-zwaH zFe`R*7D?M9R>y8;uE&}7w%80L_V5DnMQ&l0j-wpXqI2txAm4-S~*z}LUE1|+ZI~gXm z_3~&LRaOWG{3NUIchGzkc7FE|PT1NF!S}>oVM`hKdeL3^(rZx3`ZLUFkn;CocG3k* z_?Y*?REf=esXsrHbJA!&G^n5_L;PEABaj`_>s|}B@e#T&vS>$2Oz7kiRZjyE94S@D zU?U1f1wF#gUmVnu1X)QS46_YZz9RBF62Hms1cKqXY63h$dn!)GZk{TpZ6U3dLH)$C z!9Rq?C(PDWtfe^#i79>`3i@{(6;A{^?kfsNq}jv=ySt*o7|2R<R zGISe`1YBntwMhq$4+#JD(Wv`^ML1YiNDK_Yc>!9(4XLmYAW!5f>v1~#&raH#7@j%0 ztartEu=lHIfUs7S)uonjuvW5!-zMSs()k;x(;Q*+j!JUFI-fLS_vLq72#5CZRZmg%+#s0)-CJsN@Ocgz zwwOsO^W}m?0^qh9?dQ5Q-(+uWWZo{6Jb$uWYzMa3bY30uoxX6VgM0PscM z!%q3SHAuo03Nu9@hh$K-kc|0Ib31w_Hpn4V<=G&AdM*kHy^^egs)3F@fU`%`Jo8k~ zsUGW!Pr(vj9}X+mHi4OszfEa?#pR#vAtU`N zwTwQk!GW*yYo!Az5&n~!5~5&t@K;u_Y?JD!Db+Z|W75eu>8?OdLL`an8`RXgJCJ`` zcYQ>Pf1PJAip^WM7n*nUtw9#z6X#v>^VTFAPRhTlhr>ZpvWRNwVCt*|?N;KvC_!<1 z*LhNS<8oWO&QCMM|EX0f7ABUdDt1HPq(V36OG_of`YcP@))_g_Z$83$3#1K$&Jpkg z>v_z#Igb(poAqyvJ%j}lZ4aOMG?Ad2I$bj`52^|u8+}u+=zWIk2d{7>c0QD zo7P@G|F-dVmH46GpV@})t7YB$DT z8wM&+$;pTw#|hSs9Vf_)pC!O?g0=hPHvPnLg34Dj#3Nbgx)3*+K&1Z4fZoGAKkw`x z@PYWyAS+!S&|M+bUXlizQ}NdYLr1JX)o<>E83c31TsYq=hq%RN_>cP77eJa@&e^U-i@t{hq6@}0j0y#2#JG$ux&8~kfGwjY3=Kx%5Jn&z zbmSKhU_qDw3mXtUB3xMdSs=5&!lo{$@azCoi2k&(?lFiLKJ?g=f>m@h$X4>>BHV~W z^?)HUx^r6j^h*w$R3eVzWnGKZE7t2^!#s8qE>nGG025uq>RBVjCBkT60t9%d%u0TE z9|IHAKdemIqLsaZ-FfhMzcw&In9}MD@l?+`*d@ylKRR;X(tt#Guft7;46&zI?lRt^ z%VvPn_^<_9Bu7RV)ckNe?rzYyntW_=k`8s?s)E|K%>}s8{A2O-K`wZ zF%frDoO`o#6y`9fNpnteBh$Fh3Ilq#0?QD)0+hWD+(0vfu^SC^U1Lk=#t+Jksw+JmA(M$3ev;bTk;?Z0#_hpDUQQ1~QVR?7(*ChT%P(c~-9 z`)ZtELuKSr=FW~D9#SMU%A7DNqI0F5YO9DyK}5@wA)Zf{CGk_0J@)gG7))2{5|Hra zD?45xxA?HS#qTSNeT`S{jx(}59n%S=As``T?a~I_AzY#q3Zm5Ff4P+wqgYY#z?uxo zjLlLrc6h&>AE?RSDdp>HH2L+;W3Y&vVA8oX0M>HQ{44C|qHRAj@#n>S)_J+{s%oGh z{wqLf;T4yH$HSFqt{T7&~a<&Th0R|PqMe=;rNk3usGj6kF_c_K`k?qWfy|Ap+Cnb zIyD&&w)#&6T)vAuxqsJvnLXrmsE38c@*c9F*t<(6E7%O!KeoUxJwyB?{W8RoG{k<6 z77w&wgYn~mF&emJ*n$G4l2M-ARJWCkJ8H198|!aLRkTEXeXH9XvYRPW&y+?$oi-nfH>Y3;`q0c z@24fpBLUasZeeS39jN#T}cqR=MeB{4sfat z{l2?dsX_^CE)D%owlKdWXg2i}M9JK}mo3}Y#>+cFG(`iSYAaJr7ceHr6erJL-U;44 zD2#>3KPpj_6xd#|bptE36)H@7R2X=<3Kcx5afi`{3uBS=6)QY`;s2(>1I(La_l-$Y zfc|rPoD?O;pVd}&8F40Eu5}8Y)Vc%AH~*}z2;}%oZH0=M#>9Cl`f?R2cv6Lb>qs!& zIc$dAcZ@DF#82&52Ha32(2-pi4yA8@uhfUmBwOuOq$5zUh1h?2ui`l+_#9WhJjUp^ z_b7yMjHcjc6=(Xja~t*8{7l(NkcauVqd^Ih5EzN9pC<9CTREzS{fHv5l7F*p0Sq^^ zeiPxqHTcbw)t76bN~E#kAlgC?xBu_ElqPBkW9L&-wvkPoJbmx}fJ^!7KXWBAWBi@E zY}~5iPdtR4(xV6ZwyP1~+YIsDZb%nP z>Hmgk;q_c8y}yIE;i<0WzsIqM3-B&^i1E+blz+!; zfPxv~o_>6X^?_!1j)P|tJRx7Kho=`jYvHMbXEi+G>nE`Sp3qvc9G>IhSqe{Yc-kRE z1v=lDWGTQHKq-7Z>YtL_q*(fN4ggFF^7l2Mz1f#`iKo`>>xN3?-8HQIW4(tKE<`PY zibj>0_U23%V7OjNRjqFaI+AQ4PuyuK8VW%UtmMI&u=L{xO@XF;oeocE+Sf_&1krpQ z4^I%y*U|6<(R>{aPY})5A@BsQz7CYm-n{a9?7G>os`vIkN?`TS246q6vJa45#FFZS z?V2WgycvG9E_c4}LNx8j4V_Z3pK>HFm5^#0M&iweSNiEdZj5m6AylE%UR-!7=z#N2 zB;oG`UFIk4(I~&fRTpy1d+gT4`hsq=XxAscd7;F-wBTUkf(z_q1GWyoOf@Iuby!K! zSHkpn9ElcK2_nSd0xd}(ma5gZ$33cgwh~zz)JA zW%zS4+#r7|+p07hKPJNu%ix>YOexqrJm1NkvZzfkC^a< z%Y!&GcNZ@8IPhVVLI9+2n0Lx16Y{O(i|afnuulOmoYxL58d)$~mtRdY3l`&}x&Xe^ zuBNR8@Ev~juI>UaZequC;Bw(aTR9lJOlq@Dr()qX#{1bI+z=*x;Dum*#}7FQ%D5hL z6MW%ou?0;)z(Ai2nC}m-z$3l_zMFOdxj)*bsbFT*TVkzy_V!9b-tX)m>VlU0It7{+ep`G^WbejfqgokVmrj21vDxNyg zv*%9H*y3wdoi@_K5QSoO<#3e9U92W4`Z3HC8cZ>s1}xWL+8Jiyay3n{mO3?trz{G6W#?$p2~ zQ#Dq8rv`2rEiXtF^k@)rMiChN$I(yuB?ixxY2Znk@--T`wgGIT1sfM1~8ph=(;TIB(DpC{b5%K28H~)WEG8_+bs)Ccy=zk1w(X zE1d60B0ld>6S%0+k@IyLc!j2d77bh@!SkIAE{rP4#Q_b0ZZ&}sg9bjRfv2nC7ko7E zQVraMaA8c#M>INcf!7ci-I0?dbRkOv@7GjNqK1o_23&9gT<{oXqP7K%8UkgS3R*Sr zCJp?c2HvTG_a>X5|HmXC_Gl2!s|k#Gdr1O61Zd#Ay8I838hDNdZb`lq`e2L!;?N-E zXb3cF;AI;4e!Bk`5KzEr(3gz!IlP+04P6$$X3|Mw>AvF4t(4agA z2KEqBu$d{`@dBX(2S*DZ3DxB}sPmB^Z)U;AVbAZ;ZgrH}oy7qA)kgdiX zm&q`Xt(>aMFQDHVG3HzI_lP9b}`Ho8cbgw8n9YT z(^sNdxLRGiZw+;7F*_N}544yN+-NW-#6uS7PUn68| zFuNJ%F%9NK!^S7nh!brL@y;jIBquh~GA-s|hPhXZIZS&snEufWGxkX}P5)Aw^`yFX z|5j>go<(1Iau$mU{rA(uPcCFpAs6mpY+)DTRGiw_a+w-Ud3gjX%_R##C4y{Gr5v92M$39V#Md{Gsl!x`NXh zf2bSQz%~BROT*x4i_Cx$6?%CF*1*7NjX(6-uc<)e553N7;5AAMrLb2Yt%18G_-It9 zw`&mEH3S+p@GcEpE1`Z3TqB_&!r+2U2}{RmI26X2dv;rL7qfo=_fY7Kl)1Mg77 z&-iHI8VQ^+X`(`(NDTpAO~6N^BWJQS@D@$^5)Hgh19v9FhOtLf=xfp-G^z>s+BEQ1 z4O}yf&m7dik7&yGvJK<0uylf^kItM|6PRFC6F3{7for7uiOMT4Lj$7gfY z@N42V43uf$o-+qfva>ZB_)c|s;jCMO(5pr`+pdQDX(V*EO9M~Wl<(KT%T;heJV!JL ztttfZoIwL0)WGA^@QE4;owI7-Dh33&7R9DP&}i{FhX&rGVW3V8_xIDlTQqP_2~C4O zI@hT|faTG3DYO0iVFF`H?IcwjQuE8qBFuHRlMtlsx(zm5x56T6-@4foV1bj(ZTw-j zQ61xr>z$M0XnOTRDOo}*lw>X4tt30>fRY@h;ZI4p0qHbPN!HMMCD~37E6G8se_Fzw z97mIsWC<--lI?V-lI*8_a&n4+hWu)w4*2b?38oMKDwuIL#Yt;_B@vs_LH8@kVLB`) zr^eCvnuwsO<+#VhA0s)iC7X>4x;V$9w(1~8W@-hkt&zw~-AVT=$v!$PCj%pC{CWu? z&`wL0WHW76l5ko{PEHG3^J1I<>F>9kf!meKu6(oKhz zWEYLElZ&0FrE)UZM4Rg(?hVe9J8uX&Z&2wx_{2pI`*L`4o!#ro?|{>-$H@7gfWz0r zWLSZZc7AXRP1+#U9o$dLLrB17@RTKxIljtpzGmj;JwDMvh@l^)!1>!^w0Hn3eVdm8rFK z7^qC`)KKx@W$Lg7=D|xKe1Olm{@YEtuZ+J`1>3Z5A5fj>mufImHw zb~G9jr{`$!9b=f&Yc#NW4_F5c*aTDtY4j#zqQNs-0{%|-k6{|J)l`P`9ywhroMw7nA#fp#$d*9YTIno8M9~w zP%%2yRL<}on4WO~@Ny>G1J*;kfJ(5Q4gi&44cBK{#xR35x_8#2k3#&YG9sD^Qu4P7l9e44qk_q2kd;Gk5ROHzjhoh-ukk6lM-H zf8Q%FVkYO8F-zf+d_G?kD&3?lFK(jKN7DR`&_KA7+5n53A)bq$9`JC02mS;+9^e7a zo32bC6A=8g1dv%|0P^ujC#QzY%wVrFgJ7DWBGTPE1ESob7@405)|uq7B%9HHQ?L{{A3*D(C57NU*5)OLE zNs}M-dr6{eil@;pu{Cwmj$rt{96J5ym()$ws2r?murwpx)xoh3@zKL?NGTY)bgkXg z11sxBZ!!SK`G-c=+rt7emxeY6DjU9R6+J8+maS5lkiv3c$tsv`Yo0ENuZ4Cr%d8L5 z9yu8nPKTS7oq>Wd=hZbMtoG7okR}YirDhcBFKtHHZe-Y>ggZGBZa!M5$78qD)*Lzk zw^GA8F^e(m^|HipSR*yP91+@*1BXKb;jD)Pzt)87ctha8NC*V?aIkR8cf0H_KcUwZ z`qAFs1>xiT??QElTB+f+J9O_Q(URBh)g9E+&exuRgIIX;v0`4FTMOLGUbtay0N4Io z$u#8*R1@N@J6hmYaN&@j9!HiMYO|{Y5>p{u zS?eSb<|o7YcV++H=mobD?<#8zf@KTC`Bd#XW>*Ns^6F&bXgK14ROI-IPHI9imsrB4!;qzywD&IN~BEI6%>q`Yhcbx zfQbi#n+s~3LScy6CS65JD^%rtK(?dhghS9&D2wG#FDR6wb-UoJ|4W31lEsSI>b~eIj?VE60KB9#vX!7}`ZYe5#Ymj!mIonv^vmtFv z(D?D01fCmC{r(iHgS&X5|CH!Gx7566ZWAs2)9-ZfFtjb4645T5#`1j%8{=F7Q@*^m z(Ag1AIh!4B#cb)O5b=maHo{Gt+9C=fClXzU6ez9=HvO2&VM9R&yfq-6;= zTRm*4Sk69=gL|_hSj>j^IA3xA$#UI-m*57Nzxrao`M)zeKlT4?E8LFl)u--7c?V7lLWI&_{zYNYoT ziP*>xHV*%O2VXR=gl7F22-ef;KUap%>q&)^m5Hk&2U2?B%-Y|hk`n79D-z#~%uM_w zvXh$r^5Tv8O_g(=ajf-~FWxM}i!ewn3Kc$$=A0|$wk9qinP!p52MG0F30r#Mi(XNR z8H(oZruvQ-b#r&qmJaBzc6zYm4jtSTX4n%d%ROZ z=M5|2nqqliv8w}`2>WOjPeek2ol*fE{DF##OzWhRlJf?34eZfzIyka8`1brMg{HJ7 zVndUhYylOxtpFn8y$dH49Ohln6rs>WOZG1GE;PZoIY`}mjX{Mbuty>A*I7(=IF+FW zSY3OKI(s-B*c;((4>#{Auuwy1OsG8^F3h-yhw2#huQl-ScCSq>Vr}uer{k!UW z9_8SnKl%a3_0c289_9vjS$ai1KEC?PI1c{$%D2CTi@a-(f57$a${TpaKdN`b9Jp`O zi64hw%m#IH@xC|e>t^8xI2Y65o&5y>r|+VHUC}?@q+8&DK9ZzHABl#)CNcs8wN+Kj zeetu&z-U{=ByKi%x$rjkR4&n1x2U|WCxnBkHh=;`xqo0A8~RM#AB-2mvBO|UJ%>+I z5z!&Ngs4SF+IEI<-;UQUu4rhC;+EobD2l6sXPaX__k@mRL_-1qW!G^x>I!WQyRYM7 zLJL~cwh}hU;-R5vr%?^@Z-3MYg|_EoxFB6Ydjni?KEI&Q1`Tjxg`iV5P^Qt<(*(HK zZbNFJ4H_u|P98I4K#niEchnUiIe=O4PpP1pMZ181N+q=y6a>mPxS_5oKvNzK3QZbU z7#PkfT~TOb-l2(@kO1ZZv3}s&0?f2OA1gI0J(h#3>Dn7gV!7K`Gh?|ICls^;+O5vD zP>(H+JBs<11>8n&){gdu?giYxFt1}FSD@sXuIK(6^ZKsm66L(M@ObVAKb_sL&6LLZ zbC76=yAgzHH?-}%k@MGw=Af(Ssl1aq;MoBuj050YMX!13eM9D>IoWO9w{TN9-Mw{f z1OLSx_15K>8V=vVt=8qlH>BUm{RT>l+u&M-rl6$t9C;CL;rlPkmZ>!HJeNE(dC4+4d!ug zd+9L8#{IxS*@m4Da0hfb0S)Cv+!9@mq3vK1_iF%H+p>$fml30FcscaFcaBd&I=mQG zaPP79_C3U<=yc2$qaf~_yBc~Q1$E2a*lhv z$*sHSv_)b%)k3Nc`sCKIe2$p*{W-VvVr@<$(#XZpaq6KS$&`K-1!rO(69!5;sh| zR_@!JTZ&@@+8vi*5Jgr)ypwx(Mz3P^A*z6BGBG9DaJjCir7h6KZT8mPf3U6fSKQk^ z*w1Bkoc@M}ns>R@hVpm0KtUW?Gc1lg6b?Tp{CdQZhvUVO(mHYE7ds``Bk*1ZIEc5Z z41Nd2kvr?9cg*`={e=H5f&XQBLE(O7^Z$QKvbx2QyFf~JHHstIA>zn=CUInGlQ?qE zkT~*Sz)#W@nD`#p|C2~RD`H2aI8xp!jy!G=N9@)A2`#z*Q3}iY#gP?V;z$Yf`wAea zNap|Qd4G;Lk_URGqNnt~V^9LD_8UW3-3bLiGWm7lNI~FOzKVcH{}-12JdU>PX#Z;g zSMzQQ7^~10HH0gy;Em_?3`#&0X+>S?75-ah;1M zt_w-*`p5^n^qP-xMEd# z@yb;Uoz+N4)}s$2fl-*8O;!~@VwY;U`H}LKYfNR2u6U%}1e~udUbU)i)BkX(QGTRq zZXo;&@C#|n<8%kSrbI@Pswh4D!0ts=!>_e1ew=RMIN>x;^zfe#{G2BY@c(fn0N$ju zH!;C~Qd$rB0mS)lxUUhOEMHDL3H-;>fXfk<#*|Jkr#T4R16bN*FJ4}{ti0H?AZB6A zBGXNewtY20w|v6fVN$gi*pAABC$POZ9Dece182238-8|#wUtfNHJF4rQ4E3aiSQS^ ze=M!!DbJu%fVWHVpk`5=(J7^kfsk+g`JX~Z`xLYpQ!;^gq(oeKdg9F3J;vnZ^J8gG zoXM?Y`G%aav?q?77Dii-8Np?uI3-JB04rwSd^g~#e{IeibUx#gM2~-@Dz1W5MK=?|LHBc^fI5j(#Y3mY*2=}CJbbBI zRMQZhsxwY_Acs^vaFA3L@eTXj++44!dutogwsXI1JCUlRUJ?CPNgq%ruKa6gxk;Be zrLcrl!OxXzAg*TyNL7x#Vdy5^gWO<4Ub=49f{|d0J1jhOxJ}5+2Iq!bO+&X=4D)b_%!sU>wI2NH)?6ujNNRb6?0TnDrA1=-1i>LJT2?@{pDwR z@11k*x#ymH?m6fFd5@YWgKC}#s-4G0*Tv2aywObp9=F%!{+n;ULiK>3b4JVQKe(!_eo|G-aWy9IUi&tVvu>Niat~6$(}t{vin`1`qs(!UqnNF*%?MsA#E+PBFyYYnTm3S zy+w&u@~6KjbSdJAZ_BM*<|%Af_AXuh)+|Djx8gmCMjFHmjxPkDIm+6evrAXHx%HlM z=WWx<%@$H&zro?o0OspnbVyjh?r^xFvP%2jeRk(_@p*zoh?O(#w36o*LE|c{kG`nhydYrTEs}1IE6X-0*a`7{rUkl~m92YSW}C-Z z998aoAgUbENXCjFeB0S@c;*nEC6NkKC*e#@MMj3tC?p^a=f@Ucv`cpjUsymRCoElJ z8$x;gD3*=E7+2(EB@Hg=14pkb-_}W`2@AQ6v}MAz6E=d-BeZWq0uL8mV8Yz=^#}ou z69qTnnXGS;Hsr1}afIdt#sx0@2P^0dH2j276QG{pL(L&|TG|&~dsdKel+U$+=zwOb z^d%S}aC8NcP-*5DUD5`kXRM9|#wCrhnu;Wfq8KYzx^396iSSHQCN2qk@pCKr!)?jC zWetFtx6!hRt9e@+?U^`Tta{ZgxEDFCy-C+T>E^=ZGOi&Bq5Wjte zqb0}dH49+vaLt#?6ospF3fwGPzt@AtgQJH2gM`#_B%HoU^kAvC$?%hIvz4?j2C*%I zbYBR_Io~NpJ)B)ZI#DBn8(`ehuURD<3S&*561`a>lK9rSa-r&BBkk2EE&Y%43a?J! zdk9IlE^xFc*1jBZS)C}u$^NWe(EYr$#{Z4H_m^TOxO4N>KZ0ff|LIXbc{xNsy9u;&)69;Tk47{d!M_>gn~NeYs< z7OWvOK6qi!I{69A%N`nG7>pK<44TC+zAeu^vbX}3te|UYP4MiXAYWpU5f@*HSXf8& zKC&}Y0yA2Ia4VPklF9~sg}IXnyaefmy@Sg^02|V21@Pr5iH?q(Ro>Aif%V83<2$dy zxaVtBym3UK{t%Z(Ulf$i}){tdfDV0Lz z1!YuzLK?wg9HcEFG2&n|if<)t6A&bACe5lkWVg=c%LXzyWF;?uD1P>Bd6peL)=Qt5 z9z7M#o+bADuITuCIncb6WvFEQgi{GL_kh4vool{FH$Gg?4D2OVTdXox;v$WQ*Oe61qvtiI!6%+o7POb zCm9l6`dlee4;6l#W&~%t>&s&h~+`@+4F?lG45-ZueNFIt@G`0kXoePE_ZJT^Ua0(k{t_8NvZBrs* zP7Mf0zS4_tpDRxzghRG|wrnqo;0xnv?v&)9G+8HL|DkJVOb6>bb?`Fs)+sT;$)8_F z*n1hFF7)=G!ZA7+P%&S{f*|=qKhcT%PhLK#>O+$zZ@a073vrPX2c7LPd(hKCW3H0r1qkDR+)JIBK2@{YsXrhyuv zIV~?$dL4)5>Q574H(4&5%Zqzy>a_GQwNWj1j5SJ{7PtBbHL7I1QGYlO&UQH}VccOY zJgTtlyh78W!{;r2&=3DZnpPTqGhaANJHi|8c%FU??mGq#%mVx((`uJWXJWOJnplGuQK zv}wlAqU>o$#S!qFpc=blc}~5P=uK{-gHSp|b&;u)ks$z%(mxPg?LG7-kwzXc(mbOs zTS_`XYa)}l)AY}g*T%%Ykh~Kz>z$-g-0>MWO%f_TH*lM{+7NDWEiBtMGd9=xXjD14 zp`*2%pqs!p=q6?##e%r9#>&Or?@G0GvM##&@8#0Ilf|r?BvQO(-yh16MpdP2DGY|v zne(UE3D+p*g_z!eAIjT2GbKewdbN>h#yo-ze98oMN=pRr4-z}C_pW!AuBikHVG86MORraK&?V2FPbtTjC#KblD0THt+^b5-x&eX`lQ3^q9KRHT zrNO|jani`B*_@RMQAs~r)+9RmZWEgf;n^;4FV-p8jgYh0x0p#471iO~EO!>LBA`c1V2i;9h#=nX zv2yd)W3yb>**ftVJB!+*nUiux&*j&YQg^grvfx3PwtV*Ia*(N;c16eha_+lVR^P?P z#?>bbV=+a27E~Kk->3Ny0sPdaXl;S>JG6E!H%!ZC&7Hc&nhC?(~&s z@{)HkORRR?3PE!D2jv(jw*
V%wUg*+|6k>sSC=jfROY4Uc1<9MESfGs-ZZo%%5 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 C==A#l