mirror of
https://github.com/EnderIce2/rpc-bridge.git
synced 2025-07-01 18:39:14 +00:00
Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
5d0e6c4026
|
|||
418e53e7f7
|
|||
03d5e8dbc8
|
|||
e5a09024ff
|
|||
aeda375cba
|
|||
7af098ab26
|
|||
7461479cbb
|
|||
299408d10e
|
|||
8b356b65b8
|
|||
66a20f51c4
|
|||
d5af2277c5
|
|||
16291ec0fd
|
|||
56168a6d3d
|
|||
d2b23eb2e8
|
|||
cba4fa1e2b
|
|||
8c7b336c0e | |||
334c4e0479
|
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -2,7 +2,11 @@
|
|||||||
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc",
|
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc",
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.su": "tsv",
|
"*.su": "tsv",
|
||||||
|
"thread": "cpp",
|
||||||
|
"bitset": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
"windows.h": "c",
|
"windows.h": "c",
|
||||||
"namedpipeapi.h": "c"
|
"namedpipeapi.h": "c",
|
||||||
|
"*.rh": "c"
|
||||||
}
|
}
|
||||||
}
|
}
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2023 EnderIce2
|
Copyright (c) 2024 EnderIce2
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
4
Makefile
4
Makefile
@ -14,11 +14,11 @@ install: build
|
|||||||
|
|
||||||
build: $(C_OBJECTS)
|
build: $(C_OBJECTS)
|
||||||
$(info Linking)
|
$(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
|
%.o: %.c
|
||||||
$(info Compiling $<)
|
$(info Compiling $<)
|
||||||
i686-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
|
x86_64-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(C_OBJECTS) build/bridge.exe
|
rm -f $(C_OBJECTS) build/bridge.exe
|
||||||
|
15
README.md
15
README.md
@ -1,5 +1,9 @@
|
|||||||
# Discord RPC Bridge for Wine
|
# Discord RPC Bridge for Wine
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
Simple bridge that allows you to use Discord Rich Presence with Wine games/software.
|
Simple bridge that allows you to use Discord Rich Presence with Wine games/software.
|
||||||
|
|
||||||
Works by running a small program in the background that creates a [named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes) `\\.\pipe\discord-ipc-0` inside the prefix and forwards all data to the pipe `/run/user/1000/discord-ipc-0`.
|
Works by running a small program in the background that creates a [named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes) `\\.\pipe\discord-ipc-0` inside the prefix and forwards all data to the pipe `/run/user/1000/discord-ipc-0`.
|
||||||
@ -19,6 +23,7 @@ This bridge takes advantage of the Windows service implementation in Wine, elimi
|
|||||||
- [Steam](#steam)
|
- [Steam](#steam)
|
||||||
- [If you use Flatpak](#if-you-use-flatpak)
|
- [If you use Flatpak](#if-you-use-flatpak)
|
||||||
- [Run without installing the service](#run-without-installing-the-service)
|
- [Run without installing the service](#run-without-installing-the-service)
|
||||||
|
- [About macOS](#about-macos)
|
||||||
- [Compiling from source](#compiling-from-source)
|
- [Compiling from source](#compiling-from-source)
|
||||||
- [Command line arguments](#command-line-arguments)
|
- [Command line arguments](#command-line-arguments)
|
||||||
- [Debugging](#debugging)
|
- [Debugging](#debugging)
|
||||||
@ -76,7 +81,7 @@ If you prefer not to use the service for any reason, please refer to the [Run wi
|
|||||||
|
|
||||||
## Run without installing the service
|
## Run without installing the service
|
||||||
|
|
||||||
If you prefer not to use the service, you can manually run `bridge.exe`` within the Wine prefix.
|
If you prefer not to use the service, you can manually run `bridge.exe` within the Wine prefix.
|
||||||
This method is compatible with both Wine and Lutris.
|
This method is compatible with both Wine and Lutris.
|
||||||
|
|
||||||
In Lutris, you can achieve this by adding the path to `bridge.exe` in the `Executable` field under `Game options`. In `Arguments` field, be sure to include the _Windows_ path to the game's executable.
|
In Lutris, you can achieve this by adding the path to `bridge.exe` in the `Executable` field under `Game options`. In `Arguments` field, be sure to include the _Windows_ path to the game's executable.
|
||||||
@ -92,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.
|
- 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
|
## 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`.
|
- Open a terminal in the directory that contains this file and run `make`.
|
||||||
- The compiled executable will be located in `build/bridge.exe`.
|
- The compiled executable will be located in `build/bridge.exe`.
|
||||||
|
|
||||||
|
234
bridge.c
234
bridge.c
@ -4,22 +4,48 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#define __NR_read 3
|
#define __linux_read 3
|
||||||
#define __NR_write 4
|
#define __linux_write 4
|
||||||
#define __NR_open 5
|
#define __linux_open 5
|
||||||
#define __NR_close 6
|
#define __linux_close 6
|
||||||
#define __NR_socketcall 102
|
#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 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 likely(expr) (__builtin_expect(!!(expr), 1))
|
#define likely(expr) (__builtin_expect(!!(expr), 1))
|
||||||
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
|
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
|
||||||
|
|
||||||
#define force_inline \
|
#define force_inline \
|
||||||
__inline__ \
|
__inline__ \
|
||||||
__attribute__((__always_inline__, __gnu_inline__))
|
__attribute__((__always_inline__, __gnu_inline__))
|
||||||
|
#define naked __attribute__((naked))
|
||||||
|
|
||||||
typedef unsigned short sa_family_t;
|
typedef unsigned short sa_family_t;
|
||||||
|
typedef char *caddr_t;
|
||||||
|
typedef unsigned socklen_t;
|
||||||
struct sockaddr_un
|
struct sockaddr_un
|
||||||
{
|
{
|
||||||
sa_family_t sun_family; /* AF_UNIX */
|
sa_family_t sun_family; /* AF_UNIX */
|
||||||
@ -36,10 +62,11 @@ void print(char const *fmt, ...);
|
|||||||
LPTSTR GetErrorMessage();
|
LPTSTR GetErrorMessage();
|
||||||
extern BOOL RunningAsService;
|
extern BOOL RunningAsService;
|
||||||
BOOL RetryNewConnection;
|
BOOL RetryNewConnection;
|
||||||
|
BOOL IsLinux;
|
||||||
|
|
||||||
static force_inline int syscall(int num,
|
static force_inline int linux_syscall(int num,
|
||||||
intptr_t arg1, intptr_t arg2, intptr_t arg3,
|
int arg1, int arg2, int arg3,
|
||||||
intptr_t arg4, intptr_t arg5, intptr_t arg6)
|
int arg4, int arg5, int arg6)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
__asm__ __volatile__(
|
__asm__ __volatile__(
|
||||||
@ -48,38 +75,124 @@ static force_inline int syscall(int num,
|
|||||||
: "0"(num), "b"(arg1), "c"(arg2),
|
: "0"(num), "b"(arg1), "c"(arg2),
|
||||||
"d"(arg3), "S"(arg4), "D"(arg5)
|
"d"(arg3), "S"(arg4), "D"(arg5)
|
||||||
: "memory");
|
: "memory");
|
||||||
|
|
||||||
return ret;
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
print("Failed to open /proc/self/environ: %d\n", fd);
|
print("Failed to open /proc/self/environ: %d\n", fd);
|
||||||
@ -120,22 +233,24 @@ char *linux_getenv(const char *name)
|
|||||||
void ConnectToSocket(int fd)
|
void ConnectToSocket(int fd)
|
||||||
{
|
{
|
||||||
print("Connecting to socket\n");
|
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)
|
if (runtime == NULL)
|
||||||
{
|
{
|
||||||
print("XDG_RUNTIME_DIR not set\n");
|
print("IPC directory not set\n");
|
||||||
if (!RunningAsService)
|
if (!RunningAsService)
|
||||||
{
|
MessageBox(NULL, "IPC directory not set",
|
||||||
MessageBox(NULL,
|
|
||||||
"XDG_RUNTIME_DIR not set",
|
|
||||||
"Environment variable not set",
|
"Environment variable not set",
|
||||||
MB_OK | MB_ICONSTOP);
|
MB_OK | MB_ICONSTOP);
|
||||||
}
|
|
||||||
ExitProcess(1);
|
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[] = {
|
const char *discordUnixPipes[] = {
|
||||||
"/discord-ipc-0",
|
"/discord-ipc-0",
|
||||||
"/snap.discord/discord-ipc-0",
|
"/snap.discord/discord-ipc-0",
|
||||||
@ -156,12 +271,17 @@ void ConnectToSocket(int fd)
|
|||||||
|
|
||||||
print("Connecting to %s\n", pipePath);
|
print("Connecting to %s\n", pipePath);
|
||||||
|
|
||||||
|
if (IsLinux)
|
||||||
|
{
|
||||||
unsigned long socketArgs[] = {
|
unsigned long socketArgs[] = {
|
||||||
(unsigned long)fd,
|
(unsigned long)fd,
|
||||||
(unsigned long)&socketAddr,
|
(unsigned long)(intptr_t)&socketAddr,
|
||||||
sizeof(socketAddr)};
|
sizeof(socketAddr)};
|
||||||
|
|
||||||
sockRet = sys_socketcall(3, socketArgs);
|
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
|
||||||
|
|
||||||
free(pipePath);
|
free(pipePath);
|
||||||
if (sockRet >= 0)
|
if (sockRet >= 0)
|
||||||
@ -172,21 +292,20 @@ void ConnectToSocket(int fd)
|
|||||||
{
|
{
|
||||||
print("socketcall failed for: %d\n", sockRet);
|
print("socketcall failed for: %d\n", sockRet);
|
||||||
if (!RunningAsService)
|
if (!RunningAsService)
|
||||||
{
|
MessageBox(NULL, "Failed to connect to Discord",
|
||||||
MessageBox(NULL,
|
|
||||||
"Failed to connect to Discord",
|
|
||||||
"Socket Connection failed",
|
"Socket Connection failed",
|
||||||
MB_OK | MB_ICONSTOP);
|
MB_OK | MB_ICONSTOP);
|
||||||
}
|
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HANDLE hOut = NULL;
|
||||||
void PipeBufferInThread(LPVOID lpParam)
|
void PipeBufferInThread(LPVOID lpParam)
|
||||||
{
|
{
|
||||||
bridge_thread *bt = (bridge_thread *)lpParam;
|
bridge_thread *bt = (bridge_thread *)lpParam;
|
||||||
print("In thread started using fd %d and pipe %#x\n",
|
print("In thread started using fd %d and pipe %#x\n",
|
||||||
bt->fd, bt->hPipe);
|
bt->fd, bt->hPipe);
|
||||||
|
int EOFCount = 0;
|
||||||
while (TRUE)
|
while (TRUE)
|
||||||
{
|
{
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
@ -200,12 +319,22 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (EOFCount > 4)
|
||||||
|
{
|
||||||
|
print("EOF count exceeded\n");
|
||||||
|
RetryNewConnection = TRUE;
|
||||||
|
TerminateThread(hOut, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (unlikely(read == 0))
|
if (unlikely(read == 0))
|
||||||
{
|
{
|
||||||
print("EOF\n");
|
print("EOF\n");
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
|
EOFCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
EOFCount = 0;
|
||||||
|
|
||||||
print("Reading %d bytes from unix pipe: \"", read);
|
print("Reading %d bytes from unix pipe: \"", read);
|
||||||
for (int i = 0; i < read; i++)
|
for (int i = 0; i < read; i++)
|
||||||
@ -330,11 +459,14 @@ NewConnection:
|
|||||||
NULL, NULL,
|
NULL, NULL,
|
||||||
NULL, NULL))
|
NULL, NULL))
|
||||||
{
|
{
|
||||||
if (!RunningAsService)
|
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
|
||||||
print("Pipe already exists: %s\n",
|
print("Pipe already exists: %s\n",
|
||||||
GetErrorMessage());
|
GetErrorMessage());
|
||||||
|
if (!RunningAsService)
|
||||||
|
{
|
||||||
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"Pipe already exists",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
|
}
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,11 +478,14 @@ NewConnection:
|
|||||||
|
|
||||||
if (hPipe == INVALID_HANDLE_VALUE)
|
if (hPipe == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
if (!RunningAsService)
|
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
|
||||||
print("Failed to create pipe: %s\n",
|
print("Failed to create pipe: %s\n",
|
||||||
GetErrorMessage());
|
GetErrorMessage());
|
||||||
|
if (!RunningAsService)
|
||||||
|
{
|
||||||
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"Failed to create pipe",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
|
}
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,30 +493,35 @@ NewConnection:
|
|||||||
print("Waiting for pipe connection\n");
|
print("Waiting for pipe connection\n");
|
||||||
if (!ConnectNamedPipe(hPipe, NULL))
|
if (!ConnectNamedPipe(hPipe, NULL))
|
||||||
{
|
{
|
||||||
|
print("Failed to connect to pipe: %s\n",
|
||||||
|
GetErrorMessage());
|
||||||
if (!RunningAsService)
|
if (!RunningAsService)
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
NULL, MB_OK | MB_ICONSTOP);
|
||||||
print("Failed to connect to pipe: %s\n",
|
|
||||||
GetErrorMessage());
|
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
print("Pipe connected\n");
|
print("Pipe connected\n");
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
if (IsLinux)
|
||||||
|
{
|
||||||
unsigned long socketArgs[] = {
|
unsigned long socketArgs[] = {
|
||||||
(unsigned long)AF_UNIX,
|
(unsigned long)AF_UNIX,
|
||||||
(unsigned long)SOCK_STREAM,
|
(unsigned long)SOCK_STREAM,
|
||||||
0};
|
0};
|
||||||
|
fd = sys_socketcall(SYS_SOCKET, socketArgs);
|
||||||
int fd = sys_socketcall(1, socketArgs);
|
}
|
||||||
|
else
|
||||||
|
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
|
||||||
print("Socket %d created\n", fd);
|
print("Socket %d created\n", fd);
|
||||||
|
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
if (!RunningAsService)
|
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
|
||||||
print("Failed to create socket: %d\n", fd);
|
print("Failed to create socket: %d\n", fd);
|
||||||
|
if (!RunningAsService)
|
||||||
|
MessageBox(NULL, "Failed to create socket",
|
||||||
|
NULL, MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,16 +535,20 @@ NewConnection:
|
|||||||
(LPVOID)&bt,
|
(LPVOID)&bt,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
|
|
||||||
HANDLE hOut = CreateThread(NULL, 0,
|
hOut = CreateThread(NULL, 0,
|
||||||
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
|
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
|
||||||
(LPVOID)&bt,
|
(LPVOID)&bt,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
|
|
||||||
if (hIn == NULL || hOut == NULL)
|
if (hIn == NULL || hOut == NULL)
|
||||||
{
|
{
|
||||||
if (!RunningAsService)
|
|
||||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
|
||||||
print("Failed to create threads: %s\n", GetErrorMessage());
|
print("Failed to create threads: %s\n", GetErrorMessage());
|
||||||
|
if (!RunningAsService)
|
||||||
|
{
|
||||||
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"Failed to create threads",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
|
}
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
build/bridge.sh
Executable file
32
build/bridge.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# This script is used to run Steam Play with the bridge.
|
||||||
|
# Usage: /path/to/bridge.sh %command%
|
||||||
|
# Original script: https://github.com/0e4ef622/wine-discord-ipc-bridge/blob/master/winediscordipcbridge-steam.sh
|
||||||
|
# As requested by https://github.com/EnderIce2/rpc-bridge/issues/2
|
||||||
|
|
||||||
|
# Exporting BRIDGE_PATH to provide the bridge with its location.
|
||||||
|
export BRIDGE_PATH="$(dirname "$0")/bridge.exe"
|
||||||
|
|
||||||
|
# The "--steam" option prevents the game from
|
||||||
|
# hanging as "running" in Steam after it is closed.
|
||||||
|
# This is done by creating a dummy service with
|
||||||
|
# startup type SERVICE_DEMAND_START so this service
|
||||||
|
# is only started when we use this script.
|
||||||
|
BRIDGE_CMD="$BRIDGE_PATH --steam"
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
TEMP_PATH="$XDG_RUNTIME_DIR"
|
||||||
|
# macOS but Steam Play is not supported on macOS https://github.com/ValveSoftware/Proton/issues/1344
|
||||||
|
TEMP_PATH=${TEMP_PATH:-"$TMPDIR"}
|
||||||
|
|
||||||
|
VESSEL_PATH="$BRIDGE_PATH"
|
||||||
|
IPC_PATHS="$TEMP_PATH /run/user/$UID $TEMP_PATH/snap.discord $TEMP_PATH/app/com.discordapp.Discord"
|
||||||
|
for discord_ipc in $IPC_PATHS; do
|
||||||
|
if [ -S "$discord_ipc"/discord-ipc-0 ]; then
|
||||||
|
VESSEL_PATH="$BRIDGE_PATH:$(echo "$discord_ipc"/discord-ipc-0)"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
PROTON_REMOTE_DEBUG_CMD="$BRIDGE_CMD" PRESSURE_VESSEL_FILESYSTEMS_RW="$VESSEL_PATH:$PRESSURE_VESSEL_FILESYSTEMS_RW" "$@"
|
9
game.c
9
game.c
@ -55,7 +55,8 @@ void LaunchGame(int argc, char **argv)
|
|||||||
if (!GetBinaryType(argv[1], &dwBinaryType))
|
if (!GetBinaryType(argv[1], &dwBinaryType))
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
"GetBinaryType",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
print("Executable type: %d\n", dwBinaryType);
|
print("Executable type: %d\n", dwBinaryType);
|
||||||
@ -104,7 +105,8 @@ void LaunchGame(int argc, char **argv)
|
|||||||
if (!CreateProcess(gamePath, gameArgs, NULL, NULL, FALSE,
|
if (!CreateProcess(gamePath, gameArgs, NULL, NULL, FALSE,
|
||||||
0, NULL, NULL, &game_si, &game_pi))
|
0, NULL, NULL, &game_si, &game_pi))
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"CreateProcess", MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
LocalFree(gameArgs);
|
LocalFree(gameArgs);
|
||||||
@ -136,7 +138,8 @@ void LaunchGame(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"CreateToolhelp32Snapshot", MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
241
gui.c
Normal file
241
gui.c
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include <winuser.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entire code could be better written, but at least it works.
|
||||||
|
*
|
||||||
|
* This will make installation and removal of the bridge WAY easier.
|
||||||
|
*/
|
||||||
|
|
||||||
|
LPTSTR GetErrorMessage();
|
||||||
|
void print(char const *fmt, ...);
|
||||||
|
void InstallService(int ServiceStartType, LPCSTR Path);
|
||||||
|
void RemoveService();
|
||||||
|
void CreateBridge();
|
||||||
|
extern BOOL IsLinux;
|
||||||
|
|
||||||
|
HWND hwnd = NULL;
|
||||||
|
|
||||||
|
VOID HandleStartButton(BOOL Silent)
|
||||||
|
{
|
||||||
|
if (!IsLinux)
|
||||||
|
{
|
||||||
|
ShowWindow(hwnd, SW_MINIMIZE);
|
||||||
|
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
||||||
|
NULL, 0, NULL);
|
||||||
|
|
||||||
|
HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
|
||||||
|
EnableWindow(item, FALSE);
|
||||||
|
item = GetDlgItem(hwnd, 4);
|
||||||
|
SetWindowText(item, "Bridge is running...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||||
|
if (hSCManager == NULL)
|
||||||
|
{
|
||||||
|
print("OpenSCManager failed: %s\n", GetErrorMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge",
|
||||||
|
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
|
||||||
|
if (schService == NULL)
|
||||||
|
{
|
||||||
|
print("OpenService failed: %s\n", GetErrorMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwBytesNeeded;
|
||||||
|
QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded);
|
||||||
|
LPQUERY_SERVICE_CONFIG lpqsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, dwBytesNeeded);
|
||||||
|
if (lpqsc == NULL)
|
||||||
|
{
|
||||||
|
print("LocalAlloc failed: %s\n", GetErrorMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QueryServiceConfig(schService, lpqsc, dwBytesNeeded, &dwBytesNeeded))
|
||||||
|
{
|
||||||
|
print("QueryServiceConfig failed: %s\n", GetErrorMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StartService(schService, 0, NULL) == FALSE)
|
||||||
|
{
|
||||||
|
if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
|
||||||
|
return;
|
||||||
|
print("StartService failed: %s\n", GetErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFree(lpqsc);
|
||||||
|
CloseServiceHandle(schService);
|
||||||
|
CloseServiceHandle(hSCManager);
|
||||||
|
if (Silent == FALSE)
|
||||||
|
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID HandleInstallButton()
|
||||||
|
{
|
||||||
|
char filename[MAX_PATH];
|
||||||
|
GetModuleFileName(NULL, filename, MAX_PATH);
|
||||||
|
CopyFile(filename, "C:\\windows\\bridge.exe", FALSE);
|
||||||
|
InstallService(SERVICE_AUTO_START, "C:\\windows\\bridge.exe --service");
|
||||||
|
MessageBox(NULL, "Bridge installed successfully", "Info", MB_OK);
|
||||||
|
HandleStartButton(TRUE);
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID HandleRemoveButton()
|
||||||
|
{
|
||||||
|
RemoveService();
|
||||||
|
MessageBox(NULL, "Bridge removed successfully", "Info", MB_OK);
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WM_COMMAND:
|
||||||
|
{
|
||||||
|
switch (LOWORD(wParam))
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
HandleStartButton(FALSE);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
HandleInstallButton();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
HandleRemoveButton();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WM_CLOSE:
|
||||||
|
DestroyWindow(hwnd);
|
||||||
|
break;
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
ExitProcess(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
|
||||||
|
{
|
||||||
|
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
|
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
|
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
|
|
||||||
|
if (!IsLinux)
|
||||||
|
{
|
||||||
|
*btnInstallStyle |= WS_DISABLED;
|
||||||
|
*btnRemoveStyle |= WS_DISABLED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||||
|
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
|
||||||
|
|
||||||
|
if (schService != NULL)
|
||||||
|
{
|
||||||
|
*btnInstallStyle |= WS_DISABLED;
|
||||||
|
|
||||||
|
SERVICE_STATUS_PROCESS ssStatus;
|
||||||
|
DWORD dwBytesNeeded;
|
||||||
|
assert(QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssStatus,
|
||||||
|
sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded));
|
||||||
|
|
||||||
|
if (ssStatus.dwCurrentState == SERVICE_RUNNING ||
|
||||||
|
ssStatus.dwCurrentState == SERVICE_START_PENDING)
|
||||||
|
*btnStartStyle |= WS_DISABLED;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*btnStartStyle |= WS_DISABLED;
|
||||||
|
*btnRemoveStyle |= WS_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseServiceHandle(schService);
|
||||||
|
CloseServiceHandle(hSCManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
||||||
|
LPSTR lpCmdLine, int nCmdShow)
|
||||||
|
{
|
||||||
|
INT btnStartStyle, btnRemoveStyle, btnInstallStyle;
|
||||||
|
SetButtonStyles(&btnStartStyle, &btnRemoveStyle, &btnInstallStyle);
|
||||||
|
|
||||||
|
const char szClassName[] = "BridgeWindowClass";
|
||||||
|
|
||||||
|
WNDCLASSEX wc;
|
||||||
|
wc.cbSize = sizeof(WNDCLASSEX);
|
||||||
|
wc.style = 0;
|
||||||
|
wc.lpfnWndProc = WndProc;
|
||||||
|
wc.cbClsExtra = 0;
|
||||||
|
wc.cbWndExtra = 0;
|
||||||
|
wc.hInstance = hInstance;
|
||||||
|
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
|
||||||
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||||
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||||
|
wc.lpszMenuName = NULL;
|
||||||
|
wc.lpszClassName = szClassName;
|
||||||
|
wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
|
||||||
|
|
||||||
|
assert(RegisterClassEx(&wc));
|
||||||
|
|
||||||
|
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,
|
||||||
|
szClassName,
|
||||||
|
"Discord RPC Bridge",
|
||||||
|
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
|
||||||
|
(GetSystemMetrics(SM_CXSCREEN) - 400) / 2,
|
||||||
|
(GetSystemMetrics(SM_CYSCREEN) - 150) / 2,
|
||||||
|
400, 150,
|
||||||
|
NULL, NULL, hInstance, NULL);
|
||||||
|
|
||||||
|
CreateWindow("STATIC", "Do you want to start, install or remove the bridge?",
|
||||||
|
WS_CHILD | WS_VISIBLE | SS_CENTER,
|
||||||
|
0, 0, 400, 50,
|
||||||
|
hwnd, (HMENU)4, hInstance, NULL);
|
||||||
|
|
||||||
|
CreateWindow("BUTTON", "Start",
|
||||||
|
btnStartStyle,
|
||||||
|
50, 50, 100, 30,
|
||||||
|
hwnd, (HMENU)1, hInstance, NULL);
|
||||||
|
|
||||||
|
CreateWindow("BUTTON", "Install",
|
||||||
|
btnInstallStyle,
|
||||||
|
150, 50, 100, 30,
|
||||||
|
hwnd, (HMENU)2, hInstance, NULL);
|
||||||
|
|
||||||
|
CreateWindow("BUTTON", "Remove",
|
||||||
|
btnRemoveStyle,
|
||||||
|
250, 50, 100, 30,
|
||||||
|
hwnd, (HMENU)3, hInstance, NULL);
|
||||||
|
|
||||||
|
ShowWindow(hwnd, nCmdShow);
|
||||||
|
UpdateWindow(hwnd);
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, NULL, 0, 0) > 0)
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
return msg.wParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CreateGUI()
|
||||||
|
{
|
||||||
|
ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
|
||||||
|
ExitProcess(WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL));
|
||||||
|
}
|
238
main.c
238
main.c
@ -6,11 +6,14 @@
|
|||||||
FILE *g_logFile = NULL;
|
FILE *g_logFile = NULL;
|
||||||
BOOL RunningAsService = FALSE;
|
BOOL RunningAsService = FALSE;
|
||||||
|
|
||||||
|
void CreateGUI();
|
||||||
void CreateBridge();
|
void CreateBridge();
|
||||||
void LaunchGame(int argc, char **argv);
|
void LaunchGame(int argc, char **argv);
|
||||||
void ServiceMain(int argc, char *argv[]);
|
void ServiceMain(int argc, char *argv[]);
|
||||||
void InstallService();
|
void InstallService(int ServiceStartType, LPCSTR Path);
|
||||||
|
char *native_getenv(const char *name);
|
||||||
void RemoveService();
|
void RemoveService();
|
||||||
|
extern BOOL IsLinux;
|
||||||
|
|
||||||
LPTSTR GetErrorMessage()
|
LPTSTR GetErrorMessage()
|
||||||
{
|
{
|
||||||
@ -45,13 +48,38 @@ void DetectWine()
|
|||||||
{
|
{
|
||||||
HMODULE hNTdll = GetModuleHandle("ntdll.dll");
|
HMODULE hNTdll = GetModuleHandle("ntdll.dll");
|
||||||
if (!hNTdll)
|
if (!hNTdll)
|
||||||
|
{
|
||||||
|
MessageBox(NULL, "Failed to load ntdll.dll",
|
||||||
|
GetErrorMessage(), MB_OK | MB_ICONERROR);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!GetProcAddress(hNTdll, "wine_get_version"))
|
if (!GetProcAddress(hNTdll, "wine_get_version"))
|
||||||
{
|
{
|
||||||
MessageBox(NULL, "This program is only intended to run under Wine.",
|
MessageBox(NULL, "This program is only intended to run under Wine.",
|
||||||
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
|
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
|
||||||
|
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);
|
||||||
|
if (strcmp(__sysname, "Linux") != 0 && strcmp(__sysname, "Darwin") != 0)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
else if (result == IDNO)
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
IsLinux = strcmp(__sysname, "Linux") == 0;
|
||||||
|
printf("Running on %s\n", __sysname);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print(char const *fmt, ...)
|
void print(char const *fmt, ...)
|
||||||
@ -65,19 +93,7 @@ void print(char const *fmt, ...)
|
|||||||
va_end(args);
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
void HandleArguments(int argc, char *argv[])
|
||||||
{
|
|
||||||
DetectWine();
|
|
||||||
char *logFilePath = "C:\\bridge.log";
|
|
||||||
g_logFile = fopen(logFilePath, "w");
|
|
||||||
if (g_logFile == NULL)
|
|
||||||
{
|
|
||||||
printf("Failed to open logs file: %ld\n",
|
|
||||||
GetLastError());
|
|
||||||
ExitProcess(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc > 1)
|
|
||||||
{
|
{
|
||||||
if (strcmp(argv[1], "--service") == 0)
|
if (strcmp(argv[1], "--service") == 0)
|
||||||
{
|
{
|
||||||
@ -93,17 +109,199 @@ int main(int argc, char *argv[])
|
|||||||
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
|
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
|
||||||
{
|
{
|
||||||
print("Service failed to start\n");
|
print("Service failed to start\n");
|
||||||
return GetLastError();
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
return 0;
|
}
|
||||||
|
else if (strcmp(argv[1], "--steam") == 0)
|
||||||
|
{
|
||||||
|
/* All this mess just so when you close the game,
|
||||||
|
it automatically closes the bridge and Steam
|
||||||
|
will not say that the game is still running. */
|
||||||
|
|
||||||
|
print("Running as Steam\n");
|
||||||
|
if (IsLinux == FALSE)
|
||||||
|
CreateBridge();
|
||||||
|
|
||||||
|
if (argc > 2)
|
||||||
|
{
|
||||||
|
if (strcmp(argv[2], "--no-service") == 0)
|
||||||
|
CreateBridge();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(argv[1], "--install") == 0)
|
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||||
InstallService();
|
if (hSCManager == NULL)
|
||||||
else if (strcmp(argv[1], "--uninstall") == 0)
|
{
|
||||||
RemoveService();
|
print("(Steam) OpenSCManager: %s\n", GetErrorMessage());
|
||||||
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge",
|
||||||
|
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
|
||||||
|
if (schService == NULL)
|
||||||
|
{
|
||||||
|
if (GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST)
|
||||||
|
{
|
||||||
|
print("(Steam) OpenService: %s\n", GetErrorMessage());
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("(Steam) Service does not exist, registering...\n");
|
||||||
|
|
||||||
|
WCHAR *(CDECL * wine_get_dos_file_name)(LPCSTR str) =
|
||||||
|
(void *)GetProcAddress(GetModuleHandleA("KERNEL32"),
|
||||||
|
"wine_get_dos_file_name");
|
||||||
|
|
||||||
|
char *unixPath = native_getenv("BRIDGE_PATH");
|
||||||
|
if (unixPath == NULL)
|
||||||
|
{
|
||||||
|
print("(Steam) BRIDGE_PATH not set\n");
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
WCHAR *dosPath = wine_get_dos_file_name(unixPath);
|
||||||
|
LPSTR asciiPath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, dosPath, -1, asciiPath, MAX_PATH, NULL, NULL);
|
||||||
|
|
||||||
|
strcat_s(asciiPath, MAX_PATH, " --service");
|
||||||
|
print("(Steam) Binary path: %s\n", asciiPath);
|
||||||
|
|
||||||
|
InstallService(SERVICE_DEMAND_START, asciiPath);
|
||||||
|
HeapFree(GetProcessHeap(), 0, asciiPath);
|
||||||
|
|
||||||
|
/* Create handle for StartService below */
|
||||||
|
print("(Steam) Service registered, opening handle...\n");
|
||||||
|
/* FIXME: For some reason here it freezes??? */
|
||||||
|
schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START);
|
||||||
|
if (schService == NULL)
|
||||||
|
{
|
||||||
|
print("(Steam) Cannot open service after creation: %s\n", GetErrorMessage());
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DWORD dwBytesNeeded;
|
||||||
|
QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded);
|
||||||
|
LPQUERY_SERVICE_CONFIG lpqsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, dwBytesNeeded);
|
||||||
|
if (lpqsc == NULL)
|
||||||
|
{
|
||||||
|
print("(Steam) LocalAlloc: %s\n", GetErrorMessage());
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!QueryServiceConfig(schService, lpqsc, dwBytesNeeded, &dwBytesNeeded))
|
||||||
|
{
|
||||||
|
print("(Steam) QueryServiceConfig: %s\n", GetErrorMessage());
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
WCHAR *(CDECL * wine_get_dos_file_name)(LPCSTR str) =
|
||||||
|
(void *)GetProcAddress(GetModuleHandleA("KERNEL32"),
|
||||||
|
"wine_get_dos_file_name");
|
||||||
|
|
||||||
|
char *unixPath = native_getenv("BRIDGE_PATH");
|
||||||
|
if (unixPath == NULL)
|
||||||
|
{
|
||||||
|
print("(Steam) BRIDGE_PATH not set\n");
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
WCHAR *dosPath = wine_get_dos_file_name(unixPath);
|
||||||
|
LPSTR asciiPath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
|
||||||
|
WideCharToMultiByte(CP_ACP, 0, dosPath, -1, asciiPath, MAX_PATH, NULL, NULL);
|
||||||
|
|
||||||
|
strcat_s(asciiPath, MAX_PATH, " --service");
|
||||||
|
print("(Steam) Binary path: %s\n", asciiPath);
|
||||||
|
|
||||||
|
if (strcmp(lpqsc->lpBinaryPathName, asciiPath) != 0)
|
||||||
|
{
|
||||||
|
print("(Steam) Service binary path is not correct, updating...\n");
|
||||||
|
ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
|
||||||
|
asciiPath, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
print("(Steam) Service binary path is correct\n");
|
||||||
|
HeapFree(GetProcessHeap(), 0, asciiPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("(Steam) Starting service...\n");
|
||||||
|
if (StartService(schService, 0, NULL) == FALSE)
|
||||||
|
{
|
||||||
|
if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
|
||||||
|
{
|
||||||
|
print("(Steam) Service is already running\n");
|
||||||
|
CloseServiceHandle(schService);
|
||||||
|
CloseServiceHandle(hSCManager);
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("StartService: %s\n", GetErrorMessage());
|
||||||
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"StartService",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("(Steam) Service started successfully, exiting...\n");
|
||||||
|
CloseServiceHandle(schService);
|
||||||
|
CloseServiceHandle(hSCManager);
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[1], "--install") == 0)
|
||||||
|
{
|
||||||
|
char filename[MAX_PATH];
|
||||||
|
GetModuleFileName(NULL, filename, MAX_PATH);
|
||||||
|
CopyFile(filename, "C:\\windows\\bridge.exe", FALSE);
|
||||||
|
|
||||||
|
InstallService(SERVICE_AUTO_START, "C:\\windows\\bridge.exe --service");
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[1], "--uninstall") == 0)
|
||||||
|
{
|
||||||
|
RemoveService();
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
else if (strcmp(argv[1], "--help") == 0)
|
||||||
|
{
|
||||||
|
printf("Usage:\n");
|
||||||
|
printf(" %s [args]\n\n", argv[0]);
|
||||||
|
|
||||||
|
printf("Arguments:\n");
|
||||||
|
printf(" --help Show this help\n\n");
|
||||||
|
|
||||||
|
printf(" --install Install service\n");
|
||||||
|
printf(" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n\n");
|
||||||
|
printf(" --uninstall Uninstall service\n");
|
||||||
|
|
||||||
|
printf(" This will remove the service and delete C:\\windows\\bridge.exe\n\n");
|
||||||
|
printf(" --steam Reserved for Steam\n");
|
||||||
|
|
||||||
|
printf(" This will start the service and exit (used with bridge.sh)\n\n");
|
||||||
|
printf(" --no-service Do not run as service\n");
|
||||||
|
|
||||||
|
printf(" (only for --steam)\n\n");
|
||||||
|
printf(" --service Reserved for service\n\n");
|
||||||
|
|
||||||
|
printf("Note: If no arguments are provided, the GUI will be shown instead\n");
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
DetectWine();
|
||||||
|
char *logFilePath = "C:\\windows\\logs\\bridge.log";
|
||||||
|
g_logFile = fopen(logFilePath, "w");
|
||||||
|
if (g_logFile == NULL)
|
||||||
|
{
|
||||||
|
printf("Failed to open logs file: %ld\n",
|
||||||
|
GetLastError());
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc > 1)
|
||||||
|
HandleArguments(argc, argv);
|
||||||
|
else
|
||||||
|
CreateGUI();
|
||||||
|
|
||||||
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
||||||
NULL, 0, NULL);
|
NULL, 0, NULL);
|
||||||
Sleep(500);
|
Sleep(500);
|
||||||
|
61
service.c
61
service.c
@ -8,6 +8,7 @@ SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
|||||||
void print(char const *fmt, ...);
|
void print(char const *fmt, ...);
|
||||||
void CreateBridge();
|
void CreateBridge();
|
||||||
LPTSTR GetErrorMessage();
|
LPTSTR GetErrorMessage();
|
||||||
|
extern BOOL IsLinux;
|
||||||
|
|
||||||
void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
|
void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
|
||||||
{
|
{
|
||||||
@ -83,47 +84,47 @@ void ServiceMain(DWORD argc, LPTSTR *argv)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallService()
|
void InstallService(int ServiceStartType, LPCSTR Path)
|
||||||
{
|
{
|
||||||
print("Registering to run on startup\n");
|
print("Registering service\n");
|
||||||
|
|
||||||
SC_HANDLE schSCManager, schService;
|
if (IsLinux == FALSE)
|
||||||
DWORD dwTagId;
|
|
||||||
|
|
||||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
|
||||||
if (schSCManager == NULL)
|
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
/* FIXME: I don't know how to get the TMPDIR without getenv */
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
MessageBox(NULL, "Registering as a service is not supported on macOS at the moment.",
|
||||||
|
"Unsupported", MB_OK | MB_ICONINFORMATION);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
schService =
|
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||||
CreateService(schSCManager,
|
if (schSCManager == NULL)
|
||||||
|
{
|
||||||
|
print("Failed to open service manager\n");
|
||||||
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
|
"OpenSCManager",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
|
ExitProcess(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD dwTagId;
|
||||||
|
SC_HANDLE schService = CreateService(schSCManager,
|
||||||
"rpc-bridge", "Wine RPC Bridge",
|
"rpc-bridge", "Wine RPC Bridge",
|
||||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||||
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
|
ServiceStartType, SERVICE_ERROR_NORMAL,
|
||||||
"C:\\bridge.exe --service",
|
Path, NULL, &dwTagId, NULL, NULL, NULL);
|
||||||
NULL, &dwTagId, NULL, NULL, NULL);
|
|
||||||
|
|
||||||
if (schService == NULL)
|
if (schService == NULL)
|
||||||
{
|
{
|
||||||
|
print("Failed to create service\n");
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
"CreateService",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
char filename[MAX_PATH];
|
|
||||||
GetModuleFileName(NULL, filename, MAX_PATH);
|
|
||||||
CopyFile(filename, "C:\\bridge.exe", FALSE);
|
|
||||||
|
|
||||||
print("Service installed successfully\n");
|
print("Service installed successfully\n");
|
||||||
CloseServiceHandle(schService);
|
CloseServiceHandle(schService);
|
||||||
}
|
|
||||||
|
|
||||||
CloseServiceHandle(schSCManager);
|
CloseServiceHandle(schSCManager);
|
||||||
ExitProcess(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoveService()
|
void RemoveService()
|
||||||
@ -133,11 +134,12 @@ void RemoveService()
|
|||||||
SC_HANDLE schSCManager, schService;
|
SC_HANDLE schSCManager, schService;
|
||||||
SERVICE_STATUS ssSvcStatus;
|
SERVICE_STATUS ssSvcStatus;
|
||||||
|
|
||||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||||
if (schSCManager == NULL)
|
if (schSCManager == NULL)
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
"OpenSCManager",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +149,8 @@ void RemoveService()
|
|||||||
if (schService == NULL)
|
if (schService == NULL)
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
"OpenService",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,13 +179,13 @@ void RemoveService()
|
|||||||
if (!DeleteService(schService))
|
if (!DeleteService(schService))
|
||||||
{
|
{
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
NULL, MB_OK | MB_ICONSTOP);
|
"DeleteService",
|
||||||
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteFile("C:\\bridge.exe");
|
DeleteFile("C:\\windows\\bridge.exe");
|
||||||
print("Service removed successfully\n");
|
print("Service removed successfully\n");
|
||||||
CloseServiceHandle(schService);
|
CloseServiceHandle(schService);
|
||||||
CloseServiceHandle(schSCManager);
|
CloseServiceHandle(schSCManager);
|
||||||
ExitProcess(0);
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user