Compare commits

...

5 Commits

Author SHA1 Message Date
EnderIce2
299408d10e
Update Makefile to use x86_64-w64-mingw32-gcc 2024-03-07 06:48:54 +02:00
EnderIce2
8b356b65b8
Add macOS support 2024-03-07 06:48:50 +02:00
EnderIce2
66a20f51c4
Update platform check and add help option 2024-03-07 06:48:29 +02:00
EnderIce2
d5af2277c5
Prevent installing as service on macOS 2024-03-07 06:47:56 +02:00
EnderIce2
16291ec0fd
Add macOS instructions 2024-03-07 06:47:35 +02:00
5 changed files with 220 additions and 57 deletions

View File

@ -14,11 +14,11 @@ install: build
build: $(C_OBJECTS)
$(info Linking)
i686-w64-mingw32-gcc $(C_OBJECTS) $(LFLAGS) $(DBGFLAGS) -o build/bridge.exe
x86_64-w64-mingw32-gcc $(C_OBJECTS) $(LFLAGS) $(DBGFLAGS) -o build/bridge.exe
%.o: %.c
$(info Compiling $<)
i686-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
x86_64-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
clean:
rm -f $(C_OBJECTS) build/bridge.exe

View File

@ -23,6 +23,7 @@ This bridge takes advantage of the Windows service implementation in Wine, elimi
- [Steam](#steam)
- [If you use Flatpak](#if-you-use-flatpak)
- [Run without installing the service](#run-without-installing-the-service)
- [About macOS](#about-macos)
- [Compiling from source](#compiling-from-source)
- [Command line arguments](#command-line-arguments)
- [Debugging](#debugging)
@ -96,9 +97,15 @@ In Wine, all you need to do is run `bridge.exe`.
- When running the program manually without providing any arguments, it will simply initiate the bridge and wait indefinitely until it's closed.
## About macOS
The bridge works similarly on macOS as it does on Linux, but it can't be registered as a service due to TMPDIR limitations. macOS users must manually run `bridge.exe` when needed.
To run `bridge.exe` on macOS, navigate to its directory in the terminal and execute `wine bridge.exe`. (or double click it in the Finder)
## Compiling from source
- Install the `wine`, `i686-w64-mingw32-gcc` and `make` packages.
- Install the `wine`, `x86_64-w64-mingw32-gcc` and `make` packages.
- Open a terminal in the directory that contains this file and run `make`.
- The compiled executable will be located in `build/bridge.exe`.

229
bridge.c
View File

@ -4,25 +4,48 @@
#include <assert.h>
#include <stdio.h>
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_socketcall 102
#define __linux_read 3
#define __linux_write 4
#define __linux_open 5
#define __linux_close 6
#define __linux_munmap 91
#define __linux_socketcall 102
#define __linux_mmap2 192
#define __linux_socket 359
#define __linux_connect 362
#define __darwin_read 0x2000003
#define __darwin_write 0x2000004
#define __darwin_open 0x2000005
#define __darwin_close 0x2000006
#define __darwin_socket 0x2000061
#define __darwin_connect 0x2000062
#define __darwin_fcntl 0x200005C
#define __darwin_sysctl 0x20000CA
#define O_RDONLY 00
#define PROT_READ 1
#define PROT_WRITE 2
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANON 0x20
#define MAP_FAILED ((void *)-1)
#define SYS_SOCKET 1
#define SYS_CONNECT 3
#define O_RDONLY 00
#define likely(expr) (__builtin_expect(!!(expr), 1))
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
#define force_inline \
__inline__ \
__attribute__((__always_inline__, __gnu_inline__))
#define naked __attribute__((naked))
typedef unsigned short sa_family_t;
typedef char *caddr_t;
typedef unsigned socklen_t;
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
@ -39,10 +62,11 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage();
extern BOOL RunningAsService;
BOOL RetryNewConnection;
BOOL IsLinux;
static force_inline int syscall(int num,
intptr_t arg1, intptr_t arg2, intptr_t arg3,
intptr_t arg4, intptr_t arg5, intptr_t arg6)
static force_inline int linux_syscall(int num,
int arg1, int arg2, int arg3,
int arg4, int arg5, int arg6)
{
int ret;
__asm__ __volatile__(
@ -51,38 +75,124 @@ static force_inline int syscall(int num,
: "0"(num), "b"(arg1), "c"(arg2),
"d"(arg3), "S"(arg4), "D"(arg5)
: "memory");
return ret;
}
static naked int darwin_syscall(int num,
long arg1, long arg2, long arg3,
long arg4, long arg5, long arg6)
{
register long r10 __asm__("r10") = arg4;
register long r8 __asm__("r8") = arg5;
register long r9 __asm__("r9") = arg6;
__asm__ __volatile__(
"syscall\n"
"jae noerror\n"
"negq %%rax\n"
"noerror:\n"
"ret\n"
: "=a"(num)
: "a"(num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), "r"(r9)
: "memory");
}
static inline int sys_read(int fd, void *buf, size_t count)
{
return syscall(__NR_read, fd, (intptr_t)buf, count, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0);
}
static inline int sys_write(int fd, const void *buf, size_t count)
{
return syscall(__NR_write, fd, (intptr_t)buf, count, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0);
}
static inline int sys_open(const char *pathname, int flags, int mode)
{
return syscall(__NR_open, (intptr_t)pathname, flags, mode, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else
return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0);
}
static inline int sys_close(int fd)
{
return syscall(__NR_close, fd, 0, 0, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else
return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0);
}
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
assert(IsLinux);
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
}
static inline int sys_munmap(unsigned int *addr, size_t length)
{
assert(IsLinux);
return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0);
}
static inline int sys_socketcall(int call, unsigned long *args)
{
return syscall(__NR_socketcall, call, (intptr_t)args, 0, 0, 0, 0);
assert(IsLinux);
return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0);
}
char *linux_getenv(const char *name)
static inline int sys_socket(int domain, int type, int protocol)
{
int fd = sys_open("/proc/self/environ", O_RDONLY, 0);
if (IsLinux)
return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0);
else
return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0);
}
static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
{
if (IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
}
char *native_getenv(const char *name)
{
if (!IsLinux)
{
char *value = getenv(name);
if (value == NULL)
{
print("Failed to get environment variable: %s\n", name);
return NULL;
}
return value;
}
/* I hope the 0x10000 is okay */
void *environStr = sys_mmap(0x10000, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
if (environStr == MAP_FAILED)
{
print("Failed to allocate environ string: %d\n", (intptr_t)environStr);
return NULL;
}
if ((uintptr_t)environStr > 0x7effffff)
print("Warning: environStr %p is above 2GB\n", environStr);
const char *linux_environ = "/proc/self/environ";
memcpy(environStr, linux_environ, strlen(linux_environ) + 1);
int fd = sys_open(environStr, O_RDONLY, 0);
sys_munmap(environStr, 4096);
if (fd < 0)
{
print("Failed to open /proc/self/environ: %d\n", fd);
@ -123,21 +233,22 @@ char *linux_getenv(const char *name)
void ConnectToSocket(int fd)
{
print("Connecting to socket\n");
const char *runtime = linux_getenv("XDG_RUNTIME_DIR");
const char *runtime;
if (IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR");
else
runtime = native_getenv("TMPDIR");
if (runtime == NULL)
{
print("XDG_RUNTIME_DIR not set\n");
print("IPC directory not set\n");
if (!RunningAsService)
{
MessageBox(NULL,
"XDG_RUNTIME_DIR not set",
MessageBox(NULL, "IPC directory not set",
"Environment variable not set",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
print("XDG_RUNTIME_DIR: %s\n", runtime);
print("IPC directory: %s\n", runtime);
/* TODO: check for multiple discord instances and create a pipe for each */
const char *discordUnixPipes[] = {
@ -160,12 +271,17 @@ void ConnectToSocket(int fd)
print("Connecting to %s\n", pipePath);
unsigned long socketArgs[] = {
(unsigned long)fd,
(unsigned long)&socketAddr,
sizeof(socketAddr)};
if (IsLinux)
{
unsigned long socketArgs[] = {
(unsigned long)fd,
(unsigned long)(intptr_t)&socketAddr,
sizeof(socketAddr)};
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
}
else
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
free(pipePath);
if (sockRet >= 0)
@ -176,12 +292,9 @@ void ConnectToSocket(int fd)
{
print("socketcall failed for: %d\n", sockRet);
if (!RunningAsService)
{
MessageBox(NULL,
"Failed to connect to Discord",
MessageBox(NULL, "Failed to connect to Discord",
"Socket Connection failed",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
}
@ -327,6 +440,17 @@ void PipeBufferOutThread(LPVOID lpParam)
void CreateBridge()
{
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
HMODULE hntdll = GetModuleHandle("ntdll.dll");
wine_get_host_version = (void *)GetProcAddress(hntdll, "wine_get_host_version");
assert(wine_get_host_version);
const char *__sysname;
const char *__release;
wine_get_host_version(&__sysname, &__release);
IsLinux = strcmp(__sysname, "Linux") == 0;
print("Running on %s\n", __sysname);
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
NewConnection:
@ -334,11 +458,11 @@ NewConnection:
NULL, NULL,
NULL, NULL))
{
print("Pipe already exists: %s\n",
GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Pipe already exists: %s\n",
GetErrorMessage());
ExitProcess(1);
}
@ -350,11 +474,11 @@ NewConnection:
if (hPipe == INVALID_HANDLE_VALUE)
{
print("Failed to create pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to create pipe: %s\n",
GetErrorMessage());
ExitProcess(1);
}
@ -362,30 +486,35 @@ NewConnection:
print("Waiting for pipe connection\n");
if (!ConnectNamedPipe(hPipe, NULL))
{
print("Failed to connect to pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to connect to pipe: %s\n",
GetErrorMessage());
ExitProcess(1);
}
print("Pipe connected\n");
unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
(unsigned long)SOCK_STREAM,
0};
int fd = sys_socketcall(SYS_SOCKET, socketArgs);
int fd;
if (IsLinux)
{
unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
(unsigned long)SOCK_STREAM,
0};
fd = sys_socketcall(SYS_SOCKET, socketArgs);
}
else
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
print("Socket %d created\n", fd);
if (fd < 0)
{
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to create socket: %d\n", fd);
if (!RunningAsService)
MessageBox(NULL, "Failed to create socket",
NULL, MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
@ -406,9 +535,9 @@ NewConnection:
if (hIn == NULL || hOut == NULL)
{
print("Failed to create threads: %s\n", GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
print("Failed to create threads: %s\n", GetErrorMessage());
ExitProcess(1);
}

16
main.c
View File

@ -60,9 +60,9 @@ void DetectWine()
const char *__sysname;
const char *__release;
wine_get_host_version(&__sysname, &__release);
if (strcmp(__sysname, "Linux") != 0)
if (strcmp(__sysname, "Linux") != 0 && strcmp(__sysname, "Darwin") != 0)
{
int result = MessageBox(NULL, "This program is designed for Linux only!\nDo you want to proceed?",
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
NULL, MB_YESNO | MB_ICONQUESTION);
if (result == IDYES)
return;
@ -114,11 +114,19 @@ int main(int argc, char *argv[])
}
return 0;
}
if (strcmp(argv[1], "--install") == 0)
else if (strcmp(argv[1], "--install") == 0)
InstallService();
else if (strcmp(argv[1], "--uninstall") == 0)
RemoveService();
else if (strcmp(argv[1], "--help") == 0)
{
printf("Usage:\n");
printf(" %s [args]\n", argv[0]);
printf(" --install - Install service\n");
printf(" --uninstall - Uninstall service\n");
printf(" --help - Show this help\n");
ExitProcess(0);
}
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,

View File

@ -83,10 +83,29 @@ void ServiceMain(DWORD argc, LPTSTR *argv)
return;
}
void DetectDarwin()
{
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
wine_get_host_version = (void *)GetProcAddress(GetModuleHandle("ntdll.dll"),
"wine_get_host_version");
const char *__sysname;
const char *__release;
wine_get_host_version(&__sysname, &__release);
if (strcmp(__sysname, "Darwin") == 0)
{
/* FIXME: I don't know how to get the TMPDIR without getenv */
MessageBox(NULL, "Registering as a service is not supported on macOS at the moment.",
"Unsupported", MB_OK | MB_ICONINFORMATION);
ExitProcess(1);
}
}
void InstallService()
{
print("Registering to run on startup\n");
DetectDarwin();
SC_HANDLE schSCManager, schService;
DWORD dwTagId;