17 Commits

10 changed files with 765 additions and 129 deletions

View File

@ -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"
} }
} }

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,9 @@
# Discord RPC Bridge for Wine # Discord RPC Bridge for Wine
![GitHub](https://img.shields.io/github/license/EnderIce2/rpc-bridge)
![GitHub All Releases](https://img.shields.io/github/downloads/EnderIce2/rpc-bridge/total)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge)
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`.
@ -13,12 +17,13 @@ This bridge takes advantage of the Windows service implementation in Wine, elimi
- [Discord RPC Bridge for Wine](#discord-rpc-bridge-for-wine) - [Discord RPC Bridge for Wine](#discord-rpc-bridge-for-wine)
- [Table of Contents](#table-of-contents) - [Table of Contents](#table-of-contents)
- [Installation \& Usage](#installation--usage) - [Installation \& Usage](#installation--usage)
- [Installing inside a prefix](#installing-inside-a-prefix) - [Installing inside a prefix](#installing-inside-a-prefix)
- [Wine (~/.wine)](#wine-wine) - [Wine (~/.wine)](#wine-wine)
- [Lutris](#lutris) - [Lutris](#lutris)
- [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`.

254
bridge.c
View File

@ -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);
unsigned long socketArgs[] = { if (IsLinux)
(unsigned long)fd, {
(unsigned long)&socketAddr, unsigned long socketArgs[] = {
sizeof(socketAddr)}; (unsigned long)fd,
(unsigned long)(intptr_t)&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");
unsigned long socketArgs[] = { int fd;
(unsigned long)AF_UNIX, if (IsLinux)
(unsigned long)SOCK_STREAM, {
0}; unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
int fd = sys_socketcall(1, socketArgs); (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); 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
View 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
View File

@ -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
View 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));
}

252
main.c
View File

@ -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,10 +93,202 @@ void print(char const *fmt, ...)
va_end(args); va_end(args);
} }
void HandleArguments(int argc, char *argv[])
{
if (strcmp(argv[1], "--service") == 0)
{
RunningAsService = TRUE;
print("Running as service\n");
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{"rpc-bridge", (LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL, NULL},
};
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
{
print("Service failed to start\n");
ExitProcess(1);
}
}
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();
}
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager == NULL)
{
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[]) int main(int argc, char *argv[])
{ {
DetectWine(); DetectWine();
char *logFilePath = "C:\\bridge.log"; char *logFilePath = "C:\\windows\\logs\\bridge.log";
g_logFile = fopen(logFilePath, "w"); g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL) if (g_logFile == NULL)
{ {
@ -78,31 +298,9 @@ int main(int argc, char *argv[])
} }
if (argc > 1) if (argc > 1)
{ HandleArguments(argc, argv);
if (strcmp(argv[1], "--service") == 0) else
{ CreateGUI();
RunningAsService = TRUE;
print("Running as service\n");
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{"rpc-bridge", (LPSERVICE_MAIN_FUNCTION)ServiceMain},
{NULL, NULL},
};
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
{
print("Service failed to start\n");
return GetLastError();
}
return 0;
}
if (strcmp(argv[1], "--install") == 0)
InstallService();
else if (strcmp(argv[1], "--uninstall") == 0)
RemoveService();
}
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge, CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
NULL, 0, NULL); NULL, 0, NULL);

View File

@ -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)
"rpc-bridge", "Wine RPC Bridge", {
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, print("Failed to open service manager\n");
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, MessageBox(NULL, GetErrorMessage(),
"C:\\bridge.exe --service", "OpenSCManager",
NULL, &dwTagId, NULL, NULL, NULL); MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
DWORD dwTagId;
SC_HANDLE schService = CreateService(schSCManager,
"rpc-bridge", "Wine RPC Bridge",
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
ServiceStartType, SERVICE_ERROR_NORMAL,
Path, 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");
CloseServiceHandle(schService);
}
print("Service installed successfully\n");
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);
} }