From 96ace87a0d520fb67b746ed78fffa591d3994161 Mon Sep 17 00:00:00 2001 From: radiomanV Date: Mon, 3 Feb 2025 01:54:52 +0200 Subject: [PATCH] Code refactoring. Using LibUsb async transfer. --- README.md | 7 +- udev/60-minipro.rules | 5 +- wine/Makefile | 10 +- wine/readme | 21 - wine/readme.md | 48 ++ wine/setupapi.c | 1200 ++++++++++++++++++++++++++--------------- wine/setupapi.dll | Bin 107832 -> 112176 bytes 7 files changed, 835 insertions(+), 456 deletions(-) delete mode 100644 wine/readme create mode 100644 wine/readme.md mode change 100755 => 100644 wine/setupapi.dll diff --git a/README.md b/README.md index 382c841..551f2ae 100644 --- a/README.md +++ b/README.md @@ -65,5 +65,10 @@ cp -R TL866_Updater.app /Applications ``` ### InfoicDump utility -This utility can dump the `infoic.dll` and `infoic2plus.dll` to an XML format database. +This utility can dump the `infoic.dll` and `infoic2plus.dll` to an XML format database. More information on this here: [InfoicDump](https://github.com/radiomanV/TL866/tree/master/InfoIcDump#infoicdump) + + +### WineLib wrapper +This library provide an easy way to make Minipro/Xgpro software work in Linux. +More information on this here: [Wine](https://github.com/radiomanV/TL866/tree/master/wine#) diff --git a/udev/60-minipro.rules b/udev/60-minipro.rules index 6a7361e..1e83cee 100644 --- a/udev/60-minipro.rules +++ b/udev/60-minipro.rules @@ -23,7 +23,10 @@ SUBSYSTEM!="usb", GOTO="minipro_rules_end" # TL866A/CS ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="e11c", ENV{ID_MINIPRO}="1" -# TL866II+ +# TL866II+, T48, T56 ATTRS{idVendor}=="a466", ATTRS{idProduct}=="0a53", ENV{ID_MINIPRO}="1" +# T76 +ATTRS{idVendor}=="a466", ATTRS{idProduct}=="1a86", ENV{ID_MINIPRO}="1" + LABEL="minipro_rules_end" diff --git a/wine/Makefile b/wine/Makefile index c93b68e..1300186 100644 --- a/wine/Makefile +++ b/wine/Makefile @@ -2,9 +2,15 @@ SRCDIR = . DLL = setupapi.dll ### Common settings -CFLAGS = +CFLAGS = CEXTRA = -m32 -mincoming-stack-boundary=2 -LIBRARIES = -lusb-1.0 -ludev +LIBRARIES = -lusb-1.0 + +ifeq ($(hotplug),udev) +CFLAGS += -DUDEV +LIBRARIES += -ludev +endif + ### setupapi.dll sources and settings diff --git a/wine/readme b/wine/readme deleted file mode 100644 index 82c0672..0000000 --- a/wine/readme +++ /dev/null @@ -1,21 +0,0 @@ -Simple low level winelib usb wrapper for the TL866A/CS, TL866II+ and Xgecu T56 programmers -This version will autodetect the software used. - -Add the following rule to the udev subsystem: -sudo cp ./udev/* /etc/udev/rules.d/ && sudo udevadm trigger - - -How to install: -1. Install wine, libusb -2. Copy the provided setupapi.dll file in the Minipro or Xgpro folder -3. Run the Minipro.exe or Xgpro.exe - - -How to compile: -1. Install wine, wine-devel, libusb-devel, libudev-devel packages -2. Run make -3. Rename the setup.dll.so file as setupapi.dll and copy this file in the Minipro/Xgpro folder - -You should install the 32 bit version of the libusb library (Debian users libusb-1.0-0:i386, Arch users lib32-libusb) -To compile the debug version pass the DBG define to make like this: make CFLAGS=-DDBG. This will print to console all the usb communication. - diff --git a/wine/readme.md b/wine/readme.md new file mode 100644 index 0000000..13f9a98 --- /dev/null +++ b/wine/readme.md @@ -0,0 +1,48 @@ +## Simple low level winelib usb wrapper for the Minipro TL866A/CS, TL866II+, Xgecu T48, T56 and T76 programmers. + + +#### Installing the udev rules: +Add the following rules to the udev subsystem: +```nohighlight +sudo cp ./udev/* /etc/udev/rules.d/ && sudo udevadm control --reload-rules && sudo udevadm trigger +``` +This will grant you permission to run the software as regular user, otherwise you must be a superuser. + +#### How to install: +1. Install `wine`, `libusb` and `libudev` packages +You should install the 32 bit version of `LibUsb` and `libudev` and create a 32 bit wine prefix: + +```nohighlight +sudo apt install libusb-1.0-0:i386 libudev1:i386 +WINEPREFIX="$HOME/wine32" WINEARCH=win32 wine wineboot +``` + +2. Copy the provided setupapi.dll file in the Minipro or Xgpro folder + +3. Run the `Minipro.exe`, `Xgpro.exe` or `Xgpro_T76.exe` using `wine`: +```nohighlight +WINEPREFIX="$HOME/wine32" wine ./Xgpro.exe 2>/dev/null +``` +If you already have a default 32 bit `wine` prefix located in `$HOME/.wine` you can use it without creating a new one: +```nohighlight +wine ./Xgpro.exe 2>/dev/null +``` + +#### How to compile: +1. Install `wine`, `wine-devel`, `libusb-1.0-0-dev:i386`, `libudev-dev:i386` packages + +2. Run `make hotplug=udev` to compile the `setupapi.dll` using `udev` library for hotplug notifications subsystem. +Running only `make` will compile the `setupapi.dll` using `libusb` library for handling hotplug events which can +be useful if `udev` subsystem is not available in your OS. + +4. Rename the compiled `setupapi.dll.so` file as `setupapi.dll` and copy this file in the Minipro/Xgpro folder + + +#### Debugging: +Running this in a terminal session: +```nohighligh +TL_DEBUG=1 wine ./Xgpro.exe or TL_DEBUG=1 wine ./Xgpro.exe 2>/dev/null +``` +will dump all usb communication to the console which can be very useful when something goes wrong. +The `2>/dev/null` will redirect the `stderr` to `/dev/null` thus cancelling the wine debugging messages which can be +very annoying sometime. diff --git a/wine/setupapi.c b/wine/setupapi.c index 77640b7..732c1d1 100644 --- a/wine/setupapi.c +++ b/wine/setupapi.c @@ -1,32 +1,49 @@ +/* + * setupapi.c + * Winelib wrapper for Minipro TL866A/CS, TL866II+, XgPro T48, T56 and T76 + * programmers. + * This library will redirect all USB related functions from Minipro or Xgpro + * software to the Linux USB subsystem using the standard LibUsb library. + * Created: May 5, 2014 + * Author: radiomanV + */ + #define __WINESRC__ #define __CYGWIN__ #define _GNU_SOURCE #include +#ifdef UDEV #include +#endif #include +#include #include #include #include #include #include -#include - #include #include #include #include +// Defines #define TL866A_VID 0x04d8 #define TL866A_PID 0xe11c -#define TL866II_VID 0xA466 -#define TL866II_PID 0x0A53 -#define T76_PID 0x1A86 -#define T76_VID 0xA466 +#define TL866II_VID 0xa466 +#define TL866II_PID 0x0a53 +#define T76_VID 0xa466 +#define T76_PID 0x1a86 +#define PIPE_TRANSFER_TIMEOUT 0x03 +#define X86_PUSH 0x68 +#define X86_RET 0xc3 +#define X86_JMP 0xeb +// Typedefs typedef struct { - HANDLE InterfaceHandle; + libusb_device_handle *handle; UCHAR PipeID; PUCHAR Buffer; ULONG BufferLength; @@ -34,34 +51,49 @@ typedef struct { LPOVERLAPPED Overlapped; } Args; +typedef struct { + struct libusb_transfer *transfer; + int timeout; +} Endpoint; + +typedef BOOL(WINAPI *pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT); +typedef HWND(WINAPI *pGetForegroundWindow)(); +typedef LRESULT(WINAPI *pSendMessageA)(HWND, UINT, WPARAM, LPARAM); +typedef BOOL(WINAPI *pRedrawWindow)(HWND, const RECT *, HRGN, UINT); + +// Notification interfaces +const GUID MINIPRO_GUID = + {0x85980D83, 0x32B9, 0x4BA1, {0x8F, 0xDF, 0x12, 0xA7, 0x11, 0xB9, 0x9C, 0xA2}}; +const GUID XGPRO_GUID1 = + {0xE7E8BA13, 0x2A81, 0x446E, {0xA1, 0x1E, 0x72, 0x39, 0x8F, 0xBD, 0xA8, 0x2F}}; +const GUID XGPRO_GUID2 = + {0x015DE341, 0x91CC, 0x8286, {0x39, 0x64, 0x1A, 0x00, 0x6B, 0xC1, 0xF0, 0x0F}}; + // Global variables -libusb_device_handle *device_handle[4]; -libusb_device **devs; int debug = 0; - -HANDLE h_thread; - -HWND hWnd; BOOL cancel; + +libusb_device **devs = NULL; +libusb_device_handle *device_handle[4]; +Endpoint endpoints[2][7]; +unsigned short device_vid; +unsigned short device_pid; +GUID m_guid; + HANDLE *usb_handle; HANDLE *winusb_handle; int *devices_count; -GUID m_guid; -unsigned short device_vid; -unsigned short device_pid; - -typedef BOOL(__stdcall *pMessageBoxA)(HWND, LPCSTR, LPCSTR, UINT); -typedef HWND(__stdcall *pGetForegroundWindow)(); -typedef LRESULT(__stdcall *pSendMessageA)(HWND, UINT, WPARAM, LPARAM); -typedef BOOL(__stdcall *pRedrawWindow)(HWND, const RECT *, HRGN, UINT); +HANDLE hotplug_thread; +HWND hWnd; pMessageBoxA message_box; pGetForegroundWindow get_foreground_window; pSendMessageA send_message; pRedrawWindow redraw_window; -pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER; +void device_changed(unsigned int); // These are functions signature extracted from Xgpro.exe and should be // compatible from V7.0 and above. @@ -70,45 +102,43 @@ const unsigned char xgpro_open_devices_pattern1[] = { 0x6A, 0x00, 0x6A, 0x03, 0x68, 0x00, 0x00, 0x00, 0xC0, 0x68}; const unsigned char xgpro_open_devices_pattern2[] = { - 0x6A, 0x00, 0x68, 0x80, 0x00, 0x00, 0x40, 0x6A, 0x03, 0x6A, - 0x00, 0x6A, 0x03, 0x68, 0x00, 0x00, 0x00, 0xC0, 0x51}; + 0x6A, 0x00, 0x68, 0x80, 0x00, 0x00, 0x40, 0x6A, 0x03, 0x6A, 0x00, + 0x6A, 0x03, 0x68, 0x00, 0x00, 0x00, 0xC0, 0x51}; // These are functions signature extracted from MiniPro.exe and should be // compatible from V6.0 and above. const unsigned char minipro_open_devices_pattern[] = { - 0x6A, 0x00, 0x68, 0x80, 0x00, 0x00, 0x00, - 0x6A, 0x03, 0x6A, 0x00, 0x6A, 0x03}; -const unsigned char usb_write_pattern[] = {0x8B, 0x94, 0x24, 0x0C, 0x10, 0x00, - 0x00, 0x8D, 0x44, 0x24, 0x00, 0x6A, - 0x00, 0x50, 0x8B, 0x84}; -const unsigned char usb_write2_pattern[] = {0x8B, 0x94, 0x24, 0x10, 0x10, 0x00, - 0x00, 0x8D, 0x44, 0x24, 0x00, 0x6A, - 0x00, 0x50, 0x8B, 0x84}; -const unsigned char usb_read_pattern[] = {0x64, 0xA1, 0x00, 0x00, 0x00, 0x00, - 0x8B, 0x4C, 0x24, 0x08, 0x8B, 0x54, - 0x24, 0x04, 0x6A, 0xFF}; -const unsigned char usb_read2_pattern[] = {0x8B, 0x4C, 0x24, 0x0C, 0x8B, 0x54, - 0x24, 0x08, 0x8D, 0x44, 0x24, 0x0C, - 0x6A, 0x00, 0x50, 0x51}; -const unsigned char brickbug_pattern[] = {0x83, 0xC4, 0x18, 0x3D, 0x13, - 0xF0, 0xC2, 0xC8, 0x75}; + 0x6A, 0x00, 0x68, 0x80, 0x00, 0x00, 0x00, 0x6A, 0x03, 0x6A, 0x00, + 0x6A, 0x03}; +const unsigned char usb_write_pattern[] = { + 0x8B, 0x94, 0x24, 0x0C, 0x10, 0x00, 0x00, 0x8D, 0x44, 0x24, 0x00, + 0x6A, 0x00, 0x50, 0x8B, 0x84}; +const unsigned char usb_write2_pattern[] = { + 0x8B, 0x94, 0x24, 0x10, 0x10, 0x00, 0x00, 0x8D, 0x44, 0x24, 0x00, + 0x6A, 0x00, 0x50, 0x8B, 0x84}; +const unsigned char usb_read_pattern[] = { + 0x64, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x4C, 0x24, 0x08, 0x8B, + 0x54, 0x24, 0x04, 0x6A, 0xFF}; +const unsigned char usb_read2_pattern[] = { + 0x8B, 0x4C, 0x24, 0x0C, 0x8B, 0x54, 0x24, 0x08, 0x8D, 0x44, 0x24, + 0x0C, 0x6A, 0x00, 0x50, 0x51}; +const unsigned char brickbug_pattern[] = { + 0x83, 0xC4, 0x18, 0x3D, 0x13, 0xF0, 0xC2, 0xC8, 0x75}; // Print given array in hex -void print_hex(unsigned char *buffer, unsigned int size) { - int i, k, r = 0; +void print_hex(const unsigned char *buffer, unsigned int size) { + unsigned int i; for (i = 0; i < size; i++) { printf("%02X ", buffer[i]); - r++; - if ((r == 16) || (i + 1 == size && r < 16)) { - if (i + 1 == size && r < 16) - printf("%*c", r * 3 - 48, ' '); - printf(" "); - - for (k = i - r + 1; k <= i; k++) { - printf("%c", (buffer[k] < 32 || buffer[k] > 127) ? '.' : buffer[k]); + if ((i + 1) % 16 == 0 || i + 1 == size) { + unsigned int start = i / 16 * 16; + if ((i + 1) % 16 != 0) { + printf("%*s", (16 - (i + 1) % 16) * 3, ""); + } + printf(" "); + for (unsigned int j = start; j <= i; j++) { + printf("%c", (buffer[j] < 32 || buffer[j] > 126) ? '.' : buffer[j]); } - - r = 0; printf("\n"); } } @@ -117,29 +147,24 @@ void print_hex(unsigned char *buffer, unsigned int size) { // USB open/close function replacement void close_devices() { - printf("Close devices.\n"); if (devs != NULL) { - for (int i = 0; i < 4; i++) { + printf("Close devices.\n"); + // Xgpro T76 doesn't support multiple devices yet. + for (int i = 0; i < (device_pid == T76_PID ? 1 : 4); i++) { if (device_handle[i] != NULL) { libusb_release_interface(device_handle[i], 0); libusb_close(device_handle[i]); device_handle[i] = NULL; } } + // close session libusb_free_device_list(devs, 1); - libusb_exit(NULL); // close session + libusb_exit(NULL); devs = NULL; } } int open_devices() { - char *debug_var = NULL; - debug_var = getenv("TL_DEBUG"); - if (debug_var && 0 == strncmp(debug_var, "1", 1)) - debug = 1; - else - debug = 0; - printf("Open devices.\n"); close_devices(); device_handle[0] = NULL; device_handle[1] = NULL; @@ -147,6 +172,14 @@ int open_devices() { device_handle[3] = NULL; devs = NULL; + // Initialize all transfers pointers and timeouts + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 7; j++) { + endpoints[i][j].transfer = NULL; + endpoints[i][j].timeout = 5000; + } + } + // initialize a new session libusb_init(NULL); // set verbosity level @@ -157,18 +190,25 @@ int open_devices() { #endif usb_handle[0] = INVALID_HANDLE_VALUE; - usb_handle[1] = INVALID_HANDLE_VALUE; - usb_handle[2] = INVALID_HANDLE_VALUE; - usb_handle[3] = INVALID_HANDLE_VALUE; + + // Xgpro T76 doesn't support multiple devices yet. + if (device_pid != T76_PID) { + usb_handle[1] = INVALID_HANDLE_VALUE; + usb_handle[2] = INVALID_HANDLE_VALUE; + usb_handle[3] = INVALID_HANDLE_VALUE; + } if (device_vid == TL866II_VID) { *devices_count = 0; winusb_handle[0] = INVALID_HANDLE_VALUE; - winusb_handle[1] = INVALID_HANDLE_VALUE; - winusb_handle[2] = INVALID_HANDLE_VALUE; - winusb_handle[3] = INVALID_HANDLE_VALUE; + if (device_pid != T76_PID) { + winusb_handle[1] = INVALID_HANDLE_VALUE; + winusb_handle[2] = INVALID_HANDLE_VALUE; + winusb_handle[3] = INVALID_HANDLE_VALUE; + } } + printf("Open devices.\n"); int devices_found = 0, ret; struct libusb_device_descriptor desc; int count = libusb_get_device_list(NULL, &devs); @@ -177,6 +217,7 @@ int open_devices() { return 0; } + char name[128]; for (int i = 0; i < count; i++) { ret = libusb_get_device_descriptor(devs[i], &desc); if (ret != LIBUSB_SUCCESS) { @@ -184,7 +225,6 @@ int open_devices() { } if (device_pid == desc.idProduct && device_vid == desc.idVendor) { - if (libusb_open(devs[i], &device_handle[devices_found]) == LIBUSB_SUCCESS && libusb_claim_interface(device_handle[devices_found], 0) == @@ -194,152 +234,329 @@ int open_devices() { winusb_handle[devices_found] = (HANDLE)devices_found; *devices_count = devices_found + 1; } + libusb_get_string_descriptor_ascii(device_handle[devices_found], 2, + (unsigned char *)name, sizeof(name)); + + // Get device name string and remove trailing spaces + if (strstr(name, "Xingong")) { + strcpy(name, "XGecu TL866II+"); + } else if (strstr(name, "MiniPro")) { + strcpy(name, "Minipro TL866A/CS"); + } + char *end = name + strlen(name) - 1; + while (end > name && isspace((unsigned char)*end)) end--; + end[1] = '\0'; + + // Get device speed string + char *speed = ""; + switch (libusb_get_device_speed(devs[i])) { + case LIBUSB_SPEED_LOW: + speed = "Low speed (1.5MBit/s)"; + break; + case LIBUSB_SPEED_FULL: + speed = "Full speed (12MBit/s)"; + break; + case LIBUSB_SPEED_HIGH: + speed = "High speed (480MBit/s)"; + break; + case LIBUSB_SPEED_SUPER: + speed = "Super speed (5000MBit/s)"; + default: + break; + } devices_found++; - if (devices_found == 4) - return 0; + printf("Found USB device %u: VID_%04X, PID_%04X; %s; %s\n", + devices_found, desc.idVendor, desc.idProduct, name, speed); + + // Xgpro T76 doesn't support multiple devices yet. + if (devices_found == ((device_pid == T76_PID) ? 1 : 4)) return 0; } } } return 0; } -/// Xgpro replacement functions. -BOOL __stdcall WinUsb_SetPipePolicy(HANDLE InterfaceHandle, UCHAR PipeID, - ULONG PolicyType, ULONG ValueLength, - PVOID Value) { - return TRUE; +// Helper function to retrieve a transfer structure from a PipeID +struct libusb_transfer *get_transfer(UCHAR PipeID) { + return endpoints[(PipeID > 80 ? 1 : 0)][(PipeID & 0x7f) - 1].transfer; } -BOOL __stdcall WinUsb_AbortPipe(HANDLE InterfaceHandle, UCHAR PipeID) { - return TRUE; +// Helper functions to set/get timeout from PipeID +int get_timeout(UCHAR PipeID) { + return endpoints[(PipeID > 80 ? 1 : 0)][(PipeID & 0x7f) - 1].timeout; } -BOOL __stdcall WinUsb_FlushPipe(HANDLE InterfaceHandle, UCHAR PipeID) { - return TRUE; +void set_timeout(UCHAR PipeID, int ep_timeout) { + endpoints[(PipeID > 80 ? 1 : 0)][(PipeID & 0x7f) - 1].timeout = ep_timeout; } -// Asynchronous transfer for WinUsb_ReadPipe/WinUsb_WritePipe. -void async_transfer(Args *args) { - libusb_bulk_transfer(device_handle[(int)args->InterfaceHandle], args->PipeID, - args->Buffer, args->BufferLength, - args->LengthTransferred, 20000); - if (debug) { - pthread_mutex_lock(&mylock); - printf("%s %lu bytes on endpoint %u\n", - (args->PipeID & 0x80) ? "Read async" : "Write async", - args->BufferLength, args->PipeID & 0x7F); - print_hex(args->Buffer, *args->LengthTransferred); - pthread_mutex_unlock(&mylock); +// libusb transfer callback +void transfer_cb(struct libusb_transfer *transfer) { + // We only set the completion flag here. + *(int *)transfer->user_data = 1; +} + +/*** Xgpro replacement functions. ***/ + +// USB transfer for WinUsb_ReadPipe/WinUsb_WritePipe. +// This function will run in a separate thread if overlapped +// transfer is specified. +void usb_transfer(Args *args) { + + int ret, completed = 0; + + // Allocate transfer + struct libusb_transfer *tr = get_transfer(args->PipeID); + tr = libusb_alloc_transfer(0); + if (!tr) { + printf("Out of memory!\n"); + free(args); + return; } - SetEvent(args->Overlapped - ->hEvent); // signal the event to release the waiting object. - free(args); // Free the malloced args. -} -// WinUsb_ReadPipe/winUsb_WritePipe LibUsb implementation. -BOOL __stdcall WinUsb_Transfer(HANDLE InterfaceHandle, UCHAR PipeID, - PUCHAR Buffer, ULONG BufferLength, - PUINT LengthTransferred, - LPOVERLAPPED Overlapped) { - if (InterfaceHandle == INVALID_HANDLE_VALUE) - return FALSE; - if (device_handle[(int)InterfaceHandle] == NULL) - return FALSE; - int ret; - if ((PipeID & 0x80) && (PipeID & 0x7F) > 1 && BufferLength < 64) - BufferLength = 64; - if (Overlapped != NULL) // If an asynchronous transfer is needed then pack - // all the arguments to an Arg structure and pass - // them to a new thread and return immediately. - { - ResetEvent(Overlapped->hEvent); - Args *args = malloc(sizeof(*args)); - args->InterfaceHandle = InterfaceHandle; - args->PipeID = PipeID; - args->Buffer = Buffer; - args->BufferLength = BufferLength; - args->LengthTransferred = LengthTransferred; - args->Overlapped = Overlapped; - CreateThread(NULL, 0, (void *)async_transfer, args, 0, NULL); - return TRUE; - } else // Just an synchronous transfer is needed; just call the - // libusb_bulk_transfer. - { - ret = libusb_bulk_transfer(device_handle[(int)InterfaceHandle], PipeID, - Buffer, BufferLength, LengthTransferred, 20000); - if (debug) { - pthread_mutex_lock(&mylock); - printf("%s %lu bytes on endpoint %u\n", - (PipeID & 0x80) ? "Read normal" : "Write normal", BufferLength, - PipeID & 0x7F); - print_hex(Buffer, *LengthTransferred); - pthread_mutex_unlock(&mylock); + // Initialize transfer structure for bulk transfer + libusb_fill_bulk_transfer(tr, args->handle, args->PipeID, args->Buffer, + args->BufferLength, + (libusb_transfer_cb_fn)transfer_cb, &completed, + get_timeout(args->PipeID)); + + // Submit the transfer + ret = libusb_submit_transfer(tr); + if (ret < 0) { + printf("\nIO error: %s\n", libusb_error_name(ret)); + free(args); + libusb_free_transfer(tr); + return; + } + + // Wait for transfer to complete + while (!completed) { + ret = libusb_handle_events_completed(NULL, &completed); + if (ret < 0) { + if (ret == LIBUSB_ERROR_INTERRUPTED) + continue; + libusb_cancel_transfer(tr); + continue; } } - return (ret == LIBUSB_SUCCESS); -} - -/// Minipro replacement functions -unsigned int uread(HANDLE hDevice, unsigned char *data, size_t size) { - if (hDevice == INVALID_HANDLE_VALUE) - return 0; - if (device_handle[(int)hDevice] == NULL) - return 0; - size_t bytes_read; - int ret = - libusb_bulk_transfer(device_handle[(int)hDevice], LIBUSB_ENDPOINT_IN | 1, - data, size, &bytes_read, 20000); - if (debug) { - printf("Read %d bytes\n", bytes_read); - print_hex(data, bytes_read); + // Check if transfer was okay + if (tr->status != 0) { + printf("\nIO Error: %s\n", libusb_error_name(tr->status)); + libusb_free_transfer(tr); + tr = NULL; + free(args); + return; } - return (ret == LIBUSB_SUCCESS ? bytes_read : 0xFFFFFFFF); + + // Get the actual transfer length + *args->LengthTransferred = tr->actual_length; + + // Free the allocated transfer structure + libusb_free_transfer(tr); + tr = NULL; + + // If debug mode is active print some debug info + if (debug) { + pthread_mutex_lock(&print_lock); + printf("%s %s %u bytes on endpoint 0x%02X\n", + (args->PipeID & 0x80) ? "Read" : "Write", + args->Overlapped ? "Async" : "Normal", *args->LengthTransferred, + args->PipeID); + if (debug == 1) { + print_hex(args->Buffer, *args->LengthTransferred); + } + pthread_mutex_unlock(&print_lock); + } + + // If Overlapped (async) transfer was completed + // signal the event to release the waiting object. + if (args->Overlapped) { + SetEvent(args->Overlapped->hEvent); + } + + // Free the malloced args. + free(args); } +/********************** ENDPOINTS USAGE ******************************** + *********************************************************************** + * TL866A/CS; wMaxPacketSize=64 bytes, 2 endpoints; USB 2.0, 12MBit/s * + * EP1_OUT=0x01, EP1_IN=0x81; All used * + *********************************************************************** + * TL866II+; wMaxPacketSize=64 bytes, 6 endpoints; USB 2.0, 12MBit/s * + * EP1_OUT=0x01, EP1_IN=0x81, EP2_OUT=0x02, EP2_IN=0x82, * + * EP3_OUT=0x03, EP1_IN=0x83; All used * + *********************************************************************** + * T48; wMaxPacketSize=512 bytes, 4 endpoints; USB 2.0, 480MBit/s * + * EP1_OUT=0x01, EP1_IN=0x81, EP2_OUT=0x02, EP2_IN=0x82; All used * + *********************************************************************** + * T56 wMaxPacketSize = 512 bytes, 2 endpoints; USB 2.0, 480MBit/s * + * EP1_OUT=0x01, EP1_IN=0x81; All used * + *********************************************************************** + * T76 wMaxPacketSize = 1024 bytes, 14 endpoints; USB 3.0, 5000MBit/s * + * EP1_OUT=0x01, EP1_IN=0x81, EP2_OUT=0x02, EP2_IN=0x82, * + * EP3_OUT=0x03, EP1_IN=0x83, EP3_OUT=0x04, EP1_IN=0x84, * + * EP3_OUT=0x05, EP1_IN=0x85, EP3_OUT=0x06, EP1_IN=0x86 * + * EP3_OUT=0x07, EP1_IN=0x87; * + * Only EP1_OUT, EP1_IN, EP2_IN, EP5_OUT are used in current firmware * + ***********************************************************************/ + +// WinUsb_ReadPipe/winUsb_WritePipe LibUsb implementation. +BOOL WINAPI WinUsb_Transfer(HANDLE InterfaceHandle, UCHAR PipeID, PUCHAR Buffer, + ULONG BufferLength, PUINT LengthTransferred, + LPOVERLAPPED Overlapped) { + // Check for usb handles + if (InterfaceHandle == INVALID_HANDLE_VALUE) return FALSE; + libusb_device_handle *handle = device_handle[(int)InterfaceHandle]; + if (handle == NULL) return FALSE; + + // Workaround for T76 endpoint 0x83 not used issue. + // The Xgecu T76 software will issue a Winusb_ReadPipe on + // endpoint 0x83 and later on will call WinUSB_AbortPipe. + // Because the T76 firmware doesn't use endpoint 0x83 this will + // get us a libusb timeout error and the Xgpro software locked + // waiting for 'Overlapped->hEvent' to be signaled. + // This will throw an error in Xgpro T76 and the programmer power + // must be cycled. + // We handle this bug here by releasing the waiting object first + // and then aborting the transfer on this endpoint. + if (device_pid == T76_PID && PipeID == 0x83) { + if (Overlapped != NULL) { + SetEvent(Overlapped->hEvent); + } + return TRUE; + } + + // Workaround for Xgpro read BufferLength issue. + // Depending on what chip is used we can get more bytes than + // declared by the Xgpro software in 'BufferLength' argument; + // so if the BufferLength < LengthTransferred we end with a libusb + // overflow error (there is more unread data). + // Perhaps the Windows driver handle this somewhat (multiple reads + // or bigger buffers). We handle this by rounding the buffer size + // in multiple of wMaxPacketSize bytes (64, 512 or 1024). + if ((PipeID > 0x80)) { + libusb_device *device = libusb_get_device(handle); + if (device == NULL) { + return FALSE; + } + // Round BufferLength to the next multiple of endpoint wMaxPacketSize + int wMaxPacketSize = libusb_get_max_packet_size(device, PipeID) - 1; + BufferLength = (BufferLength + wMaxPacketSize) & ~wMaxPacketSize; + } + + // Prepare args + Args *args = malloc(sizeof(Args)); + if (!args) { + printf("Out of memory!\n"); + return FALSE; + } + + *args = (Args){.handle = handle, + .PipeID = PipeID, + .Buffer = Buffer, + .BufferLength = BufferLength, + .LengthTransferred = LengthTransferred, + .Overlapped = Overlapped}; + + // If an overlapped (async) transfer is needed then create a + // new thread and return immediately. + if (Overlapped != NULL) { + ResetEvent(Overlapped->hEvent); + CreateThread(NULL, 0, (void *)usb_transfer, args, 0, NULL); + return TRUE; + } else { + // Just a synchronous transfer is needed; + usb_transfer(args); + } + return TRUE; +} + +// WinUsb_SetPipePolicy LibUsb implementation. +// Only setting pipe timeout is supported +BOOL WINAPI WinUsb_SetPipePolicy(HANDLE InterfaceHandle, UCHAR PipeID, + ULONG PolicyType, ULONG ValueLength, + PVOID Value) { + if (PolicyType == 0x03) { + set_timeout(PipeID, *(int *)Value); + } + return TRUE; +} + +// WinUsb_AbortPipe LibUsb implementation +BOOL WINAPI WinUsb_AbortPipe(HANDLE InterfaceHandle, UCHAR PipeID) { + struct libusb_transfer *tr = get_transfer(PipeID); + if (tr) { + libusb_cancel_transfer(tr); + } + return TRUE; +} + +// WinUsb unused but stubbbed functions. +BOOL WINAPI WinUsb_FlushPipe(HANDLE InterfaceHandle, UCHAR PipeID) { + return TRUE; +} + +BOOL WINAPI WinUsb_Initialize(HANDLE DeviceHandle, PVOID *InterfaceHandle) { + return TRUE; +} + +BOOL WINAPI WinUsb_Free(HANDLE InterfaceHandle) { return TRUE; } + +/*** Minipro replacement functions ***/ + +// USB read implementation. Use the WinUsb_Transfer above +int uread(HANDLE hDevice, unsigned char *data, unsigned int size) { + unsigned int transferred = 0; + set_timeout(LIBUSB_ENDPOINT_IN | 1, 20000); + BOOL ret = WinUsb_Transfer(hDevice, LIBUSB_ENDPOINT_IN | 1, data, size, + &transferred, NULL); + return (ret ? transferred : -1); +} + +// USB write implementation. Use the WinUsb_Transfer above BOOL uwrite(HANDLE hDevice, unsigned char *data, size_t size) { - if (hDevice == INVALID_HANDLE_VALUE) - return 0; - if (device_handle[(int)hDevice] == NULL) - return 0; - size_t bytes_writen; - int ret = - libusb_bulk_transfer(device_handle[(int)hDevice], LIBUSB_ENDPOINT_OUT | 1, - data, size, &bytes_writen, 20000); - if (debug) { - printf("Write %d bytes\n", bytes_writen); - print_hex(data, bytes_writen); - } - return (ret == LIBUSB_SUCCESS); + unsigned int transferred = 0; + set_timeout(LIBUSB_ENDPOINT_OUT | 1, 20000); + return WinUsb_Transfer(hDevice, LIBUSB_ENDPOINT_OUT | 1, data, size, + &transferred, NULL); } +// USB write to device zero BOOL usb_write(unsigned char *lpInBuffer, unsigned int nInBufferSize) { - BOOL ret = uwrite(0, lpInBuffer, nInBufferSize); - return ret; + return uwrite(0, lpInBuffer, nInBufferSize); } -unsigned int usb_read(unsigned char *lpOutBuffer, unsigned int nBytesToRead, - unsigned int nOutBufferSize) { - unsigned int ret = uread(0, lpOutBuffer, nBytesToRead); - if (ret == 0xFFFFFFFF) +// USB read from device zero +int usb_read(unsigned char *lpOutBuffer, unsigned int nBytesToRead, + unsigned int nOutBufferSize) { + int ret = uread(0, lpOutBuffer, nBytesToRead); + if (ret == -1) message_box(get_foreground_window(), "Read error!", "TL866", MB_ICONWARNING); return ret; } +// USB write to specified device BOOL usb_write2(HANDLE hDevice, unsigned char *lpInBuffer, unsigned int nInBufferSize) { - BOOL ret = uwrite(hDevice, lpInBuffer, nInBufferSize); - return ret; + return uwrite(hDevice, lpInBuffer, nInBufferSize); } -unsigned int usb_read2(HANDLE hDevice, unsigned char *lpOutBuffer, - unsigned int nBytesToRead, unsigned int nOutBufferSize) { - unsigned int ret = uread(hDevice, lpOutBuffer, nBytesToRead); - return ret; +// USB read from specified device +int usb_read2(HANDLE hDevice, unsigned char *lpOutBuffer, + unsigned int nBytesToRead, unsigned int nOutBufferSize) { + return uread(hDevice, lpOutBuffer, nBytesToRead); } -// Return the device count +// If make hotplug=udev is invoked then libudev is used for monitoring, +// otherwise libusb hotplug events monitoring is used. +#ifdef UDEV + +/*** Udev functions ***/ + +// Return the device count using Udev library API int get_device_count() { struct udev *udev = udev_new(); if (!udev) { @@ -373,37 +590,38 @@ int get_device_count() { return count; } +// Udev hotplug USB monitoring thread void notifier_function() { struct udev *udev; struct udev_monitor *mon; struct udev_device *dev; - DEV_BROADCAST_DEVICEINTERFACE_W DevBi; - DevBi.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_W); - DevBi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; - DevBi.dbcc_classguid = m_guid; - udev = udev_new(); if (!udev) { printf("Can't create udev\n"); return; } + // Get a new udev monitor from the netlink mon = udev_monitor_new_from_netlink(udev, "udev"); if (!mon) { printf("NetLink not available!\n"); return; } + + // Get the device count int count = get_device_count(); if (count == -1) { printf("udev error.\n"); return; } + printf("Using Udev hotplug events.\n\n"); udev_monitor_filter_add_match_subsystem_devtype(mon, "usb", NULL); udev_monitor_enable_receiving(mon); - int fd = udev_monitor_get_fd(mon); + int udev_mon_fd = udev_monitor_get_fd(mon); + // Enter the monitoring loop cancel = FALSE; while (!cancel) { fd_set fds; @@ -411,12 +629,12 @@ void notifier_function() { int ret; FD_ZERO(&fds); - FD_SET(fd, &fds); + FD_SET(udev_mon_fd, &fds); tv.tv_sec = 0; tv.tv_usec = 0; - ret = select(fd + 1, &fds, NULL, NULL, &tv); - if (ret > 0 && FD_ISSET(fd, &fds)) { + ret = select(udev_mon_fd + 1, &fds, NULL, NULL, &tv); + if (ret > 0 && FD_ISSET(udev_mon_fd, &fds)) { dev = udev_monitor_receive_device(mon); if (dev && !strcasecmp(udev_device_get_devtype(dev), "usb_device")) { int count_new; @@ -425,50 +643,113 @@ void notifier_function() { if (count != count_new) { count = count_new; // printf("device added.\n"); - close_devices(); - usleep(100000); - send_message(hWnd, WM_DEVICECHANGE, DBT_DEVICEARRIVAL, - (LPARAM)&DevBi); - usleep(100000); - redraw_window(hWnd, NULL, NULL, RDW_INVALIDATE); + device_changed(DBT_DEVICEARRIVAL); } - } else if (!strcasecmp(udev_device_get_action(dev), "remove")) { count_new = get_device_count(); if (count != count_new) { count = count_new; // printf("device removed.\n"); - close_devices(); - usleep(100000); - send_message(hWnd, WM_DEVICECHANGE, DBT_DEVICEARRIVAL, - (LPARAM)&DevBi); - usleep(100000); - redraw_window(hWnd, NULL, NULL, RDW_INVALIDATE); + device_changed(DBT_DEVICEREMOVECOMPLETE); } } udev_device_unref(dev); } } - usleep(10000); + usleep(50000); } udev_monitor_unref(mon); } -// RegisterDeviceNotifications WIN API replacement -HANDLE __stdcall RegisterDeviceNotifications(HANDLE hRecipient, - LPVOID NotificationFilter, - DWORD Flags) { - printf("RegisterDeviceNotifications hWnd=%X4\n", (unsigned int)hRecipient); - hWnd = hRecipient; - h_thread = CreateThread(NULL, 0, (void *)notifier_function, NULL, 0, NULL); - if (!h_thread) - printf("Thread notifier failed.\n"); +// Use libusb hotplug events. +#else +// LibUsb hotplug callback function +int hotplug_cb(struct libusb_context *ctx, struct libusb_device *dev, + libusb_hotplug_event event, void *user_data) { + + // Notify the event loop thread to rescan the devices. + // If we call the device_changed function from here the code will crash. + // See https://libusb.sourceforge.io/api-1.0/libusb_hotplug.html + *(int *)user_data = event; return 0; } -/// Patcher functions -BOOL patch_function(char *library, char *func, void *funcaddress) { +// LibUsb hotplug USB monitoring thread +void notifier_function() { + printf("Using LibUsb hotplug events.\n\n"); + int changed = 0; + libusb_hotplug_callback_handle callback_handle; + + // Register the hotplug callback for the desired USB_VID/PID + libusb_init(NULL); + int rc = libusb_hotplug_register_callback( + NULL, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, + device_vid, device_pid, LIBUSB_HOTPLUG_MATCH_ANY, + (libusb_hotplug_callback_fn)hotplug_cb, &changed, &callback_handle); + + // Check if the registration was okay + if (LIBUSB_SUCCESS != rc) { + printf("LibUsb hotplug callback error.\n"); + return; + } + + // Enter the monitoring thread and handle hotplug events. + while (!cancel) { + libusb_handle_events_completed(NULL, NULL); + usleep(50000); + if (changed) { + changed = 0; + device_changed(changed == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED + ? DBT_DEVICEARRIVAL + : DBT_DEVICEREMOVECOMPLETE); + } + } + + // Deregister hotplug callback and exit + libusb_hotplug_deregister_callback(NULL, callback_handle); + libusb_exit(NULL); +} +#endif + +// Notifier function. This will force the software to rescan devices. +void device_changed(unsigned int event) { + + // Initialize the device broadcast interface + DEV_BROADCAST_DEVICEINTERFACE_W DevBi; + DevBi.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_W); + DevBi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + DevBi.dbcc_classguid = m_guid; + + // Close all devices + close_devices(); + usleep(100000); + + // Broadcast a device change message + send_message(hWnd, WM_DEVICECHANGE, event, (LPARAM)&DevBi); + usleep(100000); + + // Force software to refresh the GUI + redraw_window(hWnd, NULL, NULL, RDW_INVALIDATE); +} + +// RegisterDeviceNotifications WINAPI replacement +HANDLE WINAPI RegisterDeviceNotifications(HANDLE hRecipient, + LPVOID NotificationFilter, + DWORD Flags) { + printf("RegisterDeviceNotifications hWnd = %p\n", hRecipient); + hWnd = hRecipient; + hotplug_thread = + CreateThread(NULL, 0, (void *)notifier_function, NULL, 0, NULL); + if (!hotplug_thread) printf("Failed to create the USB monitoring thread!\n"); + return 0; +} + +/*** Patcher functions ***/ + +// Dll redirect patch function +BOOL patch_function(char *library, char *func_name, void *custom_func) { DWORD dwOldProtection; DWORD func_addr = 0; @@ -483,7 +764,7 @@ BOOL patch_function(char *library, char *func, void *funcaddress) { [IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress); - // Search for library in the IAT + // Search for library in the import directory while (ImpDesc->Characteristics && ImpDesc->Name) { if (strcasecmp(BaseAddress + ImpDesc->Name, library) == 0) { break; // Found it! @@ -491,54 +772,59 @@ BOOL patch_function(char *library, char *func, void *funcaddress) { ImpDesc++; } - // check if the library was found in the IAT + // check if the library was found in the import directory if (!ImpDesc->Characteristics) { - printf("%s was not found in the IAT.\n", library); - return FALSE; // nope, exit with error. + printf("Library '%s' was not found in the import directory.\n", library); + return FALSE; } - // Get the address of function in library + // If the desired library was found we can get the function address DWORD_PTR ProcAddress = - (DWORD_PTR)GetProcAddress(GetModuleHandleA(library), func); + (DWORD_PTR)GetProcAddress(GetModuleHandleA(library), func_name); - // Find the address in the thunk table + // Check if the desired function address was found + if (!ProcAddress) { + printf("Function '%s' was not found in '%s' library.\n", func_name, + library); + return FALSE; + } + + // We have the address, let's search it in the thunk table PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)(BaseAddress + ImpDesc->FirstThunk); while (thunk->u1.Function) { if ((DWORD_PTR)thunk->u1.Function == ProcAddress) { - // if found, patch it to point to our custom function + // if an entry is found, patch it to point to our custom function MEMORY_BASIC_INFORMATION info; VirtualQuery(&thunk->u1.Function, &info, sizeof(MEMORY_BASIC_INFORMATION)); VirtualProtect(info.BaseAddress, info.RegionSize, PAGE_READWRITE, &dwOldProtection); func_addr = thunk->u1.Function; - thunk->u1.Function = (DWORD_PTR)funcaddress; + thunk->u1.Function = (DWORD_PTR)custom_func; VirtualProtect(info.BaseAddress, info.RegionSize, info.Protect, &dwOldProtection); } - thunk++; } // check if the patch was ok. if (!func_addr) { - printf("%s was not found in %s.\n", func, library); - return FALSE; // nope, exit with error. + printf("Function '%s' was not found in the IAT thunk table.\n", func_name); + return FALSE; } - return TRUE; } -// Inline helper patch function +// Inline helper patch function. Warning, this is x86 ASM code static inline void patch(void *src, void *dest) { // push xxxx, ret; an absolute Jump replacement. - *(BYTE *)src = 0x68; + *(BYTE *)src = X86_PUSH; *((DWORD *)((BYTE *)src + 1)) = (DWORD)dest; - *((BYTE *)src + 5) = 0xc3; + *((BYTE *)src + 5) = X86_RET; } -// Xgpro patcher function. Called from DllMain. Return TRUE if patch was ok and +// Xgpro patcher function. Called from DllMain. Returns TRUE if patch was ok and // continue with program loading or FALSE to exit with error. BOOL patch_xgpro() { // Get the BaseAddress, NT Header and Image Import Descriptor @@ -547,26 +833,29 @@ BOOL patch_xgpro() { (PIMAGE_NT_HEADERS)((PBYTE)BaseAddress + ((PIMAGE_DOS_HEADER)BaseAddress)->e_lfanew); - // Search for version - BOOL t76 = FALSE; - unsigned char *version = - memmem(BaseAddress, NtHeader->OptionalHeader.SizeOfImage, "Xgpro v", 7); - if (!version) { - version = memmem(BaseAddress, NtHeader->OptionalHeader.SizeOfImage, - "Xgpro T76 v", 11); - if (!version) - return FALSE; - t76 = TRUE; - } + // Search for version and set the Xgpro GUID and VID/PID + unsigned char *version; + if ((version = memmem(BaseAddress, NtHeader->OptionalHeader.SizeOfImage, + "Xgpro v", 7))) { + // TL866II+, T48, T56 VID/PID and interface GUID + device_vid = TL866II_VID; + device_pid = TL866II_PID; + memcpy(&m_guid, &XGPRO_GUID1, sizeof(GUID)); + } else if ((version = + memmem(BaseAddress, NtHeader->OptionalHeader.SizeOfImage, + "Xgpro T76 v", 11))) { + + // T76 VID/PID and interface GUID + device_vid = T76_VID; + device_pid = T76_PID; + memcpy(&m_guid, &XGPRO_GUID2, sizeof(GUID)); + } else { + return FALSE; + } printf("Found %s\n", version); - // Set some function pointers - HMODULE hmodule = LoadLibraryA("user32.dll"); - send_message = (pSendMessageA)GetProcAddress(hmodule, "SendMessageA"); - redraw_window = (pRedrawWindow)GetProcAddress(hmodule, "RedrawWindow"); - - // Patch the Linux incompatible functions functions + // Patch the Linux incompatible functions if (!patch_function("user32.dll", "RegisterDeviceNotificationA", &RegisterDeviceNotifications)) return FALSE; @@ -581,6 +870,11 @@ BOOL patch_xgpro() { if (!patch_function("winusb.dll", "WinUsb_ReadPipe", &WinUsb_Transfer)) return FALSE; + if (!patch_function("winusb.dll", "WinUsb_Initialize", &WinUsb_Initialize)) + return FALSE; + + if (!patch_function("winusb.dll", "WinUsb_Free", &WinUsb_Free)) return FALSE; + // Searching for functions signature in code section. void *p_opendevices = NULL; void *p_closedevices = NULL; @@ -588,18 +882,40 @@ BOOL patch_xgpro() { void *p_usbhandle = NULL; void *p_devicescount = NULL; - // Search for open device function pattern 1 (xgpro < V12.7x) + // Search for open_device function pattern 1 (xgpro < V12.7x) void *p_od1 = memmem(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, &xgpro_open_devices_pattern1, sizeof(xgpro_open_devices_pattern1)); - // Search for open device function pattern 2 (xgpro > V12.7x) + // Search for open_device function pattern 2 (xgpro > V12.7x) void *p_od2 = memmem(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, &xgpro_open_devices_pattern2, sizeof(xgpro_open_devices_pattern2)); + // If we obtained the most important function address (open_devices) then, + // we can also calculate the other necessary addresses. + // Basically we need two function pointers (open_devices and close_devices) + // which are invoked by Minipro/Xgpro when the program is started/closed + // or a device is attached or dettached. + // + // We also need three data pointers: + // 1. usb_handle which is used in both Minipro and Xgpro as a handle to a + // device obtained by calling the CreateFile API. This is actually an array of + // four pointers which in windows holds a handle to the newly opened device or + // an invalid handle value. As we redirect the open/close usb functions we + // initialize each item with an index (0 to 3) or the INVALID_HANDLE_VALUE + // (0xffffffff) if the coresponding device is not found. + // + // 2. winusb_handle used only in Xgpro because Xgpro uses the WinUsb library + // for USB communications. Like the usb_handle this is actually an array of + // four pointers which are initialized with an index (0 to 3) or the invalid + // handle value in our custom open_devices function. + // + // 3. devices_count which is used only by Xgpro to know how many devices are + // available. The Minipro and Xgpro can handle up to four devices while the + // Xgpro_T76 software can handle only one programmer at this time. if (p_od1) { p_opendevices = p_od1 - 0x1D; p_closedevices = (void *)(*(int *)((unsigned char *)p_opendevices + 5)) + @@ -621,65 +937,57 @@ BOOL patch_xgpro() { return FALSE; } else { printf("Function signatures not found! Unsupported Xgpro version.\n"); - return FALSE; // nope, exit with error. + return FALSE; } // Print debug info. - printf("Base Address = 0x%08lX\n", (DWORD)BaseAddress); - printf("Code section = 0x%08lX,0x%08lX\n", - (DWORD)BaseAddress + NtHeader->OptionalHeader.BaseOfCode, + printf("Base Address = %p\n", BaseAddress); + printf("Code section = %p, 0x%lx\n", + BaseAddress + NtHeader->OptionalHeader.BaseOfCode, (DWORD)NtHeader->OptionalHeader.SizeOfCode); - printf("Open Devices found at 0x%08lX\n", (DWORD)p_opendevices); - printf("Close Devices found at 0x%08lX\n", (DWORD)p_closedevices); - printf("Usb Handle found at 0x%08lX\n", (DWORD)p_usbhandle); - printf("WinUsb Handle found at 0x%08lX\n", (DWORD)p_winusbhandle); - printf("Devices count found at 0x%08lX\n", (DWORD)p_devicescount); + printf("Open Devices found at %p\n", p_opendevices); + printf("Close Devices found at %p\n", p_closedevices); + printf("Usb Handle found at %p\n", p_usbhandle); + printf("WinUsb Handle found at %p\n", p_winusbhandle); + printf("Devices count found at %p\n", p_devicescount); // Patch all low level functions in Xgpro.exe to point to our custom // functions. DWORD dwOldProtection; - // Initialize the usb handle address. + // Initialize the usb_handle, winusb_handle and devices_count pointers + // These variables are used by Xgpro to handle all opened devices usb_handle = p_usbhandle; winusb_handle = p_winusbhandle; devices_count = p_devicescount; + + // Now this is the actual code patch. So we need to patch the code + // to redirect the open_devices/close_devices functions to our custom + // functions. The patch is done by inserting an absolute jump at the + // desired adress. To do this we need first to change the READ_ONLY + // attribute of the code section, patch the desired address and then + // restore the old READ_ONLY attribute. + // So, we have a self modifying code here. + + // Unprotect the code memory section (make it writable) VirtualProtect(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, PAGE_READWRITE, - &dwOldProtection); // unprotect the code memory section + &dwOldProtection); - // patch Open_Devices function + // patch open_devices function to point to our implementation patch(p_opendevices, &open_devices); - // patch close_devices function + // patch close_devices function to point to our implementation patch(p_closedevices, &close_devices); + // restore the old READ_ONLY protection VirtualProtect(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, dwOldProtection, - &dwOldProtection); // restore the old protection - - // Set the Xgpro GUID and VID/PID - GUID guid; - if (t76) { - device_vid = T76_VID; - device_pid = T76_PID; - guid = (GUID){0x015DE341, - 0x91CC, - 0x8286, - {0x39, 0x64, 0x1A, 0x00, 0x6B, 0xC1, 0xF0, 0x0F}}; - } else { - device_vid = TL866II_VID; - device_pid = TL866II_PID; - guid = (GUID){0xE7E8BA13, - 0x2A81, - 0x446E, - {0xA1, 0x1E, 0x72, 0x39, 0x8F, 0xBD, 0xA8, 0x2F}}; - } - - memcpy(&m_guid, &guid, sizeof(GUID)); + &dwOldProtection); return TRUE; } -// Minipro patcher function. Called from DllMain. Return TRUE if patch was ok +// Minipro patcher function. Called from DllMain. Returns TRUE if patch was ok // and continue with program loading or FALSE to exit with error. BOOL patch_minipro() { // Get the BaseAddress, NT Header and Image Import Descriptor @@ -690,18 +998,9 @@ BOOL patch_minipro() { unsigned char *version = memmem(BaseAddress, NtHeader->OptionalHeader.SizeOfImage, "MiniPro v", 9); - if (!version) - return FALSE; + if (!version) return FALSE; printf("Found %s\n", version); - // Set some function pointers - HMODULE hmodule = LoadLibraryA("user32.dll"); - message_box = (pMessageBoxA)GetProcAddress(hmodule, "MessageBoxA"); - get_foreground_window = - (pGetForegroundWindow)GetProcAddress(hmodule, "GetForegroundWindow"); - send_message = (pSendMessageA)GetProcAddress(hmodule, "SendMessageA"); - redraw_window = (pRedrawWindow)GetProcAddress(hmodule, "RedrawWindow"); - // Patch the Linux incompatible functions functions if (!patch_function("user32.dll", "RegisterDeviceNotificationA", &RegisterDeviceNotifications)) @@ -736,152 +1035,194 @@ BOOL patch_minipro() { if (!p_opendevices || !p_usbwrite || !p_usbwrite2 || !p_usbread || !p_usbread2) { printf("Function signatures not found! Unsupported MiniPro version.\n"); - return FALSE; // nope, exit with error. + return FALSE; } - // search for brick bug + // Search for brick bug. This is not an actually bug but a special code + // used to brick pirated TL866A/CS devices. The problem is that they + // used a wrong detection which can also brick genuine TL866A/CS devices + // See this for more info: https://pastebin.com/i5iLGPs1 unsigned char *p_brickbug = memmem(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, &brickbug_pattern, sizeof(brickbug_pattern)); - // Print debug info. - printf("Base Address = 0x%08lX\n", (DWORD)BaseAddress); - printf("Code section = 0x%08lX,0x%08lX\n", - (DWORD)BaseAddress + NtHeader->OptionalHeader.BaseOfCode, + // Print some debug info. + printf("Base Address = %p\n", BaseAddress); + printf("Code section = %p, 0x%lx\n", + BaseAddress + NtHeader->OptionalHeader.BaseOfCode, (DWORD)NtHeader->OptionalHeader.SizeOfCode); - printf("Open Devices found at 0x%08lX\n", (DWORD)p_opendevices); - printf("Close Devices found at 0x%08lX\n", (DWORD)p_closedevices); - printf("Usb Write found at 0x%08lX\n", (DWORD)p_usbwrite); - printf("Usb Read found at 0x%08lX\n", (DWORD)p_usbread); - printf("Usb Write2 found at 0x%08lX\n", (DWORD)p_usbwrite2); - printf("Usb Read2 found at 0x%08lX\n", (DWORD)p_usbread2); - printf("Usb Handle found at 0x%08lX\n", (DWORD)p_usbhandle); - if (p_brickbug) - printf("Patched brick bug at 0x%08lX\n", (DWORD)p_brickbug + 0x08); + printf("Open Devices found at %p\n", p_opendevices); + printf("Close Devices found at %p\n", p_closedevices); + printf("Usb Write found at %p\n", p_usbwrite); + printf("Usb Read found at %p\n", p_usbread); + printf("Usb Write2 found at %p\n", p_usbwrite2); + printf("Usb Read2 found at %p\n", p_usbread2); + printf("Usb Handle found at %p\n", p_usbhandle); + if (p_brickbug) printf("Patched brick bug at %p\n", p_brickbug + 0x08); // Patch all low level functions in MiniPro.exe to point to our custom // functions. - DWORD dwOldProtection; - // Initialize the usb handle address. + // Initialize the usb_handle pointer. + // Compared to Xgpro software we have only a data pointer here. + // We initialize each element with a simple index (0 to 3) + // or the INVALID_HANDLE_VALUE. usb_handle = p_usbhandle; + + // Now this is the actual code patch. So we need to patch the code + // to redirect all the usb realated functions as well as open/close devices + // functions to point to our custom implementation. + // functions. The patch is done by inserting an absolute jump at the + // desired adress. To do this we need first to change the READ_ONLY + // attribute of the code section, patch the desired address and then + // restore the old READ_ONLY attribute. + // So, we have a self modifying code here. + + // Unprotect the code memory section (make it writable) + DWORD dwOldProtection; VirtualProtect(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, PAGE_READWRITE, - &dwOldProtection); // unprotect the code memory section + &dwOldProtection); - // patch Open_Devices function + // patch open_devices function patch(p_opendevices, &open_devices); // patch close_devices function patch(p_closedevices, &close_devices); - // patch USB_Write function + // patch usb_write function patch(p_usbwrite, &usb_write); - // patch USB_Read function + // patch usb_read function patch(p_usbread, &usb_read); - // patch USB_Write2 function + // patch usb_write2 function patch(p_usbwrite2, &usb_write2); - // patch USB_Read2 function + // patch usb_read2 function patch(p_usbread2, &usb_read2); // patch the brick bug - if (p_brickbug) - *(p_brickbug + 0x08) = 0xEB; + if (p_brickbug) *(p_brickbug + 0x08) = X86_JMP; + // Restore the old READ_ONLY protection VirtualProtect(BaseAddress + NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.SizeOfCode, dwOldProtection, - &dwOldProtection); // restore the old protection + &dwOldProtection); // Set the Minipro GUID - const GUID guid = {0x85980D83, - 0x32B9, - 0x4BA1, - {0x8F, 0xDF, 0x12, 0xA7, 0x11, 0xB9, 0x9C, 0xA2}}; - memcpy(&m_guid, &guid, sizeof(GUID)); + memcpy(&m_guid, &MINIPRO_GUID, sizeof(GUID)); - // set vid/pid + // Set the vid/pid device_vid = TL866A_VID; device_pid = TL866A_PID; return TRUE; } -/// DLLMAIN +/*** DllMain ***/ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { - switch (fdwReason) { - case DLL_PROCESS_ATTACH: - DisableThreadLibraryCalls(hinstDLL); - printf("Dll Loaded.\n"); - if (patch_xgpro() || patch_minipro()) - return TRUE; - printf("Dll Unloaded.\n"); - return FALSE; - break; - case DLL_PROCESS_DETACH: - cancel = TRUE; - WaitForSingleObject(h_thread, 5000); - printf("Dll Unloaded.\n"); - break; + // Get the module name (should be setupapi.dll) + WCHAR buffer[MAX_PATH]; + GetModuleFileNameW(hinstDLL, buffer, sizeof(buffer)); + char *module_path = wine_get_unix_file_name(buffer); + if(!module_path){ + module_path = "setupapi.dll"; } + switch (fdwReason) { + // Dll loaded and atached to process + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinstDLL); + printf("%s loaded.\n", module_path); + + // Set the debug mode if TL_DEBUG environment variable is set + char *debug_var = NULL; + debug_var = getenv("TL_DEBUG"); + if (debug_var && !strncmp(debug_var, "1", 1)) + debug = 1; + else if (debug_var && !strncmp(debug_var, "2", 1)) + debug = 2; + else + debug = 0; + + // Set some used function pointers from the user32.dll library + HMODULE hmodule = LoadLibraryA("user32.dll"); + message_box = (pMessageBoxA)GetProcAddress(hmodule, "MessageBoxA"); + get_foreground_window = + (pGetForegroundWindow)GetProcAddress(hmodule, "GetForegroundWindow"); + send_message = (pSendMessageA)GetProcAddress(hmodule, "SendMessageA"); + redraw_window = (pRedrawWindow)GetProcAddress(hmodule, "RedrawWindow"); + + // Try to patch the software + if (patch_xgpro() || patch_minipro()) return TRUE; + printf("%s unloaded.\n", wine_get_unix_file_name(buffer)); + return FALSE; + break; + + // We are detached from a process, terminate the USB hotplug monitoring + // thread, close all opened devices and exit. + case DLL_PROCESS_DETACH: + cancel = TRUE; + WaitForSingleObject(hotplug_thread, 5000); + close_devices(); + printf("%s unloaded.\n", module_path); + break; + } return TRUE; } -/// SetupApi redirected functions needed for the new wine 4.11+ winex11.drv -/// calls -typedef BOOL(__stdcall *pSetupDiGetDeviceInterfaceDetailW)(HANDLE, HANDLE, - HANDLE, DWORD, - PDWORD, LPVOID); +/*************************************************************************/ +/********************** Exported functions section ***********************/ +/*************************************************************************/ -typedef BOOL(__stdcall *pSetupDiGetDeviceRegistryPropertyW)(HANDLE, LPVOID, - DWORD, PDWORD, - PBYTE, DWORD, - PDWORD); -typedef BOOL(__stdcall *pSetupDiCallClassInstaller)(LPVOID, HANDLE, LPVOID); -typedef HANDLE(__stdcall *pSetupDiGetClassDevsA)(const GUID *, PCSTR, HWND, +// SetupApi redirected functions needed for the new wine >4.11 winex11.drv +// calls. These functions must be specified in setupapi.spec file. +typedef BOOL(WINAPI *pSetupDiGetDeviceInterfaceDetailW)(HANDLE, HANDLE, HANDLE, + DWORD, PDWORD, LPVOID); + +typedef BOOL(WINAPI *pSetupDiGetDeviceRegistryPropertyW)(HANDLE, LPVOID, DWORD, + PDWORD, PBYTE, DWORD, + PDWORD); +typedef BOOL(WINAPI *pSetupDiCallClassInstaller)(LPVOID, HANDLE, LPVOID); +typedef HANDLE(WINAPI *pSetupDiGetClassDevsA)(const GUID *, PCSTR, HWND, DWORD); +typedef HANDLE(WINAPI *pSetupDiGetClassDevsW)(const GUID *, PCWSTR, HWND, + DWORD); +typedef BOOL(WINAPI *pSetupDiEnumDeviceInfo)(HANDLE, DWORD, LPVOID); +typedef BOOL(WINAPI *pSetupDiEnumDeviceInterfaces)(HANDLE, LPVOID, const GUID *, + DWORD, HANDLE); +typedef BOOL(WINAPI *pSetupDiGetDevicePropertyW)(HANDLE, LPVOID, const LPVOID *, + LPVOID *, PBYTE, DWORD, PDWORD, DWORD); -typedef HANDLE(__stdcall *pSetupDiGetClassDevsW)(const GUID *, PCWSTR, HWND, - DWORD); -typedef BOOL(__stdcall *pSetupDiEnumDeviceInfo)(HANDLE, DWORD, LPVOID); -typedef BOOL(__stdcall *pSetupDiEnumDeviceInterfaces)(HANDLE, LPVOID, - const GUID *, DWORD, - HANDLE); -typedef BOOL(__stdcall *pSetupDiGetDevicePropertyW)(HANDLE, LPVOID, - const LPVOID *, LPVOID *, - PBYTE, DWORD, PDWORD, - DWORD); typedef BOOL (*pSetupDiDestroyDeviceInfoList)(HANDLE DeviceInfoSet); -typedef HANDLE(__stdcall *pSetupDiCreateDeviceInfoList)(const GUID *ClassGuid, - HWND hwndParent); -typedef BOOL(__stdcall *pSetupDiSetDevicePropertyW)(HANDLE, LPVOID, - const LPVOID *, LPVOID, - const PBYTE, DWORD, DWORD); -typedef BOOL(__stdcall *pSetupDiCreateDeviceInfoW)(HANDLE, PCWSTR, const GUID *, - PCWSTR, HWND, DWORD, LPVOID); -typedef BOOL(__stdcall *pSetupDiOpenDeviceInfoW)(HANDLE, PCWSTR, HWND, DWORD, - LPVOID); -typedef BOOL(__stdcall *pSetupDiRegisterDeviceInfo)(HANDLE, LPVOID, DWORD, - LPVOID, PVOID, LPVOID); -typedef BOOL(__stdcall *pSetupDiSetDeviceRegistryPropertyW)(HANDLE, LPVOID, - DWORD, const BYTE *, - DWORD); -typedef HKEY(__stdcall *pSetupDiCreateDevRegKeyW)(HANDLE, LPVOID, DWORD, DWORD, - DWORD, HANDLE, PCWSTR); -typedef BOOL(__stdcall *pSetupDiRemoveDevice)(HANDLE, LPVOID); +typedef HANDLE(WINAPI *pSetupDiCreateDeviceInfoList)(const GUID *ClassGuid, + HWND hwndParent); +typedef BOOL(WINAPI *pSetupDiSetDevicePropertyW)(HANDLE, LPVOID, const LPVOID *, + LPVOID, const PBYTE, DWORD, + DWORD); +typedef BOOL(WINAPI *pSetupDiCreateDeviceInfoW)(HANDLE, PCWSTR, const GUID *, + PCWSTR, HWND, DWORD, LPVOID); +typedef BOOL(WINAPI *pSetupDiOpenDeviceInfoW)(HANDLE, PCWSTR, HWND, DWORD, + LPVOID); +typedef BOOL(WINAPI *pSetupDiRegisterDeviceInfo)(HANDLE, LPVOID, DWORD, LPVOID, + PVOID, LPVOID); +typedef BOOL(WINAPI *pSetupDiSetDeviceRegistryPropertyW)(HANDLE, LPVOID, DWORD, + const BYTE *, DWORD); +typedef HKEY(WINAPI *pSetupDiCreateDevRegKeyW)(HANDLE, LPVOID, DWORD, DWORD, + DWORD, HANDLE, PCWSTR); +typedef BOOL(WINAPI *pSetupDiRemoveDevice)(HANDLE, LPVOID); +// Helper function to obtain the function address to original setupapi.dll FARPROC get_proc_address(LPCSTR lpProcName) { char sysdir[MAX_PATH]; GetSystemDirectoryA(sysdir, MAX_PATH); strcat(sysdir, "\\setupapi.dll"); HMODULE hmodule = LoadLibraryA(sysdir); FARPROC address = GetProcAddress(hmodule, lpProcName); - // printf("%s : 0x%08lX\n", lpProcName, (uint32_t)address); + //printf("%s : %p\n", lpProcName, address); return address; } -__stdcall BOOL SetupDiGetDeviceInterfaceDetailW( +WINAPI BOOL SetupDiGetDeviceInterfaceDetailW( HANDLE DeviceInfoSet, HANDLE DeviceInterfaceData, HANDLE DeviceInterfaceDetailData, DWORD DeviceInterfaceDetailDataSize, PDWORD RequiredSize, LPVOID DeviceInfoData) { @@ -892,7 +1233,7 @@ __stdcall BOOL SetupDiGetDeviceInterfaceDetailW( DeviceInterfaceDetailDataSize, RequiredSize, DeviceInfoData); } -_stdcall BOOL SetupDiGetDeviceRegistryPropertyW( +WINAPI BOOL SetupDiGetDeviceRegistryPropertyW( HANDLE DeviceInfoSet, LPVOID DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize) { @@ -903,40 +1244,40 @@ _stdcall BOOL SetupDiGetDeviceRegistryPropertyW( PropertyBuffer, PropertyBufferSize, RequiredSize); } -__stdcall BOOL SetupDiCallClassInstaller(LPVOID InstallFunction, - HANDLE DeviceInfoSet, - LPVOID DeviceInfoDat) { +WINAPI BOOL SetupDiCallClassInstaller(LPVOID InstallFunction, + HANDLE DeviceInfoSet, + LPVOID DeviceInfoDat) { pSetupDiCallClassInstaller pfunc = (pSetupDiCallClassInstaller)get_proc_address("SetupDiCallClassInstaller"); return pfunc(InstallFunction, DeviceInfoSet, DeviceInfoDat); } -__stdcall HANDLE SetupDiGetClassDevsA(const GUID *ClassGuid, PCSTR Enumerator, - HWND hwndParent, DWORD Flags) { +WINAPI HANDLE SetupDiGetClassDevsA(const GUID *ClassGuid, PCSTR Enumerator, + HWND hwndParent, DWORD Flags) { pSetupDiGetClassDevsA pfunc = (pSetupDiGetClassDevsA)get_proc_address("SetupDiGetClassDevsA"); return pfunc(ClassGuid, Enumerator, hwndParent, Flags); } -__stdcall HANDLE SetupDiGetClassDevsW(const GUID *ClassGuid, PCWSTR Enumerator, - HWND hwndParent, DWORD Flags) { +WINAPI HANDLE SetupDiGetClassDevsW(const GUID *ClassGuid, PCWSTR Enumerator, + HWND hwndParent, DWORD Flags) { pSetupDiGetClassDevsW pfunc = (pSetupDiGetClassDevsW)get_proc_address("SetupDiGetClassDevsW"); return pfunc(ClassGuid, Enumerator, hwndParent, Flags); } -__stdcall BOOL SetupDiEnumDeviceInfo(HANDLE DeviceInfoSet, DWORD MemberIndex, - LPVOID DeviceInfoData) { +WINAPI BOOL SetupDiEnumDeviceInfo(HANDLE DeviceInfoSet, DWORD MemberIndex, + LPVOID DeviceInfoData) { pSetupDiEnumDeviceInfo pfunc = (pSetupDiEnumDeviceInfo)get_proc_address("SetupDiEnumDeviceInfo"); return pfunc(DeviceInfoSet, MemberIndex, DeviceInfoData); } -_stdcall BOOL SetupDiEnumDeviceInterfaces(HANDLE DeviceInfoSet, - LPVOID DeviceInfoData, - const GUID *InterfaceClassGuid, - DWORD MemberIndex, - HANDLE DeviceInterfaceData) { +WINAPI BOOL SetupDiEnumDeviceInterfaces(HANDLE DeviceInfoSet, + LPVOID DeviceInfoData, + const GUID *InterfaceClassGuid, + DWORD MemberIndex, + HANDLE DeviceInterfaceData) { pSetupDiEnumDeviceInterfaces pfunc = (pSetupDiEnumDeviceInterfaces)get_proc_address( "SetupDiEnumDeviceInterfaces"); @@ -944,11 +1285,10 @@ _stdcall BOOL SetupDiEnumDeviceInterfaces(HANDLE DeviceInfoSet, DeviceInterfaceData); } -__stdcall BOOL -SetupDiGetDevicePropertyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, - const LPVOID *PropertyKey, LPVOID *PropertyType, - PBYTE PropertyBuffer, DWORD PropertyBufferSize, - PDWORD RequiredSize, DWORD Flags) +WINAPI BOOL SetupDiGetDevicePropertyW( + HANDLE DeviceInfoSet, LPVOID DeviceInfoData, const LPVOID *PropertyKey, + LPVOID *PropertyType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, + PDWORD RequiredSize, DWORD Flags) { pSetupDiGetDevicePropertyW pfunc = @@ -957,67 +1297,68 @@ SetupDiGetDevicePropertyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, PropertyBuffer, PropertyBufferSize, RequiredSize, Flags); } -__stdcall BOOL SetupDiDestroyDeviceInfoList(HANDLE DeviceInfoSet) { +WINAPI BOOL SetupDiDestroyDeviceInfoList(HANDLE DeviceInfoSet) { pSetupDiDestroyDeviceInfoList pfunc = (pSetupDiDestroyDeviceInfoList)get_proc_address( "SetupDiDestroyDeviceInfoList"); return pfunc(DeviceInfoSet); } -__stdcall HANDLE SetupDiCreateDeviceInfoList(const GUID *ClassGuid, - HWND hwndParent) { +WINAPI HANDLE SetupDiCreateDeviceInfoList(const GUID *ClassGuid, + HWND hwndParent) { pSetupDiCreateDeviceInfoList pfunc = (pSetupDiCreateDeviceInfoList)get_proc_address( "SetupDiCreateDeviceInfoList"); return pfunc(ClassGuid, hwndParent); } -__stdcall BOOL -SetupDiSetDevicePropertyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, - const LPVOID *PropertyKey, LPVOID PropertyType, - const PBYTE PropertyBuffer, DWORD PropertyBufferSize, - DWORD Flags) { +WINAPI BOOL SetupDiSetDevicePropertyW(HANDLE DeviceInfoSet, + LPVOID DeviceInfoData, + const LPVOID *PropertyKey, + LPVOID PropertyType, + const PBYTE PropertyBuffer, + DWORD PropertyBufferSize, DWORD Flags) { pSetupDiSetDevicePropertyW pfunc = (pSetupDiSetDevicePropertyW)get_proc_address("SetupDiSetDevicePropertyW"); return pfunc(DeviceInfoSet, DeviceInfoData, PropertyKey, PropertyType, PropertyBuffer, PropertyBufferSize, Flags); } -__stdcall BOOL SetupDiCreateDeviceInfoW(HANDLE DeviceInfoSet, PCWSTR DeviceName, - const GUID *ClassGuid, - PCWSTR DeviceDescription, - HWND hwndParent, DWORD CreationFlags, - LPVOID DeviceInfoData) { +WINAPI BOOL SetupDiCreateDeviceInfoW(HANDLE DeviceInfoSet, PCWSTR DeviceName, + const GUID *ClassGuid, + PCWSTR DeviceDescription, HWND hwndParent, + DWORD CreationFlags, + LPVOID DeviceInfoData) { pSetupDiCreateDeviceInfoW pfunc = (pSetupDiCreateDeviceInfoW)get_proc_address("SetupDiCreateDeviceInfoW"); return pfunc(DeviceInfoSet, DeviceName, ClassGuid, DeviceDescription, hwndParent, CreationFlags, DeviceInfoData); } -__stdcall BOOL SetupDiOpenDeviceInfoW(HANDLE DeviceInfoSet, - PCWSTR DeviceInstanceId, HWND hwndParent, - DWORD OpenFlags, LPVOID DeviceInfoData) { +WINAPI BOOL SetupDiOpenDeviceInfoW(HANDLE DeviceInfoSet, + PCWSTR DeviceInstanceId, HWND hwndParent, + DWORD OpenFlags, LPVOID DeviceInfoData) { pSetupDiOpenDeviceInfoW pfunc = (pSetupDiOpenDeviceInfoW)get_proc_address("SetupDiOpenDeviceInfoW"); return pfunc(DeviceInfoSet, DeviceInstanceId, hwndParent, OpenFlags, DeviceInfoData); } -__stdcall BOOL SetupDiRegisterDeviceInfo(HANDLE DeviceInfoSet, - LPVOID DeviceInfoData, DWORD Flags, - LPVOID CompareProc, - PVOID CompareContext, - LPVOID DupDeviceInfoData) { +WINAPI BOOL SetupDiRegisterDeviceInfo(HANDLE DeviceInfoSet, + LPVOID DeviceInfoData, DWORD Flags, + LPVOID CompareProc, PVOID CompareContext, + LPVOID DupDeviceInfoData) { pSetupDiRegisterDeviceInfo pfunc = (pSetupDiRegisterDeviceInfo)get_proc_address("SetupDiRegisterDeviceInfo"); return pfunc(DeviceInfoSet, DeviceInfoData, Flags, CompareProc, CompareContext, DupDeviceInfoData); } -__stdcall BOOL -SetupDiSetDeviceRegistryPropertyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, - DWORD Property, const BYTE *PropertyBuffer, - DWORD PropertyBufferSize) +WINAPI BOOL SetupDiSetDeviceRegistryPropertyW(HANDLE DeviceInfoSet, + LPVOID DeviceInfoData, + DWORD Property, + const BYTE *PropertyBuffer, + DWORD PropertyBufferSize) { pSetupDiSetDeviceRegistryPropertyW pfunc = @@ -1027,19 +1368,16 @@ SetupDiSetDeviceRegistryPropertyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, PropertyBufferSize); } -__stdcall HKEY SetupDiCreateDevRegKeyW(HANDLE DeviceInfoSet, - LPVOID DeviceInfoData, DWORD Scope, - DWORD HwProfile, DWORD KeyType, - HANDLE InfHandle, - PCWSTR InfSectionName) { +WINAPI HKEY SetupDiCreateDevRegKeyW(HANDLE DeviceInfoSet, LPVOID DeviceInfoData, + DWORD Scope, DWORD HwProfile, DWORD KeyType, + HANDLE InfHandle, PCWSTR InfSectionName) { pSetupDiCreateDevRegKeyW pfunc = (pSetupDiCreateDevRegKeyW)get_proc_address("SetupDiCreateDevRegKeyW"); return pfunc(DeviceInfoSet, DeviceInfoData, Scope, HwProfile, KeyType, InfHandle, InfSectionName); } -__stdcall BOOL SetupDiRemoveDevice(HANDLE DeviceInfoSet, - LPVOID DeviceInfoData) { +WINAPI BOOL SetupDiRemoveDevice(HANDLE DeviceInfoSet, LPVOID DeviceInfoData) { pSetupDiRemoveDevice pfunc = (pSetupDiRemoveDevice)get_proc_address("SetupDiRemoveDevice"); return pfunc(DeviceInfoSet, DeviceInfoData); diff --git a/wine/setupapi.dll b/wine/setupapi.dll old mode 100755 new mode 100644 index e6b147922a0f18e1366b061b065dd4f5d3e9ae22..c66a7f79b76c9634a125ab81ada8be5a1a65c6ec GIT binary patch literal 112176 zcmeHwe_&L_wf}5bV8NI~jEWj{wLwuaCWr`vZ9=kvXn+M0eh9d1c5jk}&F;E;HzW!I zE>Y5TORD&*Vns!zmA0se=tB(zAr{oA)aKRH#+s*>LZ3XPN}uR{KWFZ}yEmHvt$pA3 z{r)jmX6K$cXU?4Y%$Xl|?wz>8R$gi{nV5bJU;~&=jX`3^b1F54S=kUalI5`T6MQ{S z!#RvmFjGPnQ~6fhYoVdg#3&3zQi30VUM<{n7Go5Q`Fnn1Vm&`s!`Xuw%ds#vNXwsw z_%Qf~!KZKxGzH?54hj`euZLXF6m*{bKVe^o{w<;a6@xvf{kozJK<=Wj$OB-c<;sZwSIrmSxUb zX38FTv?9&k^yA#DEOzHGc4u+TU4^EV!%S>ZS>dWd!_P7=y1ad*Iem}S`bYPbmV#kr zR@1O-c1iJ2(v^=u*EMR?=a<^^IxTe^;_DG6BHVybgiwNDLzsea6GAzHeoe*AG@U>^ z9l>s(XX3sRVGhDvgn0;b-Hb4w-!ZlT@j`@KG`hdYiNq?6L5(0G)F9L%EJ5%h_z>s{ zAl!-&CIZ(|ghqrvd@}6x`d?jf>Dn3B{p=?Q_b&O{Cj|?B{IlC%X+JpXsj|#H7k=4T z$v!W6^S94@b#}o~)9(YTvnJ+D{B>C@=Wnwfx$)`xbvM4g`iJ{2n7wb^-Di!7Jx{UE z((=pG-rf7?vIT!7# z-WGbWGBkaC`1FSg*8TYLEqAmXx$lZggZpdiF24A@pI<-lo&l~;p8WNr=kCk8>gG#c zfBE^XllJXu``g;D|Gw+VN9JAQ`SZ`buX&dKNBbw`T@OC^;IeDax%iF6-~3^3(@pZl zzj$ut+Si_&^x}jASKRXT#dp5^>D+NmuY7XvvTq)L`2Aw&+V;f#%l6G&w|%yIO68T7 z&;It*vPjbo9~t2kx3Vf8&EY&A)#(^Yp?q2tVT$ z=bcqe8HQ+E3jGN=yLJ)((G;5eTEDEgd5=1Sc9DE2McBCq$i-UgqW)qu@nCOdOfBuh5Y?4O% zp)dQ^q&>d^{*Ty9Y=g${1pU-x6PuvXFTnvz7n=B(O8V^3cW$AHeX5l=4)lxAPh&ap z*8;!$%1z9KbfV8gd7Y>)rd?kDdElFGG_iX%`LW<10sl;mUx4yf&NnfK#(xR?3fN0? z7vVP-_B~c&Vig)c8+3dU>_63%p9dci=g&m_&8YwBoX%h-&^?Co!YJ+D-K>HSxKRFwhF@HlhA{`-}yDlc9gS0DTi+&)u577Z3+f zew9XFhWcMyW@7JX^i#;c+n~Q4_AR{*?Wy6j4)hpdd&wz<53+=HQw|Anwbp3xp z`t1h1YN3CmK|hv-tj8e#5%^sOz7uq=fqonEPoO{P{{3gvuN(fQ`_oX!F9aS{D3|cR z3G#n}|JpVBDcE0Pu%FgK8_D@`WdZeK(y>n-*=L!mMsEbw|E zrg0`qa*0&$aYydVYnHRAWz)*+Gp9QySC*Bqxs&zV z0>k}i=9a3$HO%Goxg;-$`Vjq~K^?VDpBwH3HwgGWJ~@$I?{VuQ0o>L(YQl)%O5sqI zo=H^^a=83qpUi6ga=;s|amcm6)y-<>z_aS4P{>&$IjZ~(tVWU@)&8JV6NI>%ywdGo z%0ejFp^34ea0Kz206Tb*AmuH6A1bpa48@91KAyN1e`Mbz*le*-e^|m@p(>^ zlTl5U20gN*>q@4K?v+IK>64HYsS0{rO8{>|L^ZrtYGC}XngznLtJWE0F0Vf%={2C# z5c7Jf$c~`om7L(A#%c%C89a+lR0kzJyTePKtmT#(JTfz)qcc$bgir|h`vbDa@6)-| zBAOc*wHcD#Qpgqb1Z00u&qJl?_b#u~Q`gsI%8zd6@zp@Pp5NgNxjY_*bo`^p>k2gT z82TIzm)sbDn^0@(+6`u4@RHozf#jgm7plg{L|oZZC;7uNd?2OU0VT|$sIL#mkCksMOJgkFR$Q5Wz^GOX6Ls9i6q zJfL0FC{1ytYi!f$_%f z2}pLo*W+r`6N{?+L7uE9lzPLVT9VTf%HTsDbuQ5ql@e%S-pj+h10_2Szf(DS?;4#* z8=V*<&^SK7?5XxhK}U7i=Q4VwK8|3d;M1PNg<4dhR?y>>q<}swp-1^@k^t-3lJjLU zqdLIqWAHRyvdc+@h8V^XS8dV=c%};-(N!1FIDF{AKvsu;T9P)ZkPk|}dhUuCWLyrX z+a1J!r+T+jW^ON~6u`YPpSqSfT(wIa)tEN5CcCO2FF!Bj&&wy;MR#M_I*ZaEbv;4#hy!O7T#v!6`lsYj=v% zu?D6XEtF4jCY}i>MnjFG7`s#xDaM9OA;s8aEuk0>u~R6|;}W8DlLJkHj-4#aA%)5XDzAww_|+<_4e?mq0)uAP^7;2m}NI0)hWc z1ZG7JTa>+c!Mv*N&-In=nhN9yym_fy#v}kGZC)06POVq*cIIbtN zh$i;I4o+tiO^F9vIX#?c`Z>6T(>X+2iQd3zx~&~abPK0*iOwf_C8vp1JC5i8r$-T8 zNc193lW6S}qV1d>L$safLQYfG+POsMb9y4ti-@*zx{&BvqO&+%LUe#=#%WSr+d%Y@ z?~w3hIngVL?&dTVRlA1h4o+7P-9mILr>W@Lhlt+7=>8K>6}eT?WM-%|bWA)1*$cXPUhXbaIDoL)zC7SXMoeu(JdL~r5rdZMjFZ{YL> zqDK)0>DMM|6PGM`1)Y70Rz@PHtOuOY%RZ2g$2;W!5nk`>|yZe($8; z@$K>j(L0YIYg4OyW@Yn#L=I=qh<8MGXT8~$7n3h#ZLzJFl0t=wU$yHUpbV*5MjesE z=F8ig+jc4!?ffRq)cNi)IXimqaZ>r7JT!9HVxJLz4+^6TkCRSSUCWo57k)#Elvxm5 z)wUFRp=ag4rNbx|zAPG{>cFNmB8SuCFKi|Y&P59JC4cr6@8BG8qS;jO zmX=sO!!N(0@yh7og|&nmqsX-r{UE?<`$-j7@w8e8d1 zBYhTKE~g*40nE&lEnKeKua%qCkDQt0)^NG)CzBgU_ATOaExKHP_6?%?j^lE*x?Df? zT|8JhlM5B=LRLlqs+WeHg$-uPVK`fCWsWXp?xzkz*dV6t=5j}UT3s#u$PFdz)^oZ2 zx?F$Qoknspm)m$UxpcIbQqJX;>2lfqlzTd1m&@fUbh-Yp%OHKn;dQZ2Ul;2;+?^p2qtNDG1-(SY>Yxw;| z{C+;aKZoB>=l7ZXzL?(+=J(g}`#1`1QLg0oU-SEm`2FYn{!D)V8NVOQ@Bc*iQFCq^ z9}>*U=a-@Pyo)XrwPmfb9go^9(aQAZEX0}HDvz(R55(P`!a-@Ak=tyy@SE&ryOrMz zZ?=C`I4Uh#;Y`=C}9Sx?m4G z_yvBVXGd*arjE>QEOT2cjVrN;=iPYRuCyNG0|U5`R#T)q6T?QPN{lZxXEr@gdBN*G z(roK73SE^Tns@E&PP=?pWLG{0p;*LEYG=kG0j}Pus>eVSi^x1>x?W&Uv#tA3AsN=q z4V!pT{RIZF?a=2y!C#cw1L!JJJ^qRYvQKE-<<#payrQ z(a6V{XQ|AVm~?dB{96{roX2}ZNC;*raY(Id`vF`f78!$aGRm4-Ggr5&MaLpbc_pq_ zE3wyh6zS~H>{w(RiB=wsnp=S2BqI&97c;wZ_t#{zr8ft4SezQn3;xa+*|;O*#o3^14xO|TtZ@nhXUD#2t|-rz=AsIp9{7~% zZ+?IEUUj$?e8tSGKC+rI>G5nNG~13Av?`sLu~q9-HQIe4nE{N*?7f8+BayBW$&&Xr z(XxqB57q8%D$(!DW6^Ttj^0n)=8m1+11|4~-cM{($6oVJuH_^WB|FV)(EB1>p(*kz zWgK+Jvk_hxXwquBW37tE8Wm=^=;8s3836k1Z`_Askq$Be5J4LCG$1ZtqcbC|W}UA} zM_MhZ3F&&m4(Q`;R=qKNODxicw^M`eG>xt8Xpx!^`#rC-)KbUVo%oQ2tXZ&SIJ?ch~3d25~@y`PeI{yELm z5#OuH5()cjo4%1)w9Z(l3)WeFLB%^QHT}NK3*wephDGe$13$EanUyC1Sh) zMwA;K%Wfi@RIf7aO4t$Oc6^*>YUg&qf@5ED3(z;4WSW81?sGrY9(_>jbMLFwCZXwE zNNv3+7o%$JQj2X)i$!#UZa}l;#Ud)w{Yt7Wsx_l$ndu?8;kY zDz(NsYN=QgG3?4^yaHJmEv#@#HBV%{QKAXUIIP(YP2icIHM0Kl1!p1ig~`k>7$v5t znaM-60=KCJ#_Vx5#njQ1+wVxWErZP+@3 zSsBK~@Ea-{Q^y`^0~-6An<}6z(Q>^#2;<#j+>G?dF|%yL&x-QMu>tZ`_*pS2a?B(T zjvPyqvkr}oMczm#J9K$0@?nxXKNk6~Bz1Z$VtG$5wk5*d$dvg~6Zv1}OR%?z91b=E z#rTWdwwR2R88hwi7s*{>kzC}rE8G5yFtsLqw(p?Jm17`A`?5n9MtG+&MR_!Vcdg8O zc4G0)`Iro(E6<`~R34~YaWUXYjJ~vXN3$s(U>z}V`y!e+ENd!{tz3T0p84Re%x&*w zZku{6^6`q;ym)L4jgZ>dXzGlbck*Ssu3;T%sQ;%lVASlTVg{+W<{wuP%N`!Jr8k$n zYi`l?D5sN-&mE`;)+vQKd$F*?bhEJc^g4$xQAR31hwrXx+Xd-n^yX&U0e!ORDQMkm z>qJhdIgq)n(9D-AaGCv>fzT0}u@2#LTMlojbfTAcvG52!3|8_+d>oHs5JgHnd_ei~ zb50$+fd-h~I$zOr?m>FAFui#%#Z$2K(n{_in$3=dJ7>pU7_uQApFM{>Cfuc5%Evx* zS`^WxtkiAkjx6tHjUk@gtu%8x(&2MhX}P&+vsGg>)8-neF69rZ_`!7mj(WSTlSb{M z|4HUsuukH-&%y6kvRbz;i7OqxiH&3#F&fe9#h$97U2{*{Nzuz`VZd4HU9^xYXIEa(#wXQ-p3{YUq$>w%oq6E} z)R~W94e98+nBq=euDp!qUDfu-1+)&2mYUbtwnly!4{I~zEd=Y?+N@!2X|}aSmbWsU zTL^CNL1^U416Y|jWyKG`USNTbs$=;#l!n!krC*H=oaW0H*uy#T@ati>$_iBi$@={G*3(}@ps_~ z)%cvGF7nT$=GpZ_JOg43nqpov^=PEQG9Wx0$VTy0W;=>y93oWU(Vs<=6J>6*p5atmz|(e&`r zxfoR+hCYn9cy8okZ|>r3oC1qBbHut{yx{X&65=(Nvc1w>)td{0Nhsyr?6*sC(6-4lUKj5#{mEs2qAmuC=$psf$#Bs7=9C zbeU!nD4I{O&ReNIa3ewvyD|j^6Ecx}ZN8JAiP{cBH_g^#xhn1P`yeMU&|wj%&l@8E z;R)JWg z1UEzrvB+BA3-K*Lni?GqYvOhQ-5pW!C}cwyjZ`-XK5~ z4sWz84%I%m*SQhfRxG5pQWfOW>4uhT;eC7w$5X=>skyLZv4f zP27p4b+pjFcyR1K(*ZsnV>FE@T&z@;p3kKpNR_V9 zrB8d3OII7EYg47ir${d~N?)HUeVQ(P`%_%^-A3v3ROvrreB-tJsVe=jQTiiwwV15s zZy>$b)(Q_qv|EkZ)o8yObx_3HEGLTb_Qp|gc++nepB0Ptpc!V^XDX3TIJ6jt`-+_z ziyqg+7U*JluIplQDV9nR(*iPq>M*A+@{g(eh3Ww z4cP;t!*yE{Zd`VCNaAm9hOTk zP!Zl{lwOvQzEmB+rgCWv-0dW$eAUgtQh5cOhoRv>ERyy+Vqd1R`v8gZ==H)`TqsQy z;tt*#i(JhU|N1A*!J%$dLHbY{2EV7Iz{@Re(5sN&?6CYL+wGH9Q z{j}-Fw+JiS>6UK^R(8-+54yC$J>HE~ZlAf>Y@eajP{*cSz}Fr|_2S`HB~Nuhp19{B zp14~%S51soZr4;z?Tp?!DmwLm>FwCm?HE{RD;Gdh-=T7EMz5i=@9bBussD70Xf=19cZMF zw01bbKr{as=O^8%94dH$0B{HIU)#~YPLD;i-c|e8547f1M(X{-tU5Qkg@x=z4}rbC zvS-DjRxNvv*0Rb_-huj&%-1AeF-UgwtFus(Y*8io%+ZfRyC&w*#W1C`W4n|(8g{RC zGkG{YQp>|4%R88y9$9{X$%7)xJN1X`9o+l&{gET3c9lypQ1vxwNG!TYE9+YociJ|h zHOO|oNBypgYw44>SC87F3h|uiJZg`6#E^)7qV}k}42kGb$GD}p8WPc!Ge4lNyugr% z9yNj|PBtWV#v(WH#LG8kS$x5vpMl= z5-+OB3^U|nO-I16D^;p77~Rz5$cI!Pox5GNhSyz{{~?!0V=2hat)~LwFA}K?;Y{>{ z!=R$-uki&>*XVQ8|1(OL=9dzaA5Zll)zp2%~Q_wX6Pz^%3fEajTkzf+Iln`NnJ7!1XiVEq`n zf>b(_DxI3jr@z0-QKT{>sj^;EIV+(OZkEz!r>^@4SnmwnUXrN$J(|{UkLz`Zr}XO& z=>`x|HlsCvxQ%o#Nb26C>3%$+J6@l2LVqx*xAu$i`K12a;)LnDH1(4b>dAE7eX(Dv z@A&TOZ%;Z$*@tm-;IESU{-o*qLR}8io+TO;4ISATMg>Juj!AH?iEShcWSzKt8W1~EdN^5PM{JlNS5$3t%NE=2`BMK z`uZ>V{ctbu#A|Tn5o~^~YJ0tqF?@11JbJ4+IzC!tiF{|0b7?U?dyca3t9blo!%Ie* z6xFYP@y#vjr(nv&Q$Ww5cksC>shq-9dZ99M*t$3^R+5`#$Io!g@q!v&u5PgEhq`O>ugwWu~2Cysi5^74)H;yYoNON88Z4aTl&`}vQkHWp<87Qgsl!lJl%^4*l2a-;w? z5|=xj=i7 zj!lEpA8LAe^y4?J{nM~#&UktK6KB4ne75QeU&-Sa2FI^`b<@>FpDZ-(|LI+Kj(4BO zmh3!|Icv^hek#9^9~m1^3l**O(>Ha7mc~CwT#UU@7G0N{o`rV;CAnbPqpLw@8EN<9 z5TaceOSB@_yg1JHV=8hj$bfIo>@y;}hu?7KkzMWK5&5I%S{WO0MTl9gtk~-hNmjR1 z?{P_?JT^Tb`I6LJkFUn>t6_5|ORliBqI}%gv1MgfvZ)@Q#~$?aI1u!!jH0WHXR&hs zQfnw6Np9=Ng1l>|PV&fChc0KOVXrrlJX%kk;;E_CQ^$Hq9Tbb9&iK$?0Zuf*zULc|-{ED9l>bC`%!$-)EJ4?ttIp zldbs;gy&Gk!Qr{o5-w(yA)se1_f%Des;sqsIp7V~SfzT&Cx`Oz>NJJna(ca0PS+AO zcOE{ZlxjR7Sqhf$rkUoKJ=GqUQ}+0MA#3d%z;J_gL;!k9ogOdnmi<;&P;$zWRj!pd zM0I|jNA?HF2)Pz4_r+9Z&>3vBUN$0hnRTf%WcB%FYc&Ve{u&k&81%`b~tg^GpDvkVxE-@3+3pEQcE8u-YGg}2zHq3@$mb195_$$|ZX;a=>^x4d z=eC4IX;4bs7gYfyqj;$|9IE9BNvuMi8lO`R2jMUYm@l?g`aAhre`b0KqRA@G~JZ>s{KYDP*E9u zRHo)i*iF44sVtG)d$6E#5_l#GIWeBPiIBKSO5!{dlSsX0I@-r{{(uSe<-u6o-}6#D zegOA5uf*fW5X-N{7i0|10dBkrcK8E;=z43SsCi!`Y z3lWDAFG4*3xAFK!#NX_T$GZ_v-ye@>R#1)A5AZ|fydM6&=jd&vBW5h>19N%>O6z@gsi0Q+xdk}wtxE=8uco$^G z2MM1e&P99#FRvCM&iW(jgZNkYFr*VPeOyD|UhGFa1#uoeI9iQ(HR4vpzeju=@fG-D zc-#;iFGDON?n1l;@dSLwdKhsO@kq=(A0c)lF2q;e8xTK%xC`-jh=-qs(<1oDumbVR zh+7b!j}Nl`RQi!8nl}gX*md6 zUW7ih3(K&SW~ASkd75l)U_UxcoX=iga0YDMG~_T)H^UJYcOF3;2H=4dB9s~3E=js&~e-kL&tCSz<&@M zbo9af?tX9|iE__uhmVnc$?^@jFYkpQwbcUTxdwR_o-j{ZsWB7PoqTNp@?1oDbl;h$ z;y`@*dqwlX#|fDv-=5@mfIs5|eDb{`;7 zZy+TvVNr{`Um@@Hz4IFIlZ;UC^425Iapdv!&eO-%IsKGJXA|eLzr?34d@=$xwpDLM%JpK7C^}R*lj{|>vZ=W;RF(6lWHx+5fBi}>FHy&gDJGeL4 z->2@|Q|eB64R*n$FJs=hg38r> zw_5d0vdas88~CQ)<4_;ne=+wqjPhb@E9%}L;kHYs)mUne> z*hq}GR?K;C;p?7OWHsb9v|rk@Mn@uBE0AX%=0!gKpQMlJonM2Hw|finE<@g`-sPoY zlvdUoBA$0Y@;-sQIOAuuH8rn3Q0c|;ycW#aJCGMAm`;}Wwv=Ld-V)?}|9j?LiM)UL zo;L45Uh}`l;|or1b6RnV(bPuAkT(~3zfT*D!DK%fdF>}JcWFwo9IF8G&PU$wgY#zO z4I=ON@eTT@=b`VJHy7)$*O2%7;JgTVKSJK`gY$aiHT~iHmD`ED7bEZY!73Z;%E`$4 z&$dw({K1aAw;(SKQ*@Oe(7JUl(o)s|#VN~{a=w;XG&JEh+S+9*ma%LR!e;0RK+jva zr}6dfw4#jky9N|xWJS!=GV&*7jIm{mvt>-2oKZL>BR}nyj4_ik#!bqYI4PshmXSZ( zK!QDaXhv31TSj`(o(xOT-VC$tG&US+vrt$U%E;Erh@?%+pn8c*ARrJB2nYlO0s;Yn zfI#4X4uRHdOe}yvkK70GTte~R5$o$N`YN7TvHyj1_aYOcPw0DoZesM&Jgqa&Lr_4| zGdR(I`!4QpfD(Gl-h}lW9t)WT3@jg6F`ffxJxRK;MZ%6gMt@ct`xua?HQyNs_**h; z95$Hf-hLKi_^TNB!`s;ZLO^zQAp+&W;VgeQGU1e&pKLyY_%MY28}YfIzwdPzbs;VXneqmdusQiQPxr3kYTsuAiD?m~C~;c0|z2pcw-nB9iG519PxquIN3 zySrK1#iyAD6Xc1@>PXhhYDF?N0)n1OZAaGvYA~(w2k@2w!D&5K$zu@PgofCThJ1)J z{y(_%I@3E3st5TD1uF>J_oaQ?_vV?{83+M{kvE&z?Ferpo5^_O^&*XJdq20%if&uh5^=Y;Rl{_*#7T4B9jvT13E zPVinZ!Rc#2qD`q?J21}D2SX(Y-P|WAm9a7mmc#W9s3s+T?MS}`VKKrognJO4K-ht> zA3;I*4q+H{#xCEpb2F6O_YFFB(9`w*bV|Qf7puU?b6U zoE~~VJqurZc%|u8i&Zgv_#ioIB-s)6c^Vwm_)x^*bJj_aSN*Vpe-}deOFSX^3`0%O zJ`*9&4}phfp6Yjpy;3O@OhbPDemaq=eo#(<|6+SiyTMNeIjn2!zYD}1uqc7JINN?{8t(&;a?7!kP zH(ITF(h!!9TkZoqDV@=u=kFq)&}h6KX67+TdM)Tt2KqOkD-85IpqClw&p~fA(3w!R z-#}jt`lx}P4tn^t$?|9-Ei}+Aplc2EbD&!c^n0MU8|W`VcN^$zjD?o5NaR;KyB2i5 zfu0R|u7O?(I{P{kUq{f$xDND=je7pG*;Alf4fIyfyAAZ4pxX^}7wG*4`YX^K209yk z@PL8740NY~o(#InKraH_ZJ?Ke?lI8!fL09j)1VI<=$An^puY6CjPS;Xy$@Qy$X`AJ z-HmyjE~-C`5mwDVi2f2Zt<`k?5cGTYw7PaPVdZodjR)HL#e|j9M9{Rp)8(gtrnMVg zm=#$K<<~AMKL~me#wNf7eg<0udL`(Q8vP{b;bP6+zT)7o^(3+6+O-21W4fMOn-vK-jasIQAUv1!ThP(y#==nc`Agxj9O2fSR zSI}=7>OTNuBdvpIFOc;|(bwVHcC+SNE{VnXJXCW$oI6Y5c{YdXFh`t{5R>-UP zoWqJhKQ-CJ=shv{QZY1`KN<^5&kem zlb@f$_omRdgYGcc^JCDt2LFB3Aa8}v&7j8^_^%rHWPdkkdbZKq=Q!x+$D0^^i^JOw zPl(;9pYE^p?unj>^!B5VXz5uB6v592+(L#O-|pdYx=#NOBZ#RdA>G83b93GsuV zAAx^u&}e)wnz+cmwJ9`xTcuybe*tt5_!Z-x^t}#xy8-WgpgTchn}GYnXQ0in=Uz>I z00wS)&eQwfMKq9W7wI1Zy2pU`q!j*K&`UAD(6>@leii5$b4{!jd5I3C$Ty|X8&c@! zQ|R9k{fs{ToWtG$eZ17f=$jML-wAqarHRqE14MVH*I};#&_+a`j1Ybf0#l$QfN7az8U>#x1s!%Df~x4?>F$D1x?S`x_!G-~7qXzBfVBeu7=2KQ+jczOO;k{(|n08F=U~G4x+6 zZ31W)$zPX3CpHGI!g&OK<>8crE6>jy4*rZqlp`QHYVi#+zP~l51|_ev(Se7$V55T{ z83>)6?I<_}fAlH%*PMbs<`n#EPr)C13jTGc;Ey{6fBY%<1^K5EI2D2gr-HEHR2UYV z3dDj_p;&M#7z=viNKf$rmxCXfa7fMuV1Tn44#S>-nvmEwP|1{?gIVZ zlEc;2c`k+?tDUuR$aM~v*YA_4kGlPi8n3^~>2wuh8vj6Ul;I7IJ=QYPS}qG zHd%Zghcg(|`#h@-V#mbc4%gK+B8!1?KvM1{itQiuRDz3@PcJSiw>hR2O|?17rp+#z zSynWyqF3syd9x~PQ*m0Mw5YPYg5mfL&bZJXkJnKZ_AYVAL8mWNjln2DhagaN=}b~) zn^xj*fNB^wHqYo&c~x*JGQK~ao7^oq(BYtsI-nud9r8PB`H_7}K!Y!!}*qDerC1=K@Mj=HccHDJRF+LshgE-#x@?7$`x&InZ{A<@86?RKbVvK)!F z#c?oioypM3p6vK-H-SVH{Qd2Bur=a8>DtIsl2fLE9ILI!8^|v@*R9QIku)dP6qyHQ7@q`NJ~9SxZ#azzl@tkb%?= zPGZ<5|KX0LmY8sOhoc5Z9jU#8GKMzl5hR-6>z!RafobSt4h%p1%%$GesV`^v;Ur@} zX1X;=-=D1eKIW+2=d#q!pdaAmhmh1(TlXCd!op^96ILR5y#IY~qo2bN*Fcj}X_jA=5 zeIv-v!|5Gds|j!H*y?_f7~hg@tSQxIzCNWT%y00> z`n~Qu`VgOVKP@{A%t>stM4A_M4F*3*_F~O*LYTks(nL2o)s^SyI(Q*QXL5T9@`7aD z$LOLiuhUa!7*lbI6=#s8VA5%|F)~pYT;6zM&gd+7Z8=9ho7?B`EVR(kS?#bcKdXsR zKehjc!c}!xFX_{dUeR7J4EZFgqSgYIb^7hFr!mV zRA;fIi5O4iMm!Z((R7^9dkxO0gc|E`>>05fRO4Fx4j@Z;HNJ3ORoLToUxg`yQ?<@e zEz5H^`jAtN<)F%`R}a$mA{^jhP9%BtCg7D>9(ScY+~(EzL1LMNU%uqa3;Nwo*~#*x zT5W||>xK%QQnRW{RW*lxj{=-^9v2Gt%cL2maXnR`5X-}Ys!n^EsXttwB0e8{Y<2Q| z&kF1{P{=jpq5WRE@(}R%EdAJ?VYCfTfzD82Tb*AdM`0WSZQYO@eG5Zj0}3D>vhbMp zXbKT1FUirjGZg5X8T}$zx@bR2;NRIa4bh&!mldXLF+=?575m~zR zsvJ_;tq8hYe!huOApG=;Fd?1G5Old#ybq#afQ8=^-DHrX_eK=fU4=yLB7Jm45p;d@ zzK8;#NnBJWT|Y%2Ic^g68R^~q6NZAV;L-Ikf-XnzV<_yP1ntuGJp)>oTXdU=Q5Z#z zpz*~iO$4CSs>_YRT7FOsA19)d1M@0X53ZZnrqe{{JI4RZ9}Y(tBQ-Hg3s{USNK zK1QJOsa$%W`QP_LI~AZ^RxSP%F(v0~G|7$7?kOUk9t>{8tPkKF0QM5;ef%^8DwlkL je3_Stn68#5Ol-Pg&rLUhz9}-w{b`Gd(Ytp2(&hdS&$M^m3Z5>^h+p~MhhabE@@2T4Flk9K=!i;1r z%+gMI0}*mWnrw|mpmw5ii8qMZthSY~zcMV~8O`CCdiKTf6GzOMf z`VT+Py7a1j&DOlV%zm49Oy0Wqj55Qh-L2sa+M z`b$N(eeIh!=HAoq!3Q5)`N8_Duc-awH~lw#src2`>et#9TfK`WjnsYwH%hu;Hui z*4KXU>)jn6FGPCkt(X1sjdzBey=mL(j+>U;a+hWAH%HHTIC}OUzB9)?Gcn@h7v^o) zx#Q7|D-X>5_L2EZHb3~u$h)2mKUf*Q?tA40EjLF-zPos0OT!PZzT#)^+`n?6cV^}H zR$g{X#le66>)o1*AKiP&M@{FAPdwf7g%>Wq7Ufj3zn0zmx4-Xxcw_GF>(3wgtBuy4 z!4F^Xg01SE$DZ8Nbk*)_^4|OS`FAP~ZTV62egF9GH(s7HV)By$npAXvI*_<9eAxXM zBl048IdvFu5xpXdrv4)@I}-c(05ft({_QOLL$dU5$f7r8$v@2peA=ET%Rcp4dD)SO zXZat_(x*8qhgn4P8Hlcm2S ztG?G~*?Tg}{{va}O0xLV)6K;$LwaQv|7Tg{)!@c)Vxxu6zvr;?S%_{V52Wc`@TZ{M z!k)yy7{c<|V(^cdR$%B@U}F67VMor)xyez{4mh(O|h_TqWx+SfBTlSd=>cLfjmrd`#(Ya<6G19 z{Wj?NpsR$vGL(1NRNq0M_b*BFe+k-d!uvkpQF4QY%@X$CL4Ug3X<@$*{h=K4y2;)+ z_!BF!uy;gxOQ4@;>K|W){7~3O#QPiF3rom(TsHK-C%_nCD*r{W&Vhfjy|$y=ADY_# zSD^QUzDW4D9CqGbU}1X^6W-s0zbj{2*m^;Cfqw5M3!4l-iT?`nA2hX367&@G57~e2 z2i?3dUH%8q|9+)~y)NwOD7PN|(K>+a{T}|0f&aA5q4Bf@^-oN+ur~$&G5CA0$^V<7 zKO61o6#Uzvf82zp7xYOJ{S4&ZfEM|RqYG7{k}UO=vv_mXs&R*=5Ylg zu3F6vi>%D=pXK%im`e+I*?cV$tuOOUbNl_%{O)kLJOB^;euxRq3@uWoHTXPQc>sP? zyFFT&7IFLhi+Tynr$~F+uLTQ#P zKUKylK}(ediJP@FnDex{V1w|Sd3gm~weEly{Q^`NKUL8hR_lt?qKaNty9kZ!aR)q_ zpP@5E!&N3}v})Mp2}T1ER_CgT`n=L$LpoWHWStfcyK6L8Rj`rOXc1R+Fr?Llpy#Cy z<_)f3Ve}=J5Mv?D8*;CZthy$D(6gL1*3^fBF7%85x;4QbcGbHhXwE?KX?SBI;7Cn8hsIFMu&jb0u7Q66jHKbG(oUF z;tK{Oml{R%1+!68wpR;#LcaP)FeG)TFq!uF-M%`L=Zk!SN>mBG)ak3&oI$_O)5Jt( zX;m=9vt`C~e>7Z6a?BlW3V2+RkUJ2r#tb5=Ru%OxPiNK}9n)18jcAQ7-Ze6_q5+;s zK*s%-f1En^DNgBw<_zAfh-2Ny{^}cY>Gj<$qAfZSw>dz3520~hO-^FT;5rdJgze(Ci z27(b^wNDGVs-po9?{>U?xLx_sp9n9*(|{K7`vS`idy-r2^J6@?`aqD-iZr3yWLVPz zbdSSm@@T#WU!W$FOBJv7V!H8b9^tqw$k333%TNNMBb!s5bl zOs2<8OeM#sQsdIeA_k?(GRHz!@x*ahx-eP+;`~R%bUxP)Ih1F_GKY3DGLQJgC7NO* z81lKuHFAjGpAF!0MhXABzf{*;R*root@)QCgfI1BJ2ZgJ7kN9s{s4Aa{h3S9Lw!DM zKXTbJL2u`LD~kx)#`*o&ouKKW`i&JlqA&mGB&ZzLD)@BfNXae{Z^wR#=x!1Bi1^^w zjr>C*?iTT35$huE5%Dn*9~bcn5uX$>yWg;96>*-3^F?eE@lX*D7qNGPp;s$nzlaar zhnB_qwwq!gbeLisYUmUX!#u?*zKz=C2 zSl17y7{H967%gO{co6oV6rY84J;nKqT}$y`#)>E|z*!~5XxxbupTpQ>iqB=Ngkl_u zmQjoiMc5BhJeIL-6!XyxtktCuPzWdl6aoqXg@8ife-VNCv7S7A zz_}K-rsHVP!s0PKnM}5{M{<%~N0ha zpb$_9Cy)5Kssx1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4< zKp~(IPzWdl6aoqXg@8gpA)pXY2q**;0tx|zfI>hapb$_9Cy)5Kssx z1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4hapb$_9 zCy)5Kssx1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4qDzQg%4rgxe!< z^s#?Z{qG@~SwMGlx{c^OqPsY~foL1i?VNsy=;1_fe z!C?IMlVsB{*Y;7tC7+TZeLe)&bld>Du+#iAa*9RA#A7E7pN7PG@{`ZBBQv?4vS3K3 z{=PRxk``{1{3Lrv7J(5TN)>NwOEfV2@++RKjQ8AL%UCYcZT(R7=4+QS=1e-RyYpFZ zISb2Sx}HOFRw*~U54jw47QLOz9dZeKBl?i*$E-|$h|6u4a<)F?tRz>@(BJ9TyCF~%j-jK5Mj5L%WaZ!ePMSN$=$}~8c!#e&-yWaJePAyxuJcOdp2R0&*jEQ zxxTO)O!jEVB$}C&>&spN`9~u!(R}ds)ARuw;X*S=qIsK?d?Z{X=Sc={Tiewn8)<>?oA`dpsw;OPN8{WD6( zt)n^;6{D=N!^e_eK8aDeaQ@s_&j@|zr!+JlUd&kQi=7TagLOI%QFP>E{KP8{#rs>j z3U;u99qlyE6R|13OD3Iq(LeY&jX4fmVyh0@A{WFRhhpuPNz<(bEi`K(v!LLP;PBDhorUB}Prg&X^Mr7?+Y)zl-{?5BdZ(x_*vkg#F3CRx z{-GODkGqn%hZxMM2D2@p9lvGCmu^qEPiBHiSlV>#T*OOE#BSm>c+aRoB6bXO zS)8@B7p$c|(&;#kUv_k1BGyfe%Hwfs8|LS!W*UGmV@}=5vChlnpi0;Njj9B!goREk z-OpIeaU5-Oyx;20vQV;Paq1OZK98!z^rTcKzdA{ep?W87S<-jyiu8L?ku@Emg^X$6 z9SZ4I$MNF!PR9Y#gvmtg7GB@KeZp~WbsUJTI=~`V#vKO=cGwDb#Hzq9*zppY<*MB+ zFGU9@V#j&O7^v(h)D0?S&C9JlVF0h+i>5H@X}BuVC03G`M4p+*H;8oYjA{=zs!jc_ z-!cXqzRELfiI}xNwNEEsI7uqjZq3ff%g)G`8Fm;b_zA0?QoT7kClSjR_6$GrGOQbv z$-2x)S(h2YdZ%MQ*+D}ZZR&*Qk9=e0Tx##S9e}^oj%6P&4rOgR&Yf*(k2cEwx*t7W^zi#r+*>8|@yh+= z?{1iH%}JL^;Eqnj40y6+48)Nc+ihvJ#T@%F4E9^212`~8&x<*_FtodFbQ~qV^ML(e5z>vI>*VK)L)<9J(QM6=ta7Y$v1aN zHakzUpC|U@-t3IIcoCnBhwIm1?5yc1fOPAN@&54=TdU(R#rc@_5I5$<`xopeu@>yu z2a2@zIz&b3K%?Xm7VTX(^x zGvCNs+unqZ7Qzrb+CLFng?R2f%+jrVOkU3$3(9aX&Ld1Id1&K@P1Cng&K)1|$$&(M zLK+@8^?&doosV>Kf?jS2VCFf7iPhD6?lGVlqub!|nvUahIW*I)>oC21AsH22){i!c z4@;qeP9_-h!6?|_IDq;Lr#RNV8c3WSTb=*K6$3h_=a--@W2=X<$m;m?l!LQU4lWz6 z?=u{vhQR+}tu{mjJ1P&Xb6P25VP2~CmN?t-9?@C9Wf)DZ8`D8Ty1zCrkP&8V!!8SM9K1WJ~Y~Ax{Z%hCrZ*^H6}!p;q4!Aso1J+RpHa!jhQPGj|@kdhHshI`yY{J%G(I6EQiI&D6zGM;9yhxI@Qc;K*y}VO-&$eybruqku}e*|64%jg+mCA-UXDM{4q)D|1g-XEu*+L{Yvdixc0L``^(VQe}I10+A^Gr z(r;1}bUhcnSBOsM1zDHTN{=_xkIAe)=V+?07#a5(C=zf+#>LW^F_2HhuzMBzfFm!_ zfkKNH*{vUQdlplNh(qEwCvzOm({b1m)TMbrc%VbePXG@7lNi)Wim(+sFuTgG$C^vep? z(vb&8k7)g=_XWOriS?yu5Zs|F49H=xr8z@(zDc&ikhP~}Y15|v68rGhmWi+&=PiCC zPZzAc0*2x|ZS(|v?FT5gC%<;n-8hx_{Sod&B6cmGEE9<`7)Y!?iH;i|GPjmBGi@Kh|@3hNnrOWxXwkzS^cBx5S+}MI&LQ{BZBz*K9H;vnhc1^C> z3k1)jn_}PFeSS;J*H;3EH62T5;4XvZIuOEAV=*6_LuCDUOJLOqCc#UCv~;^%2HN@L z1zb;{y=ap|G5Hsz6VXI$HMb?&i|PCHbh#3r8(&A~$-ep_Pa|!0YzD0lcwu{e|HW zjOv#d9&zDKPW>WIq6&L>`B>R-2X`J_5_jy3vq_FU(OF2*O=d)*avz4`5BUhT#<8Y$ z>`TnrI(L~BxpCxjjaJ9Dc;&-LVm&|WkdTUQU5H(dUQMcwomk$(G~9nUwrU$A$F1-z z8QqkKZRfrhntaE?5#to6IOsrfGg)|UMhC)nQGf0nYz9B)AVBl>vReEmoY<1)x8@)j zYtP-wI@)f}JXsR$dJaHd)A4ax#_h^zy$|_Jg4W~hMXiUUH9*zr*bJ9Y)Sh^WbJ>8z z`rp89te&Fn`ZMqH{@&`?5?i%}MXwh!XC>CZl9IVEQ$}p4nsML)!&F(`I9n32w>cVi zL!9^9qdaG|^b1QyEG|Bw%w%+*KAY#G*VEW<&Ed>`FAHX|WYQofrZo{;ht(2BmW^Z* zF~c}BmSE|f-h0MfFcJI8RxWm}Azez5{?bWa-?a3zQu?DGa_I`Q^yX~oc$W0-X6c4( z>DwXQ>7XY)i1;Y$bZj>$-p@N7be3$i%ZPa8p7>4I#w*(`zg~7;BEGW=J8j(BhWtr1 z(0<-D8Di%q;$1@Q=wTt&g@X872S!B2zY8Bb%xCo=bk0k}4s)TUQfRB8YCS-vZ<4v&jNDN?cf8D{T{?+rXg{pRk@v#{2;6HIXY~>;5D`W?-G@p<}I^2=j!b=`_Q;r@dO-A zMz`wUGGOAF-`K)4x9N8qneoa^!qlui@h@K+pVe-8F)?csrn`lD16f+NnMH0k7VXuY ztQsfBv^Gcb?hA3mTl&uJmg-K&!Tn#y@9*!vYp>(bq`i(mc=tNqyl}7MEw}F=@lL_f^1e*X9|Tk9{#1XDOs%<^Oa;nd>bo#?2~34x3TC_SgQ=gx)E1cf z92VY=1I8M>;2hR_mfd%D#w^>{J@FNQy{6;YFZMBa?%=Z)=FZJ={HM8dGj;7Pas0=m zIWV#Q3A7hYnw$02Z}T?8Xo#)aN}DGd#Ye8ggb2vd0CFZAX2_Xl!iX$|dQl?w40VBI zbemqr^Dz_BmRHP#zm&`?C6o5Sf+^^+BW)BnsT512i}9QvGb(32j8Zp>V~ z=IC)-__VYc)6&_A_+c^Me5IR15Yxx-Ib=Hyu#GtZsXPJ>vk8F9P`4%G$A$W2sXmA= zHTsaWVgtkHKa*s8pEidIN&PL+Dp(ryVQ`cX+a<;J(K?~sLi5x9*sAR;GCa2GITjfb zTh-1Y`LR`dSfqb!)jk#-!5i=ZAFh`h?iek8BW*Bxl{GLCA1}(Plx20HJE6rf>Wzu$ z~GDAP&qvkpN8B^wujm$mzw@sOsKg}iE^;T16jgh%ekBCgF#XzGL zd)ZMwL2_?rZQ&E-=JW(viTMLFSM2PET4?5ab}Y?Y-@O@!F894PI3-KDX6DbkPd z$tIuo)kk?VtoQ@(WcrSe>3xsrK>Y}w9%4N{fR}!BAs2m;i&hw-yjvfgB2NHlZ6~)P zXrtP7SjRf`T3!$u9QzVOd$6J1#_gugTe9~h^XIPVvBRW32jV#Y<8{I4YdQR{WKwQO z@F0;&vO+G=atsWoK0aNZ$O*h*jP%`xh^4EO-4@m&oh8<2y}Q1%q`$k~SJ+uH$m{nL zIMUGH_}I;FZP8&Dr6hk$B`1GOq`sRo^IBVaK*fz;^8=w}1E_uad?+f8HIHD+mV4VI zw9t!GXxno@c3x|T{x>R(PDT$?nGHje4K^IS<9t2YU`NbE-rlGAi^%-FY4i6B^H-(Jqm!q)ugzo+FADYH?hCkA=6)erb0666Ob(%zxI-{Rr8%rESj>wtrwe zSN^}yv^P=-PmW80vrLpwZYm*TrZdmbd5m77O22N&-*3$5qKg*l(^0~jy%i{eo&XJx z-(rnVikIfaKD9*bw9cEqs762U1P*D_54`D?xdUogk{+(n&qo5chodjW$D0eIV(I-y zy%9IB+@oXpE1HWICMplikM&%;EN5P#^5DEgTmEm*+R3)#5aJu`g0=UMy;%q1bMxXk zbke<&=z+zb6u(iNJbDp-|8vTQZ?L6!0D7y{@Ms^(w zvnGV=n3rj+nz>n&`B?*BCuZxzk+GfT|_-khU@M1D+r=Q-rb44CABq0;u zFe=}Mg=g6)u$=L=plxQ_`&|gJc*Ur^_<~W^Wl5y+k;;Ptj`N*!V|#{AIrrG^ebLcH zV;9@m=&L=8*tLMSKIjWX?4zTD7}p5|Lv?PyP!a@ZjP@F8g9I;4FQ}L} zVS=Hpg+js5NEQuOF`suKtOr9#I72~i)DvOT+<_}2c27ui!=WfD!T8T?EmGkNEVl=O z5xcv=?en{<{2Givz|IX8l0lbIY3BBNSxBo3HlTs#X*IrZL<^O1JZ1+YzG|Px9q|PN zVSDYOfOpF1#p4ID1+^&AOXk7WLiTDDp?M1#z*yl9^J1%m(SX$YUU0(%q^EQJ(cwYxR11w8TS)tt`KbRaEgW{&Xr*w~8*;B$0%D-LKT^g+|yu#a`4n zx@e+*@gVf%X+f`M4{L_q6zAHM1n-|lLxqik?CyxkfYGyi38W=}ihZU#;Pq>1^)y%1 z@^li6N$Vaom#8f{k+w3+7x1CCqD4i+O$|1dvKd-rdN8Ebgs7E86aFU{rmRX7sVc^> zG&){}55(jn?i4((IJPegq-LK?r#s@QMT1s_e4gd@s%T9H{AGT>y&~xL(zu{>Wxy{e zbi`XB;HupotxStxAT6RC}%LCyE z(ppGzV4pKkNVv4G%#;mBAX+C~sSZj}bFOeOEO{y4d-+qQg(IP0Q`%ew<`bzljZaj4 z^H5`h(aU7ER?2T00BK{X?94f=Jb)@m2U3Nc8c*pWByQHyIM3q~q){5$$8u4>8yj&S zEkydceaYkor1$?8FV7;r00*AO5&sSG7~ESw#bGJkXD`Gutr_to#G4Q=M%<1#jBSn; zJIu=uCX+J}7rdTKu0;GH;%$hp|9vuf81Z9>?bw>Xhj;mt3ul_KZY(u;X@qWZ_AzLPm)R7S&R*(0fNhN z$2?|PnP<5u-)db4yzR&vfne{1JaA=$^QI5Zzp3D?h_#V@@xmLg9y{tX9KEm-g!~ti z$yv~0W%wed+?JaYWz!1!MI-%f{d1x#ZT)hhjr}HAc7a*iQM$MESJO~N5yWYma538X zO~fYMsnBigPiiZ3o)=nNht_5Eu(K07)W&`5^!sa$SqWlP4;^9sOL%n%F)JOM*OD{! z?A%*^1y2lq9{j)mH2g8(fBh8v5}2F`ejE4~W#UkVy43f#4Imr z$MGKM*5FCuwtvUQ#@;pvQ+mnhWsH#?r2EDv=QT4tq+><<#$QP$-#N9;i`hD~-u*nr z+O$*Z1k5Uo`idVq<SXtooJB%YbQMNEZh`Jspj(!yYw9b(jPgk55Y_8H(4qci zL%;eKbRNiT15-Ku)=1=er-TmGjpmxaW2Zj@{mF!TcHR0FSu-m~x@)0p#k{i~>C@=u z+>xou%Wa4566ije>2I%cACyiDP-#q@gzj%}WPUZ3E5}5&F($~b5va>+;7`UpcN)L? zjn2(1E*@(9;tY6vKMN0Pw++y_1y79kB5f+y)CM_EnH!sQ_Ce<;bY4HT&K|Q4 z`AGA78|HCWrcO3?Iny(%$8o27!ZXmVI)iR`AG&lOc^$e7Pp`WwOO=+63Jt(ETiJbO^euq3b+- zxht}YkmrYE|}5c08pYC|5ro5h5lTFXpL-@a!V zw{yCQFCCQXHsT&$j^z|1+Y4alag*(9kynen&B%KNX*Nf!Kgz$_?`~@>H|MEAv@U`f zEJw=DV#6Ww5abu&MYRh^-Z40@bg*^SU^1>Qg@8gpA)pXY2q**;0tx|zfI>ha&=&&u zLq^-iSr{D+U55P*#bZVMMG==HmfH$?3xnBlb_}_bw_6y!va$bO3!|4bXrFW;LKkQ{ zJ0kkye9lWa%Y=aWs(g+fbqx0aS5W?#~LB2mi-hXo)K%L|@y~x5A zB2*(ZAl!}c0K(%4I}r9Fyo&HH!Y2s*i!E#z!W9S;5T+w6M5sn+K)4&>0ffg9b|CCU zcopGYgijFqkA*(M6$ld$rXwsws77c&xEtXCgvSwfAnZkW72#ckPZ0W}eYdw+nA5>n z1*-An@u9nAUAkG$$g?a1s5w$s8~qV1aoO!iP~#)WT)7`!M~x~vMn6^pHV973*dVFB z=xlZ;8hIVk{~MRA^8t8C^`O2*!4ATUumzzMAHp{;w6M1j9z^E7WQL=bCy_GJ;Hv3Lg4l`?0kxFF6#R^gqa8$!dDO;MA(Y33*nClClM|I zCf6g(LGU1~LckL#HV`8t52L0({y3Nw9uGh-&&Q~S1WF2f5i#aeF=5 zOsSsodE~$Uj8hbr@t{)VAv(pg)fA_<1F1Hp9$>?~%$8bM8A3Pj6O_x?K#Vz?9Dqhv z>bC^>vk`7Z@FT25xF6wB1Uh4~OrcV~=@3SU*o9crTf z90m`KHw&-f_%-7lic*qC9?JBH_c=t){3g6oQ&;8-p^RXti3I7*0Aa3_E!RQ;%`e_@ znK5nJ=j~%=%&xr3UOc{VY+9)`iweT~_u%lJ0|cyUZHI zpHJ~NjSulhD9CR~iI`<{| zAn2dpWMTB2jp}c{NdHan2aY%JApUQuJ}8gYPeez-F9Lp&J`OrkBk?O>0{|?o*XYW@ zedGtw-(%808+=-S(Pd%hu>$BblRf(D9>YyE-MfyP^e>0L4eJcqeoG)f$|OGtjINE+ z{xE+3(@pvg@M(==ls}C96!yDJ{CVL2MXXDxzO|scQ6E~{5&b!omv1VMzS3A@lHUlL zL7vXmN&cI#Pwge~+5$eUd+1!B_%vV2HJ=5m&8KOxX-B}7gL`l%_+uWh@N@Wcuu6TA z?2C)msBeHC0X}6D|AQ>Pjn+=$BKZ-ZgOe?c-aQzC@{6EEP5Jf zT1QoZNA@((w5FoxFht)0`mm||J3)^!;gtX_FS7qM=;7{k{r8yocJRARG|7JeI?p7Z zgXdlcP@aL``OF4dUL-#{i=LT9F9J>LLwdGJ{(l)XtpjEK;wE`JWWEQQ)`POX&w}2H z_QMv2x8EzEt4l3xny~klNuT`r7id~r(z7-ykKX&yO?X`b`d(9i`5b6kqe^+&v&oC> z)qy@{(hq_Dp-DfMC4V32B~!8ffG(B)jV%7wEP7`a{d^Wp&(h>Y{*1)4D|r!pa~ACb zP3vtZcvSw~pq~I<^j-+j_hrd%0$qafPwyBJ|9?QAFpZa=XUV^pMgKjE9&FkXk$;zh z9*X{nX@igFtF!o}plRzmntQFQ8AF>VJ~-#YO&JfLkK% zv*`ITm3O1*7D@c2pwBn;*X5wEFS9Uu*M{U*fu?<&)c*$P68J-x4fFH2LCcHm{V-V~he2>*6x>Awk@_O^09I}Umn z%9s5+3Hre=T38RtqVjXG>HH4xCCG>#Mw?J^U52>WM3aB>Krf$!`wQ;NB=5@NH-f(0 zY2o)-;xlY4_(t}+B5@Ul>oredkhxrVbzE~1<*L_QwRlM!@7$VmLz>^+&zfq%mp_!G~-pL7O3wmzrD*HwHb1dGoEVey$T zEIt#6#b-jX_)IVsXX03+MO^hBSEM!?Snkr?jljTHSMM_Id5w(Jw$~uDcD@;z#^%?^ z>TCaNa5DD5M%Jmf!3MwARydVy-VLWRdu@mfPR6F#+~TM=zMU|j2&aZ-p@&t>nO0ii zaLq2A<#3hHURXM>yma=0jNJLR%wOP`g>QPMmsVCRVDn0XT3X?nGkyAe#{$=a(y1u3FC$fCWWlUyF30RL7fE=- zL02vR!lEznFigAf*1l)C%Tv4DMPJdl{6P;s7V5)6-U0a31D7H$7^WjOGfJS1S%->@8=0UsV)ezVhyG47s9#kXBt%Q9iY@ z%(2i_JaL?~fR7IX^6MA$JJa_xsUqCIIv2iwGQMcwpBb&7&!t>I9+?4nT!Uh$+GtV2 zC_SA9B62#-@TS`5ho%dkDY@#X`CQ>>RhS>C&=*XRrh2U}SF1ysCcbcT`RN-3UVD)f zCKR+lL&*$Oa++&wVUesMp#!Tpcr>)?qbSiGG6{0uG<-6lh49stE9`Lxq}2>RfKSF} z9=%W4(h{bVMid^#=U85R+mz8ZR0nZWAs+);Vd~)+ol`=%A6-=dw8lU<^7y@M+ z{q#|(i>lFQC4{w%6VFEamdr){R5psNQE|F6vib=Bg3VPIjcASN?lekzyJ@tT{1}dZ zNaEG-osS#==?}uvEt%~-2Touq`c|(VOCQ!ro9S=L#E8-weG%?CZ(Qc}V;qEvQ_hYX zFMBRc3k)CM__aWaTV#yubZ_lr5M{KN*~)4DgVv!R?P` zjz_b}-V=*JuJ6Np*{4~32vbV6saiY(vqfXNQPUltmni2lEkGR}9mu2k8gST~Q44`_ z7!?TC)AxG>pvR4m?&{2gqfV>Ce^c?IK^uHt1K?BEYliI4y`Bw(H9wAnO*XmL>6Ri; z(_=ov>ss6isW&i8Js!=^PTl9nGXBM?D-v=C!qr%(;!9NYbyKPMG;NmK7dXual;Nz= zk~LPc-0pLZkZnPAs`h5oCHn@La&0{0-_>Eb$pOsoTo~ORx)spplJcvr)Z-!ySpLnH z(L7-n|0)sdg#hjx#%H5a1?`fWa~Fw^LFJuVbP94-w6j+WdqTc?+#01mx@{Ssr;Itm z{9zskB;9f@vJ-IEWeo~UxBR|1mdUSZUDe_nQdx%inN{ioJlq1j3^%243MetgCB{Za zm$$wQtoh={#e0jf5b9NA_R}U_7il>)9|W|BADH;=*y+^K9=4!F)@S)y7UybB8t4ddX-a0U(a- z!cBD%cNJn(g2%Nog>Iu2)&!!3RZ*YcdmWrEOl1Ll{*@@>0=4dNEi3dk1z^mGBO!y+ zV0=ECLAbz!UnFlN>irQ`$Zsx%xLG32*TjYR0^J>PvqG&FHwkoaSFIPCk}@=0?oi0x zWN64V?7HiG9+Vk`E&LSn0t{nS7}SNHAZANhR#pu zOav@0NAH|apm$B=MY41)L6Gl@JbEsklOoW0hb))Mp-_uJ@0U=T-Z7!jj5L)?c~X7_ z(sVvXdGxLd1$y^E@+l@anh{8DDgwRRLV@0SAwHcCOSx90$ya`~ZASuOv`u=Sw!0QA zDOXfvVHCQ_32_l7WRspLN;!H~L*dBPLI@Gb(X|ml%F#0;3aE1GB3pEQ3xVW#QA?3R z2vLKJ%P!)_5KB3FCPiT-Wr$1K`w?gek37g0wc8;C@{i;W;aS3@QkCgjAwSikb