48 Commits
v1.0 ... v1.2

Author SHA1 Message Date
08feef776b Update version 2024-05-31 01:18:38 +03:00
8545467a85 Add .elf file to .gitignore 2024-05-30 05:33:28 +03:00
20728818d2 Update pipe creation parameters 2024-05-30 04:19:19 +03:00
751d9ab3b7 Add menu bar and tab navigation for GUI, --version command 2024-05-30 03:28:18 +03:00
b906c009f4 Update docs 2024-05-29 23:18:53 +03:00
38e620836e Update shebang to use bash instead of sh
"function" is a bash builtin
2024-05-29 23:11:14 +03:00
a640dd5af1 Remove unnecessary code for starting the bridge service 2024-05-29 23:06:16 +03:00
8fbe00b555 Display installation guide prompt for missing temp directory 2024-05-29 23:02:48 +03:00
bf98b8784c Fix formatting and remove unnecessary recursive rm's 2024-05-29 22:55:02 +03:00
3021f8e4ad Update installation instructions for MacOS 2024-05-29 21:01:05 +03:00
655caa2934 Update README.md 2024-05-29 20:35:52 +03:00
da2a006ffb Merge pull request #3 from OrigamingWasTaken/master
Add support for the windows service on MacOS
2024-05-29 20:32:54 +03:00
e61fbf556e Added github markdown link 2024-05-29 17:04:26 +02:00
2dce976038 Clearer instructions (pt1) 2024-05-29 17:02:13 +02:00
2fe1dd5ece Update README.md 2024-05-27 22:13:16 +02:00
c23af8300c Increase pipe buffer 2024-05-23 06:00:45 +03:00
14226489d7 Modified readme and script 2024-05-22 16:41:15 +02:00
66f3bc53f1 readme 2024-05-21 23:48:12 +02:00
dcd9cf520e Modified README and added warning in bridge.c 2024-05-21 23:46:19 +02:00
8dd5952a64 MacOS Fix 2024-05-21 23:39:51 +02:00
81aa5d8d18 Remove unused macro 2024-05-13 08:42:23 +03:00
3679108829 Add logo to docs site 2024-05-13 08:31:00 +03:00
77c3dabf79 Enable Visual Styles 2024-05-13 08:28:03 +03:00
a6a12720ad Update .vscode files 2024-05-13 08:03:15 +03:00
231658ff2b Update compiling instructions 2024-05-13 06:48:14 +03:00
6e99e11250 Add Stop option in GUI 2024-05-13 06:37:23 +03:00
8cd44da368 Use mmap() once 2024-05-13 06:25:37 +03:00
ac590912bc Merge GitHub workflows 2024-05-13 05:39:15 +03:00
1ba2faeb25 Add docs 2024-05-13 05:30:59 +03:00
bedd99a593 Let the bridge to start without being installed from GUI 2024-05-13 03:45:12 +03:00
e9f6b969b6 Add --rpc command to manually specify the location of discord-ipc-0 2024-05-13 02:39:19 +03:00
5d0e6c4026 Retry connection after EOF count exceeded 2024-05-07 22:09:04 +03:00
418e53e7f7 Update LICENSE 2024-04-18 17:30:22 +03:00
03d5e8dbc8 Add macOS support in GUI 2024-04-05 23:06:32 +03:00
e5a09024ff Add GUI creation if no command line arguments are provided 2024-04-05 22:39:14 +03:00
aeda375cba Update file paths 2024-04-05 22:36:05 +03:00
7af098ab26 Add script for Steam (#2) 2024-04-05 21:11:45 +03:00
7461479cbb Added captions to MessageBoxes 2024-04-04 04:05:53 +03:00
299408d10e Update Makefile to use x86_64-w64-mingw32-gcc 2024-03-07 06:48:54 +02:00
8b356b65b8 Add macOS support 2024-03-07 06:48:50 +02:00
66a20f51c4 Update platform check and add help option 2024-03-07 06:48:29 +02:00
d5af2277c5 Prevent installing as service on macOS 2024-03-07 06:47:56 +02:00
16291ec0fd Add macOS instructions 2024-03-07 06:47:35 +02:00
56168a6d3d Add socket system call constants 2024-01-26 03:35:34 +02:00
d2b23eb2e8 Check if it's running under Linux 2024-01-26 03:34:00 +02:00
cba4fa1e2b Update README.md 2023-12-17 01:25:19 +02:00
8c7b336c0e Update README.md 2023-10-13 19:20:39 +03:00
334c4e0479 Update README.md 2023-10-13 04:21:42 +03:00
37 changed files with 1438 additions and 240 deletions

46
.github/workflows/build-deploy.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Build and Deploy docs
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: dependencies
run: sudo apt-get update && sudo apt-get install gcc-mingw-w64 make
- name: make
run: make
- name: artifact
uses: actions/upload-artifact@v3
with:
name: bridge
path: build
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure Git Credentials
run: |
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force

View File

@ -1,24 +0,0 @@
name: C/C++ CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: dependencies
run: sudo apt-get update && sudo apt-get install gcc-mingw-w64 make
- name: make
run: make
- name: artifact
uses: actions/upload-artifact@v3
with:
name: bridge
path: build

4
.gitignore vendored
View File

@ -1,2 +1,4 @@
*.o *.o
*.exe *.exe
*.res
*.elf

View File

@ -8,7 +8,7 @@
"defines": [], "defines": [],
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "gnu++17", "cppStandard": "gnu++17",
"intelliSenseMode": "windows-gcc-x86" "intelliSenseMode": "windows-gcc-x64"
} }
], ],
"version": 4 "version": 4

View File

@ -1,8 +1,3 @@
{ {
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc", "C_Cpp.default.compilerPath": "/usr/bin/x86_64-w64-mingw32-gcc",
"files.associations": {
"*.su": "tsv",
"windows.h": "c",
"namedpipeapi.h": "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

@ -2,23 +2,20 @@ C_SOURCES = $(shell find ./ -type f -name '*.c')
C_OBJECTS = $(C_SOURCES:.c=.o) C_OBJECTS = $(C_SOURCES:.c=.o)
CFLAGS = -std=c17 -Wno-int-conversion CFLAGS = -std=c17 -Wno-int-conversion
LFLAGS = LFLAGS = -lgdi32
# DBGFLAGS = -Wl,--export-all-symbols -g -O0 -ggdb3 -Wall # DBGFLAGS = -Wl,--export-all-symbols -g -O0 -ggdb3 -Wall
all: build all: build
# This is valid only if this directory is a subdirectory of drive_c
install: build
cp build/bridge.exe ../bridge.exe
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-windres bridge.rc -O coff -o bridge.res
x86_64-w64-mingw32-gcc $(C_OBJECTS) bridge.res $(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 bridge.res

102
README.md
View File

@ -1,5 +1,9 @@
# Discord RPC Bridge for Wine # Discord RPC Bridge for Wine
![GitHub License](https://img.shields.io/github/license/EnderIce2/rpc-bridge?style=for-the-badge)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/EnderIce2/rpc-bridge/total?style=for-the-badge)
![GitHub Release](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge?style=for-the-badge)
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`.
@ -8,114 +12,64 @@ This bridge takes advantage of the Windows service implementation in Wine, elimi
--- ---
## Table of Contents
- [Discord RPC Bridge for Wine](#discord-rpc-bridge-for-wine)
- [Table of Contents](#table-of-contents)
- [Installation \& Usage](#installation--usage)
- [Installing inside a prefix](#installing-inside-a-prefix)
- [Wine (~/.wine)](#wine-wine)
- [Lutris](#lutris)
- [Steam](#steam)
- [If you use Flatpak](#if-you-use-flatpak)
- [Run without installing the service](#run-without-installing-the-service)
- [Compiling from source](#compiling-from-source)
- [Command line arguments](#command-line-arguments)
- [Debugging](#debugging)
- [Demo](#demo)
- [Credits](#credits)
## Installation & Usage ## Installation & Usage
Installation will place `bridge.exe` to `C:\bridge.exe` (if it's not already there) and create a Windows service. This service will automatically start when the prefix is used. Installation will copy itself to `C:\windows\bridge.exe` and create a Windows service.
Logs are stored in `C:\windows\logs\bridge.log`.
If you prefer not to use the service for any reason, please refer to the [Run without installing the service](#run-without-installing-the-service) section.
#### Installing inside a prefix #### Installing inside a prefix
##### Wine (~/.wine) ##### Wine (~/.wine)
- Open terminal in `build` - Double click `bridge.exe` and click `Install`.
- Run `$ wine cmd` and `C:\> install.bat` - ![gui](docs/assets/gui.png)
- ![wine install](imgs/wine_install.gif) - To remove, the same process can be followed, but click `Remove` instead.
- To remove, run `C:\> remove.bat`
- ![wine remove](imgs/wine_remove.gif) *Note, an [extra step](https://github.com/EnderIce2/rpc-bridge?tab=readme-ov-file#macos) is needed on MacOS*
- Note: Copying files are not required here.
##### Lutris ##### Lutris
- Right click on the game and select `Browse files` - Click on a game and select `Run EXE inside Wine prefix`.
- ![lutris browse](imgs/lutris_browse.gif) - ![lutris](docs/assets/lutris.png)
- Copy contents of `build` to the game's prefix `drive_c` - The same process can be followed as in Wine.
- ![lutris copy](imgs/lutris_copy.gif)
- To install open the console
- ![lutris console](imgs/lutris_console.gif)
- And run `C:\> install.bat` (make sure you are in `C:\`!)
- ![lutris install](imgs/lutris_install.gif)
##### Steam ##### Steam
- Open [Protontricks](https://github.com/Matoking/protontricks) and select the game you want to install the bridge to - Right click on the game and select `Properties`.
- Select `Select the default wineprefix` - Under `Set Launch Options`, add the following:
- Select `Browse files` and copy contents of `build` to the game's prefix `drive_c` - ![bridge.sh](docs/assets/steam_script.png "Set Launch Options to the path of the bridge.sh")
- Select `Run a Wine cmd shell` and run `C:\> install.bat` - The `bridge.sh` script must be in the same directory as `bridge.exe`.
- If you are not in `C:\`, type `c:` and press enter
- ![protontricks](imgs/steam_protontricks.png)
#### If you use Flatpak #### If you use Flatpak
- If you are running Steam, Lutris, etc. in a Flatpak, you will need to allow the bridge to access the `/run/user/1000/discord-ipc-0` file. - If you are running Steam, Lutris, etc in a Flatpak, you will need to allow the bridge to access the `/run/user/1000/discord-ipc-0` file.
- ##### By using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal) - ##### By using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal)
- Add `xdg-run/discord-ipc-0` under `Filesystems` category - Add `xdg-run/discord-ipc-0` under `Filesystems` category
- ![flatseal](imgs/flatseal_permission.png) - ![flatseal](docs/assets/flatseal_permission.png)
- ##### By using the terminal - ##### By using the terminal
- Per application - Per application
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>` - `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
- Globally - Globally
- `flatpak override --user --filesystem=xdg-run/discord-ipc-0` - `flatpak override --user --filesystem=xdg-run/discord-ipc-0`
## Run without installing the service ##### MacOS
If you prefer not to use the service, you can manually run `bridge.exe`` within the Wine prefix. The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you will have to install a **LaunchAgent**.
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. - Download the latest build from the [releases](https://github.com/EnderIce2/rpc-bridge/releases)
Example: - Open the archive and make the `launchd.sh` script executable by doing: `chmod +x launchd.sh`
- Without bridge: - To **install** the LaunchAgent, run `./launchd install` and to **remove** it simply run `./launchd remove`.
- Executable `/mnt/games/lutris/league-of-legends/drive_c/Riot Games/League of Legends/LeagueClient.exe`
- Arguments `--locale=en_US --launch-product=league_of_legends --launch-patchline=live`
- With bridge:
- Executable `/mnt/games/lutris/league-of-legends/drive_c/bridge.exe`
- Arguments `"C:\Riot Games\League of Legends\LeagueClient.exe" --locale=en_US --launch-product=league_of_legends --launch-patchline=live`
In Wine, all you need to do is run `bridge.exe`. The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
- When running the program manually without providing any arguments, it will simply initiate the bridge and wait indefinitely until it's closed. *Note: You will need to launch the `bridge.exe` file manually in Wine at least once for it to register and launch automatically the next time.*
## Compiling from source ## Compiling from source
- Install the `wine`, `i686-w64-mingw32-gcc` and `make` packages. - Install the `wine`, `gcc-mingw-w64` 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`.
## Command line arguments
- `--install` - Installs the service
- `--remove` - Removes the service
- `--service` - Reserved for the service
- `[Target Executable]` - Starts the bridge and the game
- Example: `bridge.exe "C:\Riot Games\League of Legends\LeagueClient.exe" --locale=en_US --launch-product=league_of_legends --launch-patchline=live`
- Note: The game executable must be enclosed in quotes. The rest of the arguments are passed to the target executable.
## Debugging
The bridge will write the logs in `C:\bridge.log`.
## Demo
![League of Legends running under Wine](imgs/lutris_lol.png)
![Among Us running from Steam](imgs/steam_amongus.png)
## Credits ## Credits
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge). This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).

306
bridge.c
View File

@ -4,22 +4,50 @@
#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))
#define BUFFER_LENGTH 2048
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 +64,13 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage(); LPTSTR GetErrorMessage();
extern BOOL RunningAsService; extern BOOL RunningAsService;
BOOL RetryNewConnection; BOOL RetryNewConnection;
BOOL IsLinux;
HANDLE hOut = NULL;
HANDLE hIn = NULL;
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 +79,137 @@ 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);
}
void *environStr = NULL;
char *native_getenv(const char *name)
{
static char lpBuffer[512];
DWORD ret = GetEnvironmentVariable("BRIDGE_RPC_PATH", lpBuffer, sizeof(lpBuffer));
if (ret != 0)
return lpBuffer;
if (!IsLinux)
{
char *value = getenv(name);
if (value == NULL)
{
print("Failed to get environment variable: %s\n", name);
/* Use GetEnvironmentVariable as a last resort */
DWORD ret = GetEnvironmentVariable(name, lpBuffer, sizeof(lpBuffer));
if (ret == 0)
{
print("GetEnvironmentVariable(\"%s\", ...) failed: %d\n", name, ret);
return NULL;
}
return lpBuffer;
}
return value;
}
/* I hope the 0x20000 is okay */
if (environStr == NULL)
{
environStr = sys_mmap(0x20000, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
print("Allocated 4096 bytes at %#lx\n", environStr);
}
if ((uintptr_t)environStr > 0x7effffff)
print("Warning: environStr %#lx 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 +250,38 @@ 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 (runtime == NULL) if (IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR");
else
{ {
print("XDG_RUNTIME_DIR not set\n"); runtime = native_getenv("TMPDIR");
if (!RunningAsService) if (runtime == NULL)
{ {
MessageBox(NULL, runtime = "/tmp/rpc-bridge/tmpdir";
"XDG_RUNTIME_DIR not set", print("IPC directory not set, fallback to /tmp/rpc-bridge/tmpdir\n");
"Environment variable not set",
MB_OK | MB_ICONSTOP); // Check if the directory exists
DWORD dwAttrib = GetFileAttributes(runtime);
if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
{
print("IPC directory does not exist: %s. If you're on MacOS, see the github guide on how to install the launchd service.\n", runtime);
// Handle the case where the directory doesn't exist
// For example, create the directory
int result = MessageBox(NULL, "IPC directory does not exist\nDo you want to open the installation guide?",
"Directory not found",
MB_YESNO | MB_ICONSTOP);
if (result == IDYES)
ShellExecute(NULL, "open", "https://enderice2.github.io/rpc-bridge/installation.html#macos", NULL, NULL, SW_SHOWNORMAL);
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 +302,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,12 +323,9 @@ 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);
} }
} }
@ -187,9 +335,10 @@ 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[BUFFER_LENGTH];
int read = sys_read(bt->fd, buffer, sizeof(buffer)); int read = sys_read(bt->fd, buffer, sizeof(buffer));
if (unlikely(read < 0)) if (unlikely(read < 0))
@ -200,12 +349,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++)
@ -274,7 +433,7 @@ void PipeBufferOutThread(LPVOID lpParam)
bt->fd, bt->hPipe); bt->fd, bt->hPipe);
while (TRUE) while (TRUE)
{ {
char buffer[1024]; char buffer[BUFFER_LENGTH];
DWORD dwRead; DWORD dwRead;
if (unlikely(!ReadFile(bt->hPipe, buffer, sizeof(buffer), if (unlikely(!ReadFile(bt->hPipe, buffer, sizeof(buffer),
@ -330,11 +489,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);
} }
@ -342,15 +504,18 @@ NewConnection:
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0", CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
PIPE_ACCESS_DUPLEX, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1, 1024, 1024, 0, NULL); PIPE_UNLIMITED_INSTANCES, BUFFER_LENGTH, BUFFER_LENGTH, 0, NULL);
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 +523,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);
} }
@ -390,21 +560,27 @@ NewConnection:
bridge_thread bt = {fd, hPipe}; bridge_thread bt = {fd, hPipe};
HANDLE hIn = CreateThread(NULL, 0, hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferInThread, (LPTHREAD_START_ROUTINE)PipeBufferInThread,
(LPVOID)&bt, (LPVOID)&bt,
0, NULL); 0, NULL);
print("Created in thread %#lx\n", hIn);
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);
print("Created out thread %#lx\n", hOut);
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);
} }

BIN
bridge.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

32
bridge.manifest Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="amd64"
name="EnderIce2.rpc-bridge"
type="win32"
/>
<description>Simple bridge that allows you to use Discord Rich Presence with Wine games/software.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="false"
/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

53
bridge.rc Normal file
View File

@ -0,0 +1,53 @@
#include <windef.h>
#include <winuser.h>
#include <winresrc.h>
#include "resource.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_VERSION
PRODUCTVERSION VER_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Simple bridge that allows you to use Discord Rich Presence with Wine games/software."
VALUE "FileVersion", VER_VERSION_STR
VALUE "InternalName", "bridge"
VALUE "LegalCopyright", "Copyright (c) 2024 EnderIce2"
VALUE "OriginalFilename", "bridge.exe"
VALUE "ProductName", "rpc-bridge"
VALUE "ProductVersion", VER_VERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
IDR_MAINMENU MENU
BEGIN
POPUP "&View"
BEGIN
MENUITEM "&Log", IDM_VIEW_LOG
END
POPUP "&Help"
BEGIN
MENUITEM "&Documentation", IDM_HELP_DOCUMENTATION
MENUITEM "&License", IDM_HELP_LICENSE
MENUITEM "&About", IDM_HELP_ABOUT
END
END
IDR_LICENSE_TXT RCDATA "LICENSE"
IDI_ICON_128 ICON "bridge.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST bridge.manifest

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

83
build/launchd.sh Executable file
View File

@ -0,0 +1,83 @@
#!/bin/bash
# This script is used to create a LaunchAgent on MacOS, to support the service functionality.
# Usage: ./launchd.sh (install|remove)
SYMLINK=/tmp/rpc-bridge/tmpdir
LOCATION=~/Library/Application\ Support/rpc-bridge
SCRIPT=$LOCATION/rpc-bridge
AGENT=~/Library/LaunchAgents/com.enderice2.rpc-bridge.plist
function install() {
# Directories
if [ ! -d "$SYMLINK" ]; then
mkdir -p "$SYMLINK"
fi
if [ ! -d "$LOCATION" ]; then
mkdir -p "$LOCATION"
fi
# Link script
if [ -f "$SCRIPT" ]; then
rm -f "$SCRIPT"
fi
echo "#!/bin/bash
TARGET_DIR=/tmp/rpc-bridge/tmpdir
if [ ! -d "\$TARGET_DIR" ]; then
mkdir -p "\$TARGET_DIR"
fi
rm -rf "\$TARGET_DIR"
ln -s "\$TMPDIR" "\$TARGET_DIR"" > "$SCRIPT"
chmod +x "$SCRIPT"
# LaunchAgent
if [ -f "$AGENT" ]; then
rm -f "$AGENT"
fi
echo "<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST File Format//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.enderice2.rpc-bridge</string>
<key>ProgramArguments</key>
<array>
<string>$SCRIPT</string>
</array>
<key>RunAtLoad</key>
<true />
</dict>
</plist>" > "$AGENT"
launchctl load "$AGENT"
echo "LaunchAgent has been installed."
}
function remove() {
rm -f "$SYMLINK"
rm -f "$SCRIPT"
rmdir "$LOCATION"
if [ -f "$AGENT" ]; then
launchctl unload "$AGENT"
fi
rm -f "$AGENT"
echo "LaunchAgent has been removed."
}
# CLI
if [ $# -eq 0 ]; then
echo "Usage: $0 (install|remove)"
exit 1
fi
case $1 in
install)
install
;;
remove)
remove
;;
*)
echo "Invalid argument. Please use 'install' or 'remove'."
exit 1
;;
esac

BIN
docs/assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
docs/assets/gui.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/assets/lutris.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 160 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

33
docs/index.md Normal file
View File

@ -0,0 +1,33 @@
# Discord RPC Bridge for Wine
![GitHub License](https://img.shields.io/github/license/EnderIce2/rpc-bridge?style=for-the-badge)
![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/EnderIce2/rpc-bridge/total?style=for-the-badge)
![GitHub Release](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge?style=for-the-badge)
![GitHub Pre-Release](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge?include_prereleases&style=for-the-badge&label=pre-release)
Simple bridge that allows you to use Discord Rich Presence with Wine games/software on Linux/macOS.
[Download latest release](https://github.com/EnderIce2/rpc-bridge/releases/latest/download/bridge.zip "Recommended"){ .md-button .md-button--primary }
[Download latest pre-release](https://github.com/EnderIce2/rpc-bridge/releases "Unstable builds with experimental features"){ .md-button }
<!-- [Download latest build](https://github.com/EnderIce2/rpc-bridge/actions "Builds from the latest commits, here be dragons!"){ .md-button } -->
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`.
This bridge takes advantage of the Windows service implementation in Wine, eliminating the need to run it manually.
These docs are for the latest stable release.
For v1.0, see [the original README](https://github.com/EnderIce2/rpc-bridge/blob/v1.0/README.md).
---
## Examples
![lol](assets/lutris_lol.png "League Of Legends running under Wine using Lutris"){ width="600" }
![among us](assets/steam_amongus.png "Among Us on Steam"){ width="600" }
## Credits
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
---

111
docs/installation.md Normal file
View File

@ -0,0 +1,111 @@
# Installation
Installation will copy itself to `C:\windows\bridge.exe` and create a Windows service.
Logs are stored in `C:\windows\logs\bridge.log`.
## Installing inside a prefix
### Wine (~/.wine)
- Double click `bridge.exe` and click `Install`.
- ![gui](assets/gui.png "rpc-bridge GUI")
- To remove, the same process can be followed, but click `Remove` instead.
### Lutris
- Click on a game and select `Run EXE inside Wine prefix`.
- ![lutris](assets/lutris.png "Lutris")
- The same process can be followed as in Wine.
### Steam
There are two ways to install the bridge on Steam.
#### Using bridge.sh[^1]
This method is recommended because it's easier to manage.
- Right click on the game and select `Properties`.
- Under `Set Launch Options`, add the following:
- ![bridge.sh](assets/steam_script.png "Set Launch Options to the path of the bridge.sh")
<sup><sub>Of course, you need to replace `/path/to/bridge.sh` with the actual path to the script.</sub></sup>
!!! info "Note"
`bridge.sh` must be in the same directory as `bridge.exe`.
#### Using Protontricks
- Open [Protontricks](https://github.com/Matoking/protontricks) and select the game you want to install the bridge to.
- Select `Select the default wineprefix`
- Select `Browse files` and copy contents of `build` to the game's prefix `drive_c`
- Select `Run a Wine cmd shell` and run `C:\> install.bat`
- If you are not in `C:\`, type `c:` and press enter
![protontricks](assets/steam_protontricks.png "If use have the option for 'Run an arbitrary executable (.exe/.msi/.msu), use it instead!")
!!! warning "If you use Flatpak"
If you are running Steam, Lutris, etc in a Flatpak, you will need to allow the bridge to access the `/run/user/1000/discord-ipc-0` file.
You can do this by using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal) or the terminal.
=== "Flatseal"
Add `xdg-run/discord-ipc-0` under `Filesystems` category
![flatseal](assets/flatseal_permission.png)
=== "Terminal"
- Per application
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
- Globally
- `flatpak override --user --filesystem=xdg-run/discord-ipc-0`
## macOS
The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you will have to install a **LaunchAgent**.
- Download the latest build from the [releases](https://github.com/EnderIce2/rpc-bridge/releases)
- Open the archive and make the `launchd.sh` script executable by doing: `chmod +x launchd.sh`
- To **install** the LaunchAgent, run `./launchd install` and to **remove** it simply run `./launchd remove`.
The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
!!! info "Note"
You will need to launch the `bridge.exe` file manually in Wine at least once for it to register and launch automatically the next time.
## Run without installing the service
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.
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.
=== "Without bridge"
```
Executable
/mnt/games/lutris/league-of-legends/drive_c/Riot Games/League of Legends/LeagueClient.exe
Arguments
--locale=en_US --launch-product=league_of_legends --launch-patchline=live
```
=== "With bridge"
```
Executable
/mnt/games/lutris/league-of-legends/drive_c/bridge.exe
Arguments
"C:\Riot Games\League of Legends\LeagueClient.exe" --locale=en_US --launch-product=league_of_legends --launch-patchline=live
```
In Wine, all you need to do is run `bridge.exe` and select `Start`.
## Compiling from source
- Install the `wine`, `gcc-mingw-w64` 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`.
[^1]: As requested [here](https://github.com/EnderIce2/rpc-bridge/issues/2).

35
docs/usage.md Normal file
View File

@ -0,0 +1,35 @@
# Usage
## GUI
- When running the program manually without providing any arguments it will show a GUI.
![gui](assets/gui.png "rpc-bridge GUI")
- `Start` will start the service without installing itself.
- `Install` will install the service.
- `Remove` will uninstall the service.
## Commands
- `--help` Show help message
- This will show the help message
- `--version` Show version
- This will show the version of the program
- `--install` Install the service
- This will copy the binary to `C:\windows\bridge.exe` and register it as a service
- `--uninstall` Uninstall the service
- This will remove the service and delete `C:\windows\bridge.exe`
- `--steam` Reserved for Steam
- This will start the service and exit (used with `bridge.sh`)
- `--no-service` Do not run as service
- (only for `--steam`)
- `--service` Reserved for service
- Reserved
- `--rpc <dir>` Set RPC_PATH environment variable
- This is used to specify the directory where `discord-ipc-0` is located

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

355
gui.c Normal file
View File

@ -0,0 +1,355 @@
#include <windowsx.h>
#include <windows.h>
#include <winuser.h>
#include <assert.h>
#include <stdio.h>
#include "resource.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;
HANDLE hBridge = NULL;
extern HANDLE hOut;
extern HANDLE hIn;
BOOL IsAlreadyRunning = FALSE;
VOID HandleStartButton(BOOL Silent)
{
if (IsAlreadyRunning)
{
HWND item = GetDlgItem(hwnd, 4);
SetWindowText(item, "Do you want to start, install or remove the bridge?");
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "&Start");
EnableWindow(item, FALSE);
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
print("Killing %#x, %#lx and waiting for %#lx\n", hIn, hOut, hBridge);
if (hIn != NULL)
TerminateThread(hIn, 0);
if (hOut != NULL)
TerminateThread(hOut, 0);
WaitForSingleObject(hBridge, INFINITE);
EnableWindow(item, TRUE);
IsAlreadyRunning = FALSE;
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("Service doesn't exist: %s\n", GetErrorMessage());
/* Service doesn't exist; running without any service */
hBridge = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
NULL, 0, NULL);
HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "&Stop");
item = GetDlgItem(hwnd, 4);
SetWindowText(item, "Bridge is running...");
IsAlreadyRunning = TRUE;
ShowWindow(hwnd, SW_MINIMIZE);
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);
print("Bridge service started successfully\n");
}
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);
}
void ShowLicenseDialog()
{
HMODULE hModule = GetModuleHandle(NULL);
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_LICENSE_TXT), RT_RCDATA);
if (!hRes)
{
MessageBox(NULL, "Resource not found", "Error", MB_OK | MB_ICONERROR);
return;
}
HGLOBAL hResData = LoadResource(NULL, hRes);
if (!hResData)
{
MessageBox(NULL, "Resource failed to load", "Error", MB_OK | MB_ICONERROR);
return;
}
DWORD resSize = SizeofResource(NULL, hRes);
void *pRes = LockResource(hResData);
if (!pRes)
{
MessageBox(NULL, "Resource failed to lock", "Error", MB_OK | MB_ICONERROR);
return;
}
char *licenseText = (char *)malloc(resSize + 1);
if (!licenseText)
{
MessageBox(NULL, "Memory allocation failed", "Error", MB_OK | MB_ICONERROR);
return;
}
memcpy(licenseText, pRes, resSize);
licenseText[resSize] = '\0';
MessageBoxA(hwnd, licenseText, "About", MB_OK);
free(licenseText);
}
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;
case IDM_VIEW_LOG:
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", "C:\\windows\\logs\\bridge.log", NULL, SW_SHOW);
break;
case IDM_HELP_DOCUMENTATION:
ShellExecute(NULL, "open", "https://enderice2.github.io/rpc-bridge/index.html", NULL, NULL, SW_SHOWNORMAL);
break;
case IDM_HELP_LICENSE:
ShowLicenseDialog();
break;
case IDM_HELP_ABOUT:
{
char msg[256];
sprintf(msg, "rpc-bridge v%s\n\n"
"Simple bridge that allows you to use Discord Rich Presence with Wine games/software.\n\n"
"Created by EnderIce2\n\n"
"Licensed under the MIT License",
VER_VERSION_STR);
MessageBox(NULL, msg, "About", MB_OK);
break;
}
default:
break;
}
break;
}
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
ExitProcess(0);
break;
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC)wParam;
SetBkMode(hdcStatic, TRANSPARENT);
return (INT_PTR)(HBRUSH)GetStockObject(NULL_BRUSH);
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
{
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
// 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
*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_WINDOWEDGE,
szClassName,
"Discord RPC Bridge",
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
(GetSystemMetrics(SM_CXSCREEN) - 400) / 2,
(GetSystemMetrics(SM_CYSCREEN) - 150) / 2,
400, 150,
NULL, NULL, hInstance, NULL);
HICON hIcon = LoadIcon(hInstance, "IDI_ICON_128");
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
HWND hLbl4 = CreateWindowEx(WS_EX_TRANSPARENT,
"STATIC", "Do you want to start, install or remove the bridge?",
WS_CHILD | WS_VISIBLE | SS_CENTER,
0, 15, 400, 25,
hwnd, (HMENU)4, hInstance, NULL);
HWND hbtn1 = CreateWindow("BUTTON", "&Start",
btnStartStyle,
40, 60, 100, 30,
hwnd, (HMENU)1, hInstance, NULL);
HWND hbtn2 = CreateWindow("BUTTON", "&Install",
btnInstallStyle,
150, 60, 100, 30,
hwnd, (HMENU)2, hInstance, NULL);
HWND hbtn3 = CreateWindow("BUTTON", "&Remove",
btnRemoveStyle,
260, 60, 100, 30,
hwnd, (HMENU)3, hInstance, NULL);
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINMENU));
SetMenu(hwnd, hMenu);
HDC hDC = GetDC(hwnd);
int nHeight = -MulDiv(11, GetDeviceCaps(hDC, LOGPIXELSY), 72);
HFONT hFont = CreateFont(nHeight, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE, TEXT("Segoe UI"));
ReleaseDC(hwnd, hDC);
SendMessage(hwnd, WM_SETFONT, hFont, TRUE);
SendMessage(hLbl4, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn1, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn2, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn3, WM_SETFONT, hFont, TRUE);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
void CreateGUI()
{
ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
ExitProcess(__WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL));
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

281
main.c
View File

@ -3,14 +3,19 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "resource.h"
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 +50,35 @@ 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 == IDNO)
ExitProcess(1);
}
IsLinux = strcmp(__sysname, "Linux") == 0;
} }
void print(char const *fmt, ...) void print(char const *fmt, ...)
@ -65,10 +92,227 @@ 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], "--rpc") == 0)
{
if (argc < 3)
{
print("No directory provided\n");
ExitProcess(1);
}
SetEnvironmentVariable("BRIDGE_RPC_PATH", argv[2]);
print("BRIDGE_RPC_PATH has been set to \"%s\"\n", argv[2]);
CreateBridge();
ExitProcess(0);
}
else if (strcmp(argv[1], "--version") == 0)
{
printf("%s\n", VER_VERSION_STR);
ExitProcess(0);
}
else if (strcmp(argv[1], "--help") == 0)
{
printf("Usage:\n"
" %s [args]\n"
"\n"
"Arguments:\n"
" --help Show this help\n"
"\n"
" --version Show version\n"
"\n"
" --install Install service\n"
" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n"
"\n"
" --uninstall Uninstall service\n"
" This will remove the service and delete C:\\windows\\bridge.exe\n"
"\n"
" --steam Reserved for Steam\n"
" This will start the service and exit (used with bridge.sh)\n"
"\n"
" --no-service Do not run as service\n"
" (only for --steam)\n"
"\n"
" --service Reserved for service\n"
"\n"
" --rpc <dir> Set RPC_PATH environment variable\n"
" This is used to specify the directory where 'discord-ipc-0' is located\n"
"\n"
"Note: If no arguments are provided, the GUI will be shown instead\n",
argv[0]);
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 +322,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);
@ -112,3 +334,8 @@ int main(int argc, char *argv[])
fclose(g_logFile); fclose(g_logFile);
ExitProcess(0); ExitProcess(0);
} }
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
return main(__argc, __argv);
}

76
mkdocs.yml Normal file
View File

@ -0,0 +1,76 @@
site_name: rpc-bridge
repo_url: https://github.com/EnderIce2/rpc-bridge
repo_name: EnderIce2/rpc-bridge
theme:
name: material
features:
- content.code.copy
- content.tabs.link
- navigation.tabs
- navigation.top
- navigation.footer
- toc.integrate
- content.tooltips
palette:
- media: "(prefers-color-scheme)"
toggle:
icon: material/weather-sunny
name: Switch to light mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: indigo
toggle:
icon: material/brightness-4
name: Switch to system preference
- media: "(prefers-color-scheme: light)"
scheme: default
primary: indigo
accent: indigo
toggle:
icon: material/brightness-7
name: Switch to dark mode
font:
text: Roboto
code: Roboto Mono
favicon: assets/favicon.png
logo: assets/favicon.png
icon:
logo: logo
admonition:
note: octicons/tag-16
abstract: octicons/checklist-16
info: octicons/info-16
tip: octicons/squirrel-16
success: octicons/check-16
question: octicons/question-16
warning: octicons/alert-16
failure: octicons/x-circle-16
danger: octicons/zap-16
bug: octicons/bug-16
example: octicons/beaker-16
quote: octicons/quote-16
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
- admonition
- pymdownx.details
- pymdownx.inlinehilite
- pymdownx.snippets
- footnotes
- attr_list
- pymdownx.critic
- pymdownx.caret
- pymdownx.keys
- pymdownx.mark
- pymdownx.tilde
- pymdownx.tabbed:
alternate_style: true
plugins:
- offline
nav:
- Home: index.md
- Installation: installation.md
- Usage: usage.md

9
resource.h Normal file
View File

@ -0,0 +1,9 @@
#define IDR_MAINMENU 101
#define IDR_LICENSE_TXT 102
#define IDM_HELP_DOCUMENTATION 40001
#define IDM_HELP_LICENSE 40002
#define IDM_HELP_ABOUT 40003
#define IDM_VIEW_LOG 40004
#define VER_VERSION 1, 2, 0, 0
#define VER_VERSION_STR "1.2\0"

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; // {
// /* 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);
// }
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL) if (schSCManager == NULL)
{ {
print("Failed to open service manager\n");
MessageBox(NULL, GetErrorMessage(), MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP); "OpenSCManager",
MB_OK | MB_ICONSTOP);
ExitProcess(1); ExitProcess(1);
} }
schService = DWORD dwTagId;
CreateService(schSCManager, 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");
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);
} }