mirror of
https://github.com/radiomanV/TL866.git
synced 2026-02-19 17:21:37 +01:00
Introduced Wine64 wrapper
This commit is contained in:
11
README.md
11
README.md
@@ -69,6 +69,11 @@ This utility can dump the `infoic.dll` and `infoic2plus.dll` to an XML format da
|
||||
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/wine32#)
|
||||
### 32-bit WineLib wrapper (Unmaintained)
|
||||
|
||||
This library provides an easy way to run Minipro/Xgpro software on Linux.
|
||||
More information: [WineLib wrapper](https://github.com/radiomanV/TL866/tree/master/wine32#)
|
||||
|
||||
**Note:** This 32-bit wrapper is no longer maintained.
|
||||
For the actively maintained wrapper that supports all architectures, see [new wine wrapper](https://github.com/radiomanV/TL866/tree/master/wine64#)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
>
|
||||
> This wrapper is no longer maintained. It targets 32-bit Wine environments and is provided *as-is* for users
|
||||
> on systems/Wine versions that still support 32-bit.
|
||||
> For the actively maintained replacement, see the new wrapper [here](https://github.com/radiomanV/TL866/raw/refs/heads/master/wine64)
|
||||
> For the actively maintained replacement, see the new wrapper [here](https://github.com/radiomanV/TL866/raw/refs/heads/master/wine64#)
|
||||
|
||||
# Simple low level winelib usb wrapper for the Minipro TL866A/CS, TL866II+, Xgecu T48, T56 and T76 programmers.
|
||||
|
||||
|
||||
77
wine64/Makefile
Normal file
77
wine64/Makefile
Normal file
@@ -0,0 +1,77 @@
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -O2 -Wall -Wextra -Wno-unused-parameter
|
||||
LDFLAGS ?= -lpthread
|
||||
WINCC ?= i686-w64-mingw32-gcc
|
||||
WINRC ?= i686-w64-mingw32-windres
|
||||
|
||||
PKG_CONFIG := $(shell which pkg-config 2>/dev/null)
|
||||
|
||||
# LibUSB detection
|
||||
ifeq ($(strip $(PKG_CONFIG)),)
|
||||
LIBUSB_CFLAGS :=
|
||||
LIBUSB_LIBS := -lusb-1.0
|
||||
else
|
||||
LIBUSB_CFLAGS := $(shell $(PKG_CONFIG) --cflags libusb-1.0 2>/dev/null)
|
||||
LIBUSB_LIBS := $(shell $(PKG_CONFIG) --libs libusb-1.0 2>/dev/null)
|
||||
ifeq ($(strip $(LIBUSB_LIBS)),)
|
||||
LIBUSB_LIBS := -lusb-1.0
|
||||
endif
|
||||
endif
|
||||
|
||||
# usb-broker
|
||||
BIN := usb-broker
|
||||
SRC := broker.c
|
||||
HDR := protocol.h
|
||||
|
||||
RES_RC := resource.rc
|
||||
RES_RES := resource.res
|
||||
|
||||
|
||||
# shim.dll
|
||||
DLL := shim.dll
|
||||
DLL_SRCS := shim.c
|
||||
DLL_OBJS := $(DLL_SRCS:.c=.o)
|
||||
DLL_CFLAGS := -O2 -s -DWIN32 -D_WIN32_WINNT=0x0601 -Wall -Wextra -Wno-unused-parameter
|
||||
DLL_LDFLAGS := -shared -Wl,--kill-at -Wl,--enable-stdcall-fixup
|
||||
DLL_LIBS := -lws2_32 -lshlwapi -lgdi32 -luxtheme
|
||||
|
||||
# launcher.exe
|
||||
LAUNCHER := launcher.exe
|
||||
LAUNCHER_SRCS := launcher.c
|
||||
LAUNCHER_OBJS := $(LAUNCHER_SRCS:.c=.o)
|
||||
LAUNCHER_CFLAGS := -O2 -s -DWIN32 -D_WIN32_WINNT=0x0601 -Wall -Wextra
|
||||
LAUNCHER_LDFLAGS:= -municode -mconsole
|
||||
LAUNCHER_LIBS := -lshlwapi
|
||||
|
||||
|
||||
.PHONY: all broker shim launcher windows clean
|
||||
|
||||
all: broker shim launcher
|
||||
|
||||
broker: $(BIN)
|
||||
|
||||
$(BIN): $(SRC) $(HDR)
|
||||
$(CC) $(CFLAGS) $(LIBUSB_CFLAGS) -o $@ $(SRC) $(LDFLAGS) $(LIBUSB_LIBS)
|
||||
|
||||
windows: shim launcher
|
||||
|
||||
shim: $(DLL)
|
||||
launcher: $(LAUNCHER)
|
||||
|
||||
$(DLL): $(DLL_OBJS) $(RES_RES)
|
||||
$(WINCC) $(DLL_LDFLAGS) -o $@ $(DLL_OBJS) $(RES_RES) $(DLL_LIBS)
|
||||
|
||||
$(LAUNCHER): $(LAUNCHER_OBJS)
|
||||
$(WINCC) $(LAUNCHER_LDFLAGS) -o $@ $(LAUNCHER_OBJS) $(LAUNCHER_LIBS)
|
||||
|
||||
%.o: %.c
|
||||
$(WINCC) $(WCCFLAGS) -c $< -o $@
|
||||
|
||||
shim.o: WCCFLAGS := $(DLL_CFLAGS)
|
||||
launcher.o: WCCFLAGS := $(LAUNCHER_CFLAGS)
|
||||
|
||||
$(RES_RES): $(RES_RC) resource.h
|
||||
$(WINRC) -i $(RES_RC) -O coff -o $(RES_RES)
|
||||
|
||||
clean:
|
||||
rm -f $(BIN) *.o $(DLL) $(LAUNCHER) $(RES_RES)
|
||||
220
wine64/README.md
Normal file
220
wine64/README.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Xgpro/Minipro Cross-Architecture USB Wrapper (Broker + Shim)
|
||||
|
||||
**What is this?**
|
||||
A modern, cross-architecture replacement for the legacy 32-bit [WineLib wrapper](https://github.com/radiomanV/TL866/tree/master/wine32#).
|
||||
It splits the old `setupapi.dll` logic into:
|
||||
|
||||
- **A native USB broker** (Linux, 64-bit or 32-bit, any arch) that talks to LibUSB.
|
||||
|
||||
- **A native Windows shim DLL** that runs inside the Windows app (Xgpro/Minipro) and proxies USB calls to the broker.
|
||||
|
||||
**Why?**
|
||||
|
||||
- Works on **x86_64, aarch64, armhf**, etc.
|
||||
|
||||
- No 32-bit Wine and 32-bit libs requirement.
|
||||
|
||||
- Avoids SSE/stack alignment pitfalls from the legacy approach.
|
||||
|
||||
- Cleaner debugging and isolation.
|
||||
|
||||
---
|
||||
|
||||
## Supported programmers
|
||||
|
||||
- **Minipro TL866A/CS**
|
||||
- **XGecu TL866II+**
|
||||
- **XGecu T48 / T56 / T76**
|
||||
|
||||
## Features
|
||||
|
||||
- **Cross-arch**: broker builds natively for your host (x86_64, arm64, armhf, etc.)
|
||||
- **Broker/Shim split**: minimal 32-Bit native Windows DLL
|
||||
- **Socket protocol**: shim to broker IPC over local TCP socket
|
||||
- **Hotplug**: libusb hotplug events
|
||||
- **Verbose tracing**: single env var to dump full TX/RX
|
||||
- **Drop-in**: works with stock `Xgpro.exe` / `Xgpro_T76.exe` / `Minipro.exe`
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### Host (Linux)
|
||||
|
||||
- `libusb-1.0` (runtime; `-dev` for building broker)
|
||||
|
||||
- `wine` or `wine64` (only runtime, no `-dev` packages needed)
|
||||
|
||||
- Build tools: `gcc`, `i686-w64-mingw32-gcc`, `make`, `pkg-config`
|
||||
|
||||
```bash
|
||||
# Debian / Ubuntu / Mint
|
||||
sudo apt install -y wine winetricks libusb-1.0-0
|
||||
sudo apt install -y build-essential pkg-config libusb-1.0-0-dev \
|
||||
mingw-w64 gcc-mingw-w64-i686
|
||||
|
||||
# Arch / Manjaro
|
||||
sudo pacman -S --needed wine winetricks libusb
|
||||
sudo pacman -S --needed gcc make pkgconf mingw-w64-gcc
|
||||
|
||||
# Fedora / RHEL
|
||||
sudo dnf install -y wine winetricks libusb1
|
||||
sudo dnf install -y gcc make pkgconfig libusb1-devel \
|
||||
mingw32-gcc mingw32-binutils mingw32-headers mingw32-crt
|
||||
|
||||
# openSUSE
|
||||
sudo zypper in -y wine winetricks libusb-1_0-0
|
||||
sudo zypper in -y gcc make pkg-config libusb-1_0-devel mingw32-gcc
|
||||
```
|
||||
|
||||
### Wine / App
|
||||
|
||||
- App: `Xgpro.exe`, `Xgpro_T76.exe`, or `Minipro.exe`
|
||||
- The **shim DLL** must be placed alongside the app (see install)
|
||||
|
||||
---
|
||||
|
||||
## Install
|
||||
|
||||
### 1) Clone git repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/radiomanV/TL866.git && cd TL866
|
||||
```
|
||||
|
||||
### 2) Udev rules (recommended)
|
||||
|
||||
```bash
|
||||
sudo cp ./udev/* /etc/udev/rules.d/
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
```
|
||||
|
||||
This grants non-root access to the programmers.
|
||||
|
||||
### 3) Build the broker (native)
|
||||
|
||||
```bash
|
||||
cd wine64
|
||||
make broker # produces usb-broker
|
||||
```
|
||||
|
||||
### 4) Build the Windows component
|
||||
|
||||
```bash
|
||||
make windows # produces launcher.exe and shim.dll
|
||||
```
|
||||
|
||||
#### A simple make will compile all three components above.
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
You can use the provided `shim.dll`, `launcher.exe`, and `usb-broker`.
|
||||
`shim.dll` and `launcher.exe` do not need recompilation (they’re prebuilt natively), but
|
||||
`usb-broker` may need to be rebuilt depending on your Linux distribution or architecture.
|
||||
|
||||
### 5) Place the shim, launcher and usb-broker next to the app
|
||||
|
||||
Copy the resulting files `usb-broker` `shim.dll` `launcher.exe` and (optionally) `run.sh`to the application directory.
|
||||
Mark `usb-broker` and `run.sh` as executable:
|
||||
|
||||
```bash
|
||||
chmod +x usb-broker run.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
```
|
||||
# Minimal, without any script:
|
||||
cd /path/to/app/
|
||||
./usb-broker --quiet & wine launcher.exe 2>/dev/null
|
||||
|
||||
# Or you can use the provided run.sh script, which can be customized via
|
||||
# explicit environment variables:
|
||||
BROKER_PORT=35866
|
||||
APP_PATH="/path/to/launcher.exe"
|
||||
TARGET_EXE="Xgpro.exe"
|
||||
SHIM_DLL="shim.dll"
|
||||
USB_BROKER_PATH="/path/to/xgecu-usb-broker"
|
||||
WINECMD="wine64"
|
||||
|
||||
# This will start the broker (if needed) on the default port 35866 and
|
||||
# run the launcher with the default Xgpro.exe name.
|
||||
run.sh
|
||||
|
||||
# This will overide the default Xgpro.exe name:
|
||||
TARGET_EXE=Minipro.exe ./run.sh
|
||||
TARGET_EXE=Xgpro_T76.exe ./run.sh
|
||||
|
||||
# This will overide the default IPC port:
|
||||
BROKER_PORT=35000 ./run.sh
|
||||
```
|
||||
|
||||
- The script will:
|
||||
- start the **broker** (if not running),
|
||||
- launch Wine with the shimmed app,
|
||||
- wire them together over the configured socket.
|
||||
|
||||
Works without a 32-bit prefix; you can use `~/.wine64`, default prefix or any Wine prefix you prefer.
|
||||
|
||||
The USB broker can also be run standalone for debugging. It accepts the following arguments:
|
||||
|
||||
```
|
||||
--port <N> (listens on port N)
|
||||
--quiet (reduced logging)
|
||||
--no-exit (do not exit when there are no clients)
|
||||
```
|
||||
|
||||
The USB broker supports multiple concurrent connections and can handle up to 64 USB devices.
|
||||
Unless `--no-exit` is specified, the USB broker will automatically exit after a timeout.
|
||||
|
||||
---
|
||||
|
||||
## Configuration (env vars)
|
||||
|
||||
These are read by `run.sh` and/or the shim:
|
||||
|
||||
- `BROKER_PORT`: TCP port for shim-broker IPC (default: `35866`)
|
||||
- `APP_PATH`: path to the Windows app launcher (optional)
|
||||
- `TARGET_EXE`: the Windows binary to run (e.g. `Xgpro.exe`)
|
||||
- `SHIM_DLL`: shim file name in the app folder (e.g. `shim.dll`)
|
||||
- `USB_BROKER_PATH`: full path to the broker binary
|
||||
- `WINECMD`: `wine` or `wine64` (default: `wine`)
|
||||
- `TL_DEBUG`: set to `1` for verbose USB/protocol logs
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Device not found / permission denied**
|
||||
|
||||
- Recheck udev rules, unplug/replug, verify you’re in the correct groups (e.g., `plugdev` on some distros).
|
||||
|
||||
- **Shim cannot find broker**
|
||||
|
||||
- Verify `BROKER_PORT` and that broker is running; try `USB_BROKER_PATH` absolute path.
|
||||
|
||||
- **Socket conflicts**
|
||||
|
||||
- Change `BROKER_PORT`.
|
||||
|
||||
- **Black toolbar when theme is active under Wine**
|
||||
|
||||
- Install native comctl32 v5.80 or disable the Wine theme.
|
||||
|
||||
```bash
|
||||
winetricks comctl32 && wineboot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security & privacy
|
||||
|
||||
- The broker runs locally and exposes a local port (loopback only).
|
||||
- No network access beyond the local IPC channel.
|
||||
|
||||
---
|
||||
662
wine64/broker.c
Normal file
662
wine64/broker.c
Normal file
@@ -0,0 +1,662 @@
|
||||
/*
|
||||
* broker.c
|
||||
* Minimal USB broker for shim.dll.
|
||||
* The USB broker will redirect all RPC calls from the shim to the
|
||||
* Linux USB subsystem using LibUSB calls.
|
||||
* Created: September 10, 2025
|
||||
* Author: radiomanV
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <libusb-1.0/libusb.h>
|
||||
|
||||
#include "protocol.h"
|
||||
|
||||
typedef struct {
|
||||
uint32_t id;
|
||||
libusb_device_handle *h;
|
||||
uint8_t ifnum;
|
||||
char path[128];
|
||||
} handle_entry_t;
|
||||
|
||||
typedef struct {
|
||||
int sock;
|
||||
int running;
|
||||
libusb_hotplug_callback_handle cb;
|
||||
} hotplug_sub_t;
|
||||
|
||||
static handle_entry_t libusb_map[64];
|
||||
static pthread_mutex_t map_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t id_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int map_idx = 0;
|
||||
static uint32_t map_next_id = 1;
|
||||
static volatile int num_clients = 0;
|
||||
static volatile int b_verbose = 1;
|
||||
static volatile int b_sigint = 0;
|
||||
|
||||
#define LOG(fmt, ...) \
|
||||
do { \
|
||||
if (b_verbose) \
|
||||
fprintf(stderr, "[broker] " fmt "\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define ERR(fmt, ...) fprintf(stderr, "[broker:ERR] " fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
// Socket buffer send function
|
||||
static int socket_send(int sock, const void *buf, int len)
|
||||
{
|
||||
const char *pbuf = (const char *)buf;
|
||||
int done = 0;
|
||||
while (done < len) {
|
||||
int n = send(sock, pbuf + done, len - done, 0);
|
||||
if (n <= 0)
|
||||
return RPC_E_SEND_ERR;
|
||||
done += n;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Socket buffer receive function
|
||||
static int socket_recv(int sock, void *buf, int len)
|
||||
{
|
||||
char *p = (char *)buf;
|
||||
int rcvd = 0;
|
||||
while (rcvd < len) {
|
||||
int n = recv(sock, p + rcvd, len - rcvd, 0);
|
||||
if (n <= 0)
|
||||
return RPC_E_RECV_ERR;
|
||||
rcvd += n;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Socket reply function
|
||||
static int send_reply(int s, uint16_t type, const void *payload, uint32_t plen)
|
||||
{
|
||||
rpc_hdr_t h = { .len = plen, .type = type, .version = PROTO_VERSION };
|
||||
if (socket_send(s, &h, sizeof(h)))
|
||||
return RPC_E_SEND_ERR;
|
||||
if (plen && socket_send(s, payload, (int)plen))
|
||||
return RPC_E_SEND_ERR;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// Helpers to store/read/delete a handle_entry
|
||||
static int map_put(libusb_device_handle *h, uint8_t ifnum, uint32_t *out_id,
|
||||
const char *path)
|
||||
{
|
||||
pthread_mutex_lock(&map_mutex);
|
||||
if (map_idx >= (int)(sizeof(libusb_map) / sizeof(libusb_map[0]))) {
|
||||
pthread_mutex_unlock(&map_mutex);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
pthread_mutex_lock(&id_mutex);
|
||||
id = map_next_id++;
|
||||
if (id == 0)
|
||||
id = map_next_id++;
|
||||
pthread_mutex_unlock(&id_mutex);
|
||||
|
||||
libusb_map[map_idx] =
|
||||
(handle_entry_t){ .id = id, .h = h, .ifnum = ifnum };
|
||||
strncpy(libusb_map[map_idx].path, path, sizeof(libusb_map[0].path));
|
||||
map_idx++;
|
||||
|
||||
pthread_mutex_unlock(&map_mutex);
|
||||
*out_id = id;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static handle_entry_t *map_get(uint32_t id)
|
||||
{
|
||||
for (int i = 0; i < map_idx; i++) {
|
||||
if (libusb_map[i].id == id)
|
||||
return &libusb_map[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int map_del(uint32_t id)
|
||||
{
|
||||
pthread_mutex_lock(&map_mutex);
|
||||
for (int i = 0; i < map_idx; i++) {
|
||||
if (libusb_map[i].id == id) {
|
||||
libusb_map[i] = libusb_map[map_idx - 1];
|
||||
map_idx--;
|
||||
pthread_mutex_unlock(&map_mutex);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&map_mutex);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// LibUSB enumerate helper
|
||||
static int usb_enum(dev_info_t **out_list, uint32_t *out_cnt)
|
||||
{
|
||||
libusb_device **list = NULL;
|
||||
ssize_t n = libusb_get_device_list(NULL, &list);
|
||||
if (n < 0)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
uint32_t cap = (uint32_t)(n > 512 ? 512 : n);
|
||||
dev_info_t *arr = (dev_info_t *)calloc(cap, sizeof(dev_info_t));
|
||||
if (!arr) {
|
||||
libusb_free_device_list(list, 1);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t cnt = 0;
|
||||
for (ssize_t i = 0; i < n && cnt < cap; i++) {
|
||||
libusb_device *dev = list[i];
|
||||
struct libusb_device_descriptor dd;
|
||||
if (libusb_get_device_descriptor(dev, &dd))
|
||||
continue;
|
||||
dev_info_t di = { 0 };
|
||||
di.vid = dd.idVendor;
|
||||
di.pid = dd.idProduct;
|
||||
di.bus = libusb_get_bus_number(dev);
|
||||
di.addr = libusb_get_device_address(dev);
|
||||
di.ifnum = 0;
|
||||
snprintf(di.path, sizeof(di.path), "usb:%u-%u", di.bus,
|
||||
di.addr);
|
||||
struct libusb_config_descriptor *cfg = NULL;
|
||||
if (libusb_get_active_config_descriptor(dev, &cfg)) {
|
||||
di.has_eps = 0;
|
||||
memset(di.wMaxPacketSize, 0, sizeof(di.wMaxPacketSize));
|
||||
} else {
|
||||
di.has_eps = 1;
|
||||
memset(di.wMaxPacketSize, 0, sizeof(di.wMaxPacketSize));
|
||||
for (int i = 0; i < cfg->bNumInterfaces; i++) {
|
||||
const struct libusb_interface *it =
|
||||
&cfg->interface[i];
|
||||
for (int a = 0; a < it->num_altsetting; a++) {
|
||||
const struct libusb_interface_descriptor
|
||||
*id = &it->altsetting[a];
|
||||
for (int k = 0; k < id->bNumEndpoints;
|
||||
k++) {
|
||||
const struct libusb_endpoint_descriptor
|
||||
*ep = &id->endpoint[k];
|
||||
uint8_t num =
|
||||
ep->bEndpointAddress &
|
||||
0x0F;
|
||||
if (num < 16)
|
||||
di.wMaxPacketSize[num] =
|
||||
ep->wMaxPacketSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
libusb_free_config_descriptor(cfg);
|
||||
|
||||
arr[cnt++] = di;
|
||||
}
|
||||
libusb_free_device_list(list, 1);
|
||||
*out_list = arr;
|
||||
*out_cnt = cnt;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// LibUSB open_device RPC helper
|
||||
static libusb_device_handle *open_device(const char *path, open_resp_t *resp)
|
||||
{
|
||||
if (resp) {
|
||||
resp->status = RPC_E_OPEN_ERR;
|
||||
resp->product[0] = '\0';
|
||||
resp->speed = LIBUSB_SPEED_UNKNOWN;
|
||||
resp->handle_id = 0;
|
||||
}
|
||||
|
||||
int wa = 0, wb = 0;
|
||||
if (!path)
|
||||
return NULL;
|
||||
if (strcmp(path, "usb:auto-first") == 0) {
|
||||
wa = -1, wb = -1;
|
||||
} else if (sscanf(path, "usb:%d-%d", &wb, &wa) != 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
libusb_device **list = NULL;
|
||||
ssize_t n = libusb_get_device_list(NULL, &list);
|
||||
if (n < 0)
|
||||
return NULL;
|
||||
|
||||
libusb_device_handle *hnd = NULL;
|
||||
|
||||
for (ssize_t i = 0; i < n; i++) {
|
||||
libusb_device *d = list[i];
|
||||
if ((wb == -1 && wa == -1) ||
|
||||
(libusb_get_bus_number(d) == wb &&
|
||||
libusb_get_device_address(d) == wa)) {
|
||||
if (!libusb_open(d, &hnd)) {
|
||||
if (resp) {
|
||||
libusb_device *dev =
|
||||
libusb_get_device(hnd);
|
||||
if (dev) {
|
||||
resp->speed =
|
||||
libusb_get_device_speed(
|
||||
dev);
|
||||
|
||||
struct libusb_device_descriptor
|
||||
dd;
|
||||
if (libusb_get_device_descriptor(
|
||||
dev, &dd) ==
|
||||
LIBUSB_SUCCESS &&
|
||||
dd.iProduct) {
|
||||
unsigned char tmp[sizeof(
|
||||
resp->product)];
|
||||
int m = libusb_get_string_descriptor_ascii(
|
||||
hnd,
|
||||
dd.iProduct,
|
||||
tmp,
|
||||
sizeof(tmp) -
|
||||
1);
|
||||
if (m > 0) {
|
||||
tmp[m] = 0;
|
||||
strncpy(resp->product,
|
||||
(const char
|
||||
*)
|
||||
tmp,
|
||||
sizeof(resp->product) -
|
||||
1);
|
||||
resp->product
|
||||
[sizeof(resp->product) -
|
||||
1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint32_t id = 0;
|
||||
if (map_put(hnd, 0, &id, path) == 0) {
|
||||
if (resp) {
|
||||
resp->handle_id = id;
|
||||
}
|
||||
int cfg = 0;
|
||||
if (libusb_get_configuration(hnd,
|
||||
&cfg) ==
|
||||
LIBUSB_SUCCESS &&
|
||||
cfg == 0) {
|
||||
libusb_set_configuration(hnd,
|
||||
1);
|
||||
}
|
||||
if (libusb_claim_interface(hnd, 0) ==
|
||||
LIBUSB_SUCCESS)
|
||||
resp->status = 0;
|
||||
else
|
||||
resp->status = RPC_E_OPEN_BUSY;
|
||||
break;
|
||||
} else {
|
||||
libusb_close(hnd);
|
||||
hnd = NULL;
|
||||
}
|
||||
}
|
||||
if (wb != -1 || wa != -1)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(list, 1);
|
||||
return hnd;
|
||||
}
|
||||
|
||||
/* ------------ Client hotplug subscription socket notifier -------------*/
|
||||
static int LIBUSB_CALL on_hotplug(libusb_context *ctx, libusb_device *dev,
|
||||
libusb_hotplug_event event, void *user)
|
||||
{
|
||||
hotplug_sub_t *sub = (hotplug_sub_t *)user;
|
||||
if (!sub || sub->sock < 0)
|
||||
return 0;
|
||||
|
||||
struct libusb_device_descriptor desc;
|
||||
if (libusb_get_device_descriptor(dev, &desc))
|
||||
return 0;
|
||||
|
||||
hotplug_evt_t evt = { 0 };
|
||||
evt.arrived = (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) ? 1 : 0;
|
||||
evt.vid = desc.idVendor;
|
||||
evt.pid = desc.idProduct;
|
||||
evt.bus = libusb_get_bus_number(dev);
|
||||
evt.addr = libusb_get_device_address(dev);
|
||||
|
||||
// Send reply to client
|
||||
if (send_reply(sub->sock, MSG_HOTPLUG_EVT, &evt, sizeof(evt)) != 0) {
|
||||
sub->running = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// LibUSB hotplug thread loop
|
||||
static void *hotplug_loop(void *arg)
|
||||
{
|
||||
hotplug_sub_t *sub = (hotplug_sub_t *)arg;
|
||||
struct timeval tv = { .tv_sec = 0, .tv_usec = 250000 };
|
||||
while (sub->running) {
|
||||
int rc =
|
||||
libusb_handle_events_timeout_completed(NULL, &tv, NULL);
|
||||
if (rc != 0)
|
||||
usleep(1000);
|
||||
char tmp;
|
||||
int n = recv(sub->sock, &tmp, 1, MSG_PEEK | MSG_DONTWAIT);
|
||||
if (n == 0) {
|
||||
sub->running = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---------- Client worker thread ---------------*/
|
||||
static void *client_worker(void *arg)
|
||||
{
|
||||
//LOG("client connected (fd=%d)", sock);
|
||||
|
||||
int sock = (int)(intptr_t)arg;
|
||||
for (;;) {
|
||||
rpc_hdr_t hdr;
|
||||
if (socket_recv(sock, &hdr, sizeof(hdr)))
|
||||
break;
|
||||
if (hdr.version != PROTO_VERSION) {
|
||||
ERR("version mismatch: %u", hdr.version);
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t *request = NULL;
|
||||
if (hdr.len) {
|
||||
request = (uint8_t *)malloc(hdr.len);
|
||||
if (!request)
|
||||
break;
|
||||
if (socket_recv(sock, request, (int)hdr.len)) {
|
||||
free(request);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t buf[1 << 16];
|
||||
uint32_t len = 0;
|
||||
switch (hdr.type) {
|
||||
|
||||
// Enumerate USB devices RPC
|
||||
case MSG_ENUMERATE: {
|
||||
dev_info_t *list = NULL;
|
||||
uint32_t cnt = 0;
|
||||
enum_resp_t *resp = NULL;
|
||||
int rc = usb_enum(&list, &cnt);
|
||||
LOG("Enumerate: %u USB devices found.", cnt);
|
||||
if (rc == 0) {
|
||||
len = sizeof(enum_resp_t) +
|
||||
cnt * sizeof(dev_info_t);
|
||||
resp = (enum_resp_t *)malloc(len);
|
||||
if (!resp) {
|
||||
if (list)
|
||||
free(list);
|
||||
break;
|
||||
}
|
||||
resp->status = 0;
|
||||
resp->count = cnt;
|
||||
memcpy((uint8_t *)resp + sizeof(*resp), list,
|
||||
cnt * sizeof(dev_info_t));
|
||||
free(list);
|
||||
send_reply(sock, MSG_ENUMERATE, resp, len);
|
||||
free(resp);
|
||||
resp = NULL;
|
||||
request = NULL;
|
||||
continue;
|
||||
} else {
|
||||
enum_resp_t b = { .status = -1, .count = 0 };
|
||||
send_reply(sock, MSG_ENUMERATE, &b, sizeof(b));
|
||||
if (request)
|
||||
free(request);
|
||||
continue;
|
||||
}
|
||||
} break;
|
||||
|
||||
// Open device RPC
|
||||
case MSG_OPEN: {
|
||||
const open_req_t *rq = (const open_req_t *)request;
|
||||
open_resp_t resp;
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
resp.handle_id = 0;
|
||||
resp.speed = 0;
|
||||
resp.product[0] = '\0';
|
||||
open_device(rq->path, &resp);
|
||||
if (!resp.status) {
|
||||
LOG("Open device: %s", rq->path);
|
||||
}
|
||||
send_reply(sock, MSG_OPEN, &resp, sizeof(resp));
|
||||
} break;
|
||||
|
||||
// Close device RPC
|
||||
case MSG_CLOSE: {
|
||||
const close_req_t *rq = (const close_req_t *)request;
|
||||
close_resp_t resp = { .status = -1 };
|
||||
handle_entry_t *ent = map_get(rq->handle_id);
|
||||
if (ent && ent->h) {
|
||||
LOG("Close device: %s", ent->path);
|
||||
libusb_release_interface(ent->h, ent->ifnum);
|
||||
libusb_close(ent->h);
|
||||
map_del(rq->handle_id);
|
||||
resp.status = 0;
|
||||
}
|
||||
send_reply(sock, MSG_CLOSE, &resp, sizeof(resp));
|
||||
} break;
|
||||
|
||||
// Bulk transfer RPC
|
||||
case MSG_BULK: {
|
||||
const bulk_req_t *rq = (const bulk_req_t *)request;
|
||||
handle_entry_t *ent = map_get(rq->handle_id);
|
||||
LOG("%s %s Bulk transfer: %u bytes", ent->path,
|
||||
rq->ep & 0x80 ? "IN" : "OUT", rq->len);
|
||||
|
||||
int ep_in = ((rq->ep & 0x80) != 0);
|
||||
int rc = -1, xfer = 0;
|
||||
uint8_t *payload = NULL;
|
||||
size_t pay_len = 0;
|
||||
size_t hdr_len = sizeof(*rq);
|
||||
|
||||
if (!ep_in) {
|
||||
payload = request + hdr_len;
|
||||
pay_len = (hdr.len > hdr_len) ?
|
||||
(hdr.len - hdr_len) :
|
||||
0;
|
||||
} else {
|
||||
payload = buf + sizeof(bulk_resp_t);
|
||||
pay_len = rq->len;
|
||||
}
|
||||
|
||||
if (ent && ent->h) {
|
||||
if (hdr.type == MSG_BULK) {
|
||||
rc = libusb_bulk_transfer(
|
||||
ent->h, rq->ep, payload,
|
||||
(int)pay_len, &xfer,
|
||||
rq->timeout_ms);
|
||||
} else {
|
||||
rc = libusb_interrupt_transfer(
|
||||
ent->h, rq->ep, payload,
|
||||
(int)pay_len, &xfer,
|
||||
rq->timeout_ms);
|
||||
}
|
||||
}
|
||||
|
||||
bulk_resp_t *resp = (bulk_resp_t *)buf;
|
||||
resp->status = rc;
|
||||
resp->rx_len = (ep_in && rc == 0) ? (uint32_t)xfer : 0;
|
||||
len = sizeof(*resp) + resp->rx_len;
|
||||
send_reply(sock, hdr.type, buf, len);
|
||||
} break;
|
||||
|
||||
// Cancel transfer RPC
|
||||
case MSG_CANCEL: {
|
||||
LOG("Cancel transfer");
|
||||
cancel_resp_t resp = { .status = 0 };
|
||||
send_reply(sock, MSG_CANCEL, &resp, sizeof(resp));
|
||||
} break;
|
||||
|
||||
// Hotplug subscribe RPC
|
||||
case MSG_HOTPLUG_SUB: {
|
||||
LOG("Hotplug subcribe");
|
||||
hotplug_sub_t sub = { .sock = sock, .running = 1 };
|
||||
int rc = libusb_hotplug_register_callback(
|
||||
NULL,
|
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
|
||||
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT,
|
||||
0, LIBUSB_HOTPLUG_MATCH_ANY,
|
||||
LIBUSB_HOTPLUG_MATCH_ANY,
|
||||
LIBUSB_HOTPLUG_MATCH_ANY, on_hotplug, &sub,
|
||||
&sub.cb);
|
||||
if (rc != LIBUSB_SUCCESS) {
|
||||
ERR("hotplug not available: %s",
|
||||
libusb_error_name(rc));
|
||||
} else {
|
||||
pthread_t th;
|
||||
// GCC atomic increment builtin
|
||||
__sync_add_and_fetch(&num_clients, 1);
|
||||
pthread_create(&th, NULL, hotplug_loop, &sub);
|
||||
pthread_detach(th);
|
||||
while (sub.running) {
|
||||
char tmp;
|
||||
int n = recv(sock, &tmp, 1,
|
||||
MSG_PEEK | MSG_DONTWAIT);
|
||||
if (n == 0) {
|
||||
sub.running = 0;
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
libusb_hotplug_deregister_callback(NULL, sub.cb);
|
||||
if (request)
|
||||
free(request);
|
||||
// GCC atomic decrement builtin
|
||||
__sync_sub_and_fetch(&num_clients, 1);
|
||||
goto done;
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
ERR("unknown msg type: 0x%04x", hdr.type);
|
||||
break;
|
||||
}
|
||||
|
||||
if (request)
|
||||
free(request);
|
||||
}
|
||||
|
||||
done:
|
||||
close(sock);
|
||||
//LOG("client disconnected (fd=%d)", sock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// SIGINT handler
|
||||
static void sigint_handler(int sig)
|
||||
{
|
||||
LOG("SIGINT");
|
||||
b_sigint = 1;
|
||||
}
|
||||
|
||||
/*-------------Main loop------------------------------*/
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int port = RPC_PORT_DEFAULT;
|
||||
int no_exit = 0;
|
||||
|
||||
// Parse arguments
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--port") && i + 1 < argc) {
|
||||
port = atoi(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "--quiet")) {
|
||||
b_verbose = 0;
|
||||
} else if (!strcmp(argv[i], "--no-exit")) {
|
||||
no_exit = 1;
|
||||
} else if (!strcmp(argv[i], "--help")) {
|
||||
fprintf(stderr, "usb-broker options:\n");
|
||||
fprintf(stderr, " --port N listen port (default %d)\n",
|
||||
RPC_PORT_DEFAULT);
|
||||
fprintf(stderr,
|
||||
" --no-exit don't exit when no clients\n");
|
||||
fprintf(stderr, " --quiet less logs\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize LibUSB
|
||||
if (libusb_init(NULL) != 0) {
|
||||
ERR("libusb_init failed");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Initialize SIGINT handler
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
// Initialize broker server
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (sock < 0) {
|
||||
perror("socket");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
int on = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
perror("bind");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (listen(sock, 32) < 0) {
|
||||
perror("listen");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Set the non-blocking flag
|
||||
int flags = fcntl(sock, F_GETFL, 0) | O_NONBLOCK;
|
||||
fcntl(sock, F_SETFL, flags);
|
||||
LOG("listening on 127.0.0.1:%d", port);
|
||||
|
||||
// Enter client wait loop
|
||||
int wait = 0;
|
||||
do {
|
||||
struct timeval tv = { .tv_sec = 3, .tv_usec = 0 };
|
||||
fd_set rf;
|
||||
FD_ZERO(&rf);
|
||||
FD_SET(sock, &rf);
|
||||
int rc = select(sock + 1, &rf, NULL, NULL, &tv);
|
||||
if (rc < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
perror("select");
|
||||
break;
|
||||
}
|
||||
|
||||
// Accept and create a new client worker thread
|
||||
if (rc > 0 && FD_ISSET(sock, &rf)) {
|
||||
int cs = accept(sock, NULL, NULL);
|
||||
if (cs >= 0) {
|
||||
pthread_t th;
|
||||
pthread_create(&th, NULL, client_worker,
|
||||
(void *)(intptr_t)cs);
|
||||
pthread_detach(th);
|
||||
wait = 3;
|
||||
}
|
||||
}
|
||||
if(wait) wait--;
|
||||
} while ((num_clients || no_exit || wait) && !b_sigint);
|
||||
|
||||
close(sock);
|
||||
libusb_exit(NULL);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
190
wine64/launcher.c
Normal file
190
wine64/launcher.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* launcher.c
|
||||
* Native x86 windows dll injector and launcher.
|
||||
* Created: September 10, 2025
|
||||
* Author: radiomanV
|
||||
*/
|
||||
|
||||
#define UNICODE
|
||||
#define _UNICODE
|
||||
#include <windows.h>
|
||||
#include <shlwapi.h>
|
||||
#include <strsafe.h>
|
||||
#include <stdio.h>
|
||||
#include <wchar.h>
|
||||
|
||||
// Dll injector function
|
||||
static BOOL inject_dll(HANDLE hProc, const wchar_t *dllPath)
|
||||
{
|
||||
SIZE_T size = (wcslen(dllPath) + 1) * sizeof(wchar_t), wr = 0;
|
||||
LPVOID remote = VirtualAllocEx(
|
||||
hProc, NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!remote)
|
||||
return FALSE;
|
||||
BOOL ok = WriteProcessMemory(hProc, remote, dllPath, size, &wr) &&
|
||||
wr == size;
|
||||
if (!ok) {
|
||||
VirtualFreeEx(hProc, remote, 0, MEM_RELEASE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HMODULE hm = GetModuleHandleW(L"kernel32.dll");
|
||||
FARPROC pLoadLib = hm ? GetProcAddress(hm, "LoadLibraryW") : NULL;
|
||||
if (!pLoadLib) {
|
||||
VirtualFreeEx(hProc, remote, 0, MEM_RELEASE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HANDLE thrd = CreateRemoteThread(hProc, NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)pLoadLib,
|
||||
remote, 0, NULL);
|
||||
if (!thrd) {
|
||||
VirtualFreeEx(hProc, remote, 0, MEM_RELEASE);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
WaitForSingleObject(thrd, INFINITE);
|
||||
DWORD ret = 0;
|
||||
GetExitCodeThread(thrd, &ret);
|
||||
CloseHandle(thrd);
|
||||
VirtualFreeEx(hProc, remote, 0, MEM_RELEASE);
|
||||
return ret != 0;
|
||||
}
|
||||
|
||||
// Check file path
|
||||
static int check_path(const wchar_t *base_dir, const wchar_t *arg, wchar_t *out,
|
||||
size_t out_sz)
|
||||
{
|
||||
if (!arg || !*arg)
|
||||
return 0;
|
||||
|
||||
if (!PathIsRelativeW(arg)) {
|
||||
wcsncpy(out, arg, out_sz);
|
||||
out[out_sz - 1] = L'\0';
|
||||
} else {
|
||||
if (!PathCombineW(out, base_dir, arg))
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD att = GetFileAttributesW(out);
|
||||
return (att != INVALID_FILE_ATTRIBUTES) &&
|
||||
!(att & FILE_ATTRIBUTE_DIRECTORY);
|
||||
}
|
||||
|
||||
// Main function
|
||||
int wmain(int argc, wchar_t **argv)
|
||||
{
|
||||
const wchar_t *exe_arg = L"";
|
||||
const wchar_t *shim_arg = L"shim.dll";
|
||||
int forced = 0;
|
||||
|
||||
// Get the launcher's directory
|
||||
wchar_t dir[MAX_PATH];
|
||||
DWORD n = GetModuleFileNameW(NULL, dir, ARRAYSIZE(dir));
|
||||
if (n && n < ARRAYSIZE(dir)) {
|
||||
PathRemoveFileSpecW(dir);
|
||||
}
|
||||
|
||||
// Parse command line
|
||||
int pf = -1;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (wcscmp(argv[i], L"--exe") == 0 && i + 1 < argc) {
|
||||
exe_arg = argv[++i];
|
||||
forced = 1;
|
||||
continue;
|
||||
}
|
||||
if (wcscmp(argv[i], L"--shim") == 0 && i + 1 < argc) {
|
||||
shim_arg = argv[++i];
|
||||
continue;
|
||||
}
|
||||
if (wcscmp(argv[i], L"--") == 0) {
|
||||
pf = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
wchar_t exe_path[MAX_PATH], shim_path[MAX_PATH];
|
||||
if (forced) {
|
||||
if (!check_path(dir, exe_arg, exe_path, ARRAYSIZE(exe_path))) {
|
||||
wprintf(L"[launcher] --exe path not found or invalid: \"%ls\"\n",
|
||||
exe_arg);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
} else {
|
||||
const wchar_t *progs[] = { L"Minipro.exe", L"Xgpro.exe",
|
||||
L"Xgpro_T76.exe" };
|
||||
int found = 0;
|
||||
for (size_t i = 0; i < ARRAYSIZE(progs); ++i) {
|
||||
if (check_path(dir, progs[i], exe_path,
|
||||
ARRAYSIZE(exe_path))) {
|
||||
exe_arg = progs[i];
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
wprintf(L"[launcher] --exe not specified and no known executable found.\n",
|
||||
dir);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve shim (accept absolute or relative)
|
||||
if (!check_path(dir, shim_arg, shim_path, ARRAYSIZE(shim_path))) {
|
||||
wprintf(L"[launcher] Shim not found: \"%ls\" (base: \"%ls\")\n",
|
||||
shim_arg, dir);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Build the target process command line
|
||||
wchar_t cmdline[4096];
|
||||
int off = _snwprintf(cmdline, ARRAYSIZE(cmdline), L"\"%s\"", exe_path);
|
||||
if (off < 0 || off >= (int)ARRAYSIZE(cmdline)) {
|
||||
wprintf(L"[launcher] Command line too long (exe).\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (pf > 0) {
|
||||
for (int i = pf; i < argc; ++i) {
|
||||
int wrote = _snwprintf(cmdline + off,
|
||||
ARRAYSIZE(cmdline) - off,
|
||||
L" \"%ls\"", argv[i]);
|
||||
if (wrote < 0 ||
|
||||
off + wrote >= (int)ARRAYSIZE(cmdline)) {
|
||||
wprintf(L"[launcher] Command line too long.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
off += wrote;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a suspended process
|
||||
STARTUPINFOW si = { 0 };
|
||||
si.cb = sizeof(si);
|
||||
PROCESS_INFORMATION pinfo = { 0 };
|
||||
if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE,
|
||||
CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, NULL,
|
||||
NULL, &si, &pinfo)) {
|
||||
wprintf(L"[launcher] CreateProcessW failed, err=%lu\n",
|
||||
GetLastError());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Inject our shim.dll into the target process
|
||||
if (!inject_dll(pinfo.hProcess, shim_path)) {
|
||||
wprintf(L"[launcher] DLL injection failed, err=%lu\n",
|
||||
GetLastError());
|
||||
TerminateProcess(pinfo.hProcess, 1);
|
||||
CloseHandle(pinfo.hThread);
|
||||
CloseHandle(pinfo.hProcess);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Resume process and wait for exit
|
||||
ResumeThread(pinfo.hThread);
|
||||
WaitForSingleObject(pinfo.hProcess, INFINITE);
|
||||
DWORD ret = 0;
|
||||
GetExitCodeProcess(pinfo.hProcess, &ret);
|
||||
CloseHandle(pinfo.hThread);
|
||||
CloseHandle(pinfo.hProcess);
|
||||
return (int)ret;
|
||||
}
|
||||
BIN
wine64/launcher.exe
Executable file
BIN
wine64/launcher.exe
Executable file
Binary file not shown.
141
wine64/protocol.h
Normal file
141
wine64/protocol.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* protocol.h
|
||||
* USB broker protocol call definitions
|
||||
* Created: September 10, 2025
|
||||
* Author: radiomanV
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define PACKED
|
||||
#pragma pack(push, 1)
|
||||
#else
|
||||
#define PACKED __attribute__((__packed__))
|
||||
#endif
|
||||
|
||||
#define PROTO_VERSION 2
|
||||
#define RPC_PORT_DEFAULT 35866
|
||||
|
||||
// ---- MSG ----
|
||||
enum msg_type {
|
||||
MSG_ENUMERATE = 1,
|
||||
MSG_OPEN,
|
||||
MSG_CLOSE,
|
||||
MSG_BULK,
|
||||
MSG_CANCEL,
|
||||
MSG_HOTPLUG_SUB,
|
||||
MSG_HOTPLUG_EVT,
|
||||
};
|
||||
|
||||
// ---- RPC HEADER ----
|
||||
typedef struct PACKED {
|
||||
uint32_t len;
|
||||
uint16_t type;
|
||||
uint16_t version;
|
||||
} rpc_hdr_t;
|
||||
|
||||
// --- DEVICE INFO ---
|
||||
typedef struct PACKED {
|
||||
uint16_t vid, pid;
|
||||
uint8_t bus, addr;
|
||||
uint8_t ifnum;
|
||||
uint8_t has_eps;
|
||||
uint16_t wMaxPacketSize[16];
|
||||
char path[128];
|
||||
} dev_info_t;
|
||||
|
||||
// ---- ENUMERATE ----
|
||||
typedef struct PACKED {
|
||||
int32_t status;
|
||||
uint32_t count;
|
||||
dev_info_t devs[0];
|
||||
} enum_resp_t;
|
||||
|
||||
// ---- OPEN ----
|
||||
typedef struct PACKED {
|
||||
char path[128];
|
||||
} open_req_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
int32_t status;
|
||||
uint32_t handle_id;
|
||||
uint8_t speed;
|
||||
char product[64];
|
||||
} open_resp_t;
|
||||
|
||||
// ---- CLOSE ----
|
||||
typedef struct PACKED {
|
||||
uint32_t handle_id;
|
||||
} close_req_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
int32_t status;
|
||||
} close_resp_t;
|
||||
|
||||
// ---- BULK ----
|
||||
typedef struct PACKED {
|
||||
uint32_t handle_id;
|
||||
uint8_t ep;
|
||||
uint32_t timeout_ms;
|
||||
uint32_t len;
|
||||
} bulk_req_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
int32_t status;
|
||||
uint32_t rx_len;
|
||||
uint8_t data[0];
|
||||
} bulk_resp_t;
|
||||
|
||||
// ---- CANCEL ----
|
||||
typedef struct PACKED {
|
||||
uint32_t handle_id;
|
||||
} cancel_req_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
int32_t status;
|
||||
} cancel_resp_t;
|
||||
|
||||
// ---- HOTPLUG ----
|
||||
typedef struct PACKED {
|
||||
uint16_t vid, pid;
|
||||
} hotplug_sub_req_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t arrived;
|
||||
uint16_t vid, pid;
|
||||
uint8_t bus, addr;
|
||||
} hotplug_evt_t;
|
||||
|
||||
typedef enum rpc_err_t {
|
||||
BR_E_WSA = -1,
|
||||
BR_E_SOCK = -2,
|
||||
BR_E_CONNECT_IMMEDIATE = -3,
|
||||
BR_E_CONNECT_SOERR = -4,
|
||||
BR_E_CONNECT_TIMEOUT = -5,
|
||||
BRK_E_READY_TIMEOUT = -50,
|
||||
BRK_E_READY_FAILED = -51,
|
||||
BRK_E_WAIT_TIMEOUT = -52,
|
||||
RPC_E_RESP_LEN_NULL = -100,
|
||||
RPC_E_RESP_BUF_NULL = -101,
|
||||
RPC_E_MKSOCK_FAIL = -102,
|
||||
RPC_E_CONNECT_TIMEOUT = -103,
|
||||
RPC_E_CONNECT_FAIL = -104,
|
||||
RPC_E_SEND_HEADER = -105,
|
||||
RPC_E_SEND_REQ = -106,
|
||||
RPC_E_SEND_EXTRA = -107,
|
||||
RPC_E_RECV_HEADER = -108,
|
||||
RPC_E_PROTO_MISMATCH = -109,
|
||||
RPC_E_RESP_TOO_LARGE = -110,
|
||||
RPC_E_RECV_PAYLOAD = -111,
|
||||
RPC_E_RECV_ERR = -112,
|
||||
RPC_E_SEND_ERR = -113,
|
||||
RPC_E_OPEN_BUSY = -114,
|
||||
RPC_E_OPEN_ERR = -115,
|
||||
} rpc_err_t;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
#undef PACKED
|
||||
7
wine64/resource.h
Normal file
7
wine64/resource.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define IDD_SHIM_ABOUTBOX 0xE100
|
||||
#define IDS_SHIM_VERSION 0xE200
|
||||
#define IDM_SHIM_ABOUT 0xE300
|
||||
#define IDC_SHIM_LINK 0xE400
|
||||
#define IDC_SHIM_APPICON 0xE401
|
||||
38
wine64/resource.rc
Normal file
38
wine64/resource.rc
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
|
||||
#define VER_MAJOR 1
|
||||
#define VER_MINOR 0
|
||||
|
||||
#define STR_HELPER(x) #x
|
||||
#define STR(x) STR_HELPER(x)
|
||||
|
||||
#define IDI_APP 1
|
||||
|
||||
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
|
||||
|
||||
IDI_APP ICON "shim.ico"
|
||||
|
||||
STRINGTABLE
|
||||
BEGIN
|
||||
IDS_SHIM_VERSION, "v" STR(VER_MAJOR) "." STR(VER_MINOR)
|
||||
END
|
||||
|
||||
|
||||
// About dialog
|
||||
IDD_SHIM_ABOUTBOX DIALOGEX 0, 0, 287, 118
|
||||
STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "About USB wrapper"
|
||||
FONT 9, "MS Shell Dlg"
|
||||
BEGIN
|
||||
LTEXT "Minipro / Xgpro USB wrapper for Wine.", -1, 10, 10, 200, 10
|
||||
LTEXT "Version:", -1, 10, 24, 40, 10
|
||||
LTEXT "", IDS_SHIM_VERSION, 40, 24, 156, 10
|
||||
LTEXT "(C) 2014-2025 radiomanV", -1, 10, 38, 266, 10
|
||||
CONTROL "", -1, "Static", SS_BLACKFRAME | WS_CHILD | WS_VISIBLE, 10, 56, 270, 1
|
||||
CONTROL "", IDC_SHIM_LINK, "Static", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 10, 70, 230, 24
|
||||
ICON IDI_APP, IDC_SHIM_APPICON, 234, 21, 96, 96, WS_CHILD | WS_VISIBLE
|
||||
DEFPUSHBUTTON "OK", IDOK, 228, 95, 50, 14
|
||||
END
|
||||
|
||||
|
||||
77
wine64/run.sh
Executable file
77
wine64/run.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PORT="${BROKER_PORT:-35866}"
|
||||
DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
APP="${APP_PATH:-"$DIR/launcher.exe"}"
|
||||
EXE="${TARGET_EXE:-Xgpro.exe}"
|
||||
SHIM="${SHIM_DLL:-shim.dll}"
|
||||
BROKER="${USB_BROKER_PATH:-"$DIR/usb-broker"}"
|
||||
WINECMD="${WINECMD:-wine}"
|
||||
|
||||
have_nc() { command -v nc >/dev/null 2>&1; }
|
||||
|
||||
is_valid_port() {
|
||||
case $1 in
|
||||
''|*[!0-9]*) return 1
|
||||
esac
|
||||
[ "$1" -ge 1 ] && [ "$1" -le 65535 ]
|
||||
}
|
||||
|
||||
port_ready() {
|
||||
if ! is_valid_port "$PORT"; then
|
||||
printf 'Invalid PORT="%s" (expected 1..65535)\n' "$PORT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (exec 3<>"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null; then
|
||||
exec 3>&- 3<&-
|
||||
return 0
|
||||
fi
|
||||
if have_nc && nc -z 127.0.0.1 "$PORT" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
wait_port() {
|
||||
local timeout_sec="${1:-8}" i
|
||||
for ((i=0; i<timeout_sec*10; i++)); do
|
||||
if port_ready; then return 0; fi
|
||||
sleep 0.1
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
start_broker() {
|
||||
if port_ready; then
|
||||
echo "usb-broker already running on 127.0.0.1:$PORT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -x "$BROKER" ]]; then
|
||||
echo "ERROR: broker not executable: $BROKER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting usb-broker: $BROKER --port $PORT"
|
||||
chmod +x "$BROKER" || true
|
||||
"$BROKER" --port "$PORT" --quiet &
|
||||
local bpid=$!
|
||||
echo "usb-broker PID=$bpid"
|
||||
|
||||
if ! wait_port 10; then
|
||||
echo "ERROR: usb-broker did not open port $PORT in time" >&2
|
||||
kill "$bpid" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_app() {
|
||||
cd "$DIR"
|
||||
echo "Launching wine app: $EXE"
|
||||
"$WINECMD" "$APP" --exe "$EXE" --shim "$SHIM" -- "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
start_broker
|
||||
run_app "$@"
|
||||
1973
wine64/shim.c
Normal file
1973
wine64/shim.c
Normal file
File diff suppressed because it is too large
Load Diff
BIN
wine64/shim.dll
Executable file
BIN
wine64/shim.dll
Executable file
Binary file not shown.
BIN
wine64/shim.ico
Normal file
BIN
wine64/shim.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
wine64/usb-broker
Executable file
BIN
wine64/usb-broker
Executable file
Binary file not shown.
Reference in New Issue
Block a user