29 Commits

Author SHA1 Message Date
3ac5605a52 Implement experimental bridge for kvm virtual machines 2024-05-30 05:32:56 +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
36 changed files with 1142 additions and 285 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

2
.gitignore vendored
View File

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

View File

@ -8,7 +8,18 @@
"defines": [], "defines": [],
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "gnu++17", "cppStandard": "gnu++17",
"intelliSenseMode": "windows-gcc-x86" "intelliSenseMode": "windows-gcc-x64"
},
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64",
"compilerPath": "/usr/bin/gcc"
} }
], ],
"version": 4 "version": 4

11
.vscode/settings.json vendored
View File

@ -1,12 +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",
"thread": "cpp",
"bitset": "cpp",
"initializer_list": "cpp",
"windows.h": "c",
"namedpipeapi.h": "c",
"*.rh": "c"
}
} }

View File

@ -2,23 +2,21 @@ 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)
x86_64-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
gcc kvm.c -o build/rpc-bridge.elf
%.o: %.c %.o: %.c
$(info Compiling $<) $(info Compiling $<)
x86_64-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

111
README.md
View File

@ -1,8 +1,8 @@
# Discord RPC Bridge for Wine # Discord RPC Bridge for Wine
![GitHub](https://img.shields.io/github/license/EnderIce2/rpc-bridge) ![GitHub License](https://img.shields.io/github/license/EnderIce2/rpc-bridge?style=for-the-badge)
![GitHub All Releases](https://img.shields.io/github/downloads/EnderIce2/rpc-bridge/total) ![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/EnderIce2/rpc-bridge/total?style=for-the-badge)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge) ![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.
@ -12,121 +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)
- [About macOS](#about-macos)
- [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.*
## 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`, `x86_64-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).

293
bridge.c
View File

@ -4,6 +4,8 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#define __linux_read 3 #define __linux_read 3
#define __linux_write 4 #define __linux_write 4
#define __linux_open 5 #define __linux_open 5
@ -43,6 +45,8 @@
__attribute__((__always_inline__, __gnu_inline__)) __attribute__((__always_inline__, __gnu_inline__))
#define naked __attribute__((naked)) #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 char *caddr_t;
typedef unsigned socklen_t; typedef unsigned socklen_t;
@ -62,7 +66,9 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage(); LPTSTR GetErrorMessage();
extern BOOL RunningAsService; extern BOOL RunningAsService;
BOOL RetryNewConnection; BOOL RetryNewConnection;
BOOL IsLinux; OS_INFO OSInfo = {0};
HANDLE hOut = NULL;
HANDLE hIn = NULL;
static force_inline int linux_syscall(int num, static force_inline int linux_syscall(int num,
int arg1, int arg2, int arg3, int arg1, int arg2, int arg3,
@ -98,7 +104,7 @@ static naked int darwin_syscall(int num,
static inline int sys_read(int fd, void *buf, size_t count) static inline int sys_read(int fd, void *buf, size_t count)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0); return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else else
return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0); return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0);
@ -106,7 +112,7 @@ static inline int sys_read(int fd, void *buf, size_t count)
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)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0); return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else else
return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0); return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0);
@ -114,7 +120,7 @@ static inline int sys_write(int fd, const void *buf, size_t count)
static inline int sys_open(const char *pathname, int flags, int mode) static inline int sys_open(const char *pathname, int flags, int mode)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0); return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else else
return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0); return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0);
@ -122,7 +128,7 @@ static inline int sys_open(const char *pathname, int flags, int mode)
static inline int sys_close(int fd) static inline int sys_close(int fd)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0); return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else else
return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0); return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0);
@ -130,25 +136,25 @@ static inline int sys_close(int fd)
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset) static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset); return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
} }
static inline int sys_munmap(unsigned int *addr, size_t length) static inline int sys_munmap(unsigned int *addr, size_t length)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0); 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)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0); return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0);
} }
static inline int sys_socket(int domain, int type, int protocol) static inline int sys_socket(int domain, int type, int protocol)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0); return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0);
else else
return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0); return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0);
@ -156,12 +162,13 @@ static inline int sys_socket(int domain, int type, int protocol)
static inline int sys_connect(int s, caddr_t name, socklen_t namelen) static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0); return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0); return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
} }
void *environStr = NULL;
char *native_getenv(const char *name) char *native_getenv(const char *name)
{ {
static char lpBuffer[512]; static char lpBuffer[512];
@ -169,7 +176,7 @@ char *native_getenv(const char *name)
if (ret != 0) if (ret != 0)
return lpBuffer; return lpBuffer;
if (!IsLinux) if (!OSInfo.IsLinux)
{ {
char *value = getenv(name); char *value = getenv(name);
if (value == NULL) if (value == NULL)
@ -188,24 +195,23 @@ char *native_getenv(const char *name)
return value; return value;
} }
/* I hope the 0x10000 is okay */ /* I hope the 0x20000 is okay */
void *environStr = sys_mmap(0x10000, 4096, PROT_READ | PROT_WRITE, if (environStr == NULL)
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
if (environStr == MAP_FAILED)
{ {
print("Failed to allocate environ string: %d\n", (intptr_t)environStr); environStr = sys_mmap(0x20000, 4096, PROT_READ | PROT_WRITE,
return NULL; MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
print("Allocated 4096 bytes at %#lx\n", environStr);
} }
if ((uintptr_t)environStr > 0x7effffff) if ((uintptr_t)environStr > 0x7effffff)
print("Warning: environStr %p is above 2GB\n", environStr); print("Warning: environStr %#lx is above 2GB\n", environStr);
const char *linux_environ = "/proc/self/environ"; const char *linux_environ = "/proc/self/environ";
memcpy(environStr, linux_environ, strlen(linux_environ) + 1); memcpy(environStr, linux_environ, strlen(linux_environ) + 1);
int fd = sys_open(environStr, O_RDONLY, 0); int fd = sys_open(environStr, O_RDONLY, 0);
sys_munmap(environStr, 4096); // 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);
@ -247,17 +253,36 @@ void ConnectToSocket(int fd)
{ {
print("Connecting to socket\n"); print("Connecting to socket\n");
const char *runtime; const char *runtime;
if (IsLinux) if (OSInfo.IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR"); runtime = native_getenv("XDG_RUNTIME_DIR");
else else if (OSInfo.IsDarwin)
{
runtime = native_getenv("TMPDIR"); runtime = native_getenv("TMPDIR");
if (runtime == NULL) if (runtime == NULL)
{ {
print("IPC directory not set\n"); runtime = "/tmp/rpc-bridge/tmpdir";
if (!RunningAsService) print("IPC directory not set, fallback to /tmp/rpc-bridge/tmpdir\n");
MessageBox(NULL, "IPC directory not set",
"Environment variable not set", // Check if the directory exists
MB_OK | MB_ICONSTOP); 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);
}
}
}
else
{
print("Unsupported OS\n");
ExitProcess(1); ExitProcess(1);
} }
@ -284,7 +309,7 @@ void ConnectToSocket(int fd)
print("Connecting to %s\n", pipePath); print("Connecting to %s\n", pipePath);
if (IsLinux) if (OSInfo.IsLinux)
{ {
unsigned long socketArgs[] = { unsigned long socketArgs[] = {
(unsigned long)fd, (unsigned long)fd,
@ -312,7 +337,6 @@ void ConnectToSocket(int fd)
} }
} }
HANDLE hOut = NULL;
void PipeBufferInThread(LPVOID lpParam) void PipeBufferInThread(LPVOID lpParam)
{ {
bridge_thread *bt = (bridge_thread *)lpParam; bridge_thread *bt = (bridge_thread *)lpParam;
@ -321,7 +345,7 @@ void PipeBufferInThread(LPVOID lpParam)
int EOFCount = 0; 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))
@ -416,7 +440,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),
@ -463,6 +487,162 @@ void PipeBufferOutThread(LPVOID lpParam)
} }
} }
void COMPipeBufferInThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("COM In thread started using COM port and pipe %#x\n", bt->hPipe);
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComPort == INVALID_HANDLE_VALUE)
{
print("Failed to open COM2 port: %s\n", GetErrorMessage());
return;
}
int EOFCount = 0;
while (TRUE)
{
char buffer[BUFFER_LENGTH];
DWORD bytesRead;
if (!ReadFile(hComPort, buffer, sizeof(buffer), &bytesRead, NULL))
{
print("Failed to read from COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (EOFCount > 4)
{
print("EOF count exceeded\n");
RetryNewConnection = TRUE;
TerminateThread(hOut, 0);
break;
}
if (bytesRead == 0)
{
print("EOF\n");
Sleep(1000);
EOFCount++;
continue;
}
EOFCount = 0;
print("Reading %d bytes from COM2 port: \"", bytesRead);
for (DWORD i = 0; i < bytesRead; i++)
print("%c", buffer[i]);
print("\"\n");
DWORD dwWritten;
if (!WriteFile(bt->hPipe, buffer, bytesRead, &dwWritten, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
while (dwWritten < bytesRead)
{
int last_written = dwWritten;
if (!WriteFile(bt->hPipe, buffer + dwWritten, bytesRead - dwWritten, &dwWritten, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (last_written == dwWritten)
{
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
}
}
CloseHandle(hComPort);
}
void COMPipeBufferOutThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("COM Out thread started using COM port and pipe %#x\n", bt->hPipe);
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComPort == INVALID_HANDLE_VALUE)
{
print("Failed to open COM2 port: %s\n", GetErrorMessage());
return;
}
while (TRUE)
{
char buffer[BUFFER_LENGTH];
DWORD dwRead;
if (!ReadFile(bt->hPipe, buffer, sizeof(buffer), &dwRead, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("Out Broken pipe\n");
break;
}
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
print("Writing %d bytes to COM2 port: \"", dwRead);
for (DWORD i = 0; i < dwRead; i++)
print("%c", buffer[i]);
print("\"\n");
DWORD bytesWritten;
if (!WriteFile(hComPort, buffer, dwRead, &bytesWritten, NULL))
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
continue;
}
while (bytesWritten < dwRead)
{
int last_written = bytesWritten;
if (!WriteFile(hComPort, buffer + bytesWritten, dwRead - bytesWritten, &bytesWritten, NULL))
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (last_written == bytesWritten)
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
}
}
CloseHandle(hComPort);
}
void CreateBridge() void CreateBridge()
{ {
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0"); LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
@ -487,7 +667,7 @@ 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)
{ {
@ -516,7 +696,49 @@ NewConnection:
print("Pipe connected\n"); print("Pipe connected\n");
int fd; int fd;
if (IsLinux) if (!OSInfo.IsWine)
{
print("Running on Windows\n");
/* KVM, send 0xBB1569 and wait with timeout (TODO) for response 0xB41D6E */
print("Trying \\\\.\\COM2\n");
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
assert(hComPort != INVALID_HANDLE_VALUE);
DWORD dwWritten;
print("Writing to COM port\n");
char buffer[4] = "\x69\x15\xBB";
assert(WriteFile(hComPort, buffer, sizeof(buffer), &dwWritten, NULL));
print("Wrote %d bytes to COM port\n", dwWritten);
DWORD dwRead;
print("Reading from COM port\n");
assert(ReadFile(hComPort, buffer, sizeof(buffer), &dwRead, NULL));
print("Read %d bytes from COM port\n", dwRead);
if (dwRead != 4 || memcmp(buffer, "\x6E\x1D\xB4", 4) != 0)
{
CloseHandle(hComPort);
print("Failed to connect to COM2\n");
return;
}
CloseHandle(hComPort);
print("Connected to COM2\n");
bridge_thread bt = {0, hPipe};
hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)COMPipeBufferInThread,
(LPVOID)&bt,
0, NULL);
hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)COMPipeBufferOutThread,
(LPVOID)&bt,
0, NULL);
}
else
{
if (OSInfo.IsLinux)
{ {
unsigned long socketArgs[] = { unsigned long socketArgs[] = {
(unsigned long)AF_UNIX, (unsigned long)AF_UNIX,
@ -543,7 +765,7 @@ 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);
@ -552,6 +774,10 @@ NewConnection:
(LPTHREAD_START_ROUTINE)PipeBufferOutThread, (LPTHREAD_START_ROUTINE)PipeBufferOutThread,
(LPVOID)&bt, (LPVOID)&bt,
0, NULL); 0, NULL);
}
print("Created in thread %#lx\n", hIn);
print("Created out thread %#lx\n", hOut);
if (hIn == NULL || hOut == NULL) if (hIn == NULL || hOut == NULL)
{ {
@ -581,6 +807,7 @@ NewConnection:
print("Failed to terminate thread: %s\n", print("Failed to terminate thread: %s\n",
GetErrorMessage()); GetErrorMessage());
if (OSInfo.IsWine)
sys_close(fd); sys_close(fd);
CloseHandle(hOut); CloseHandle(hOut);
CloseHandle(hIn); CloseHandle(hIn);

13
bridge.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef _BRIDGE_H
#define _BRIDGE_H
#include <windows.h>
typedef struct _OS_INFO
{
BOOL IsLinux;
BOOL IsDarwin;
BOOL IsWine;
} OS_INFO;
#endif // _BRIDGE_H

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

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

179
gui.c
View File

@ -1,8 +1,12 @@
#include <windowsx.h>
#include <windows.h> #include <windows.h>
#include <winuser.h> #include <winuser.h>
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#include "resource.h"
/** /**
* The entire code could be better written, but at least it works. * The entire code could be better written, but at least it works.
* *
@ -14,26 +18,40 @@ void print(char const *fmt, ...);
void InstallService(int ServiceStartType, LPCSTR Path); void InstallService(int ServiceStartType, LPCSTR Path);
void RemoveService(); void RemoveService();
void CreateBridge(); void CreateBridge();
extern BOOL IsLinux; extern OS_INFO OSInfo;
extern char *logFilePath;
HWND hwnd = NULL; HWND hwnd = NULL;
HANDLE hBridge = NULL;
extern HANDLE hOut;
extern HANDLE hIn;
BOOL IsAlreadyRunning = FALSE;
VOID HandleStartButton(BOOL Silent) VOID HandleStartButton(BOOL Silent)
{ {
if (!IsLinux) if (IsAlreadyRunning)
{ {
ShowWindow(hwnd, SW_MINIMIZE); HWND item = GetDlgItem(hwnd, 4);
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge, SetWindowText(item, "Do you want to start, install or remove the bridge?");
NULL, 0, NULL); RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
item = GetDlgItem(hwnd, /* Start Button */ 1);
HWND item = GetDlgItem(hwnd, /* Start Button */ 1); Button_SetText(item, "&Start");
EnableWindow(item, FALSE); EnableWindow(item, FALSE);
item = GetDlgItem(hwnd, 4); RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
SetWindowText(item, "Bridge is running...");
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; return;
} }
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL) if (hSCManager == NULL)
{ {
print("OpenSCManager failed: %s\n", GetErrorMessage()); print("OpenSCManager failed: %s\n", GetErrorMessage());
@ -44,7 +62,19 @@ VOID HandleStartButton(BOOL Silent)
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START); SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
if (schService == NULL) if (schService == NULL)
{ {
print("OpenService failed: %s\n", GetErrorMessage()); 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; return;
} }
@ -75,6 +105,7 @@ VOID HandleStartButton(BOOL Silent)
CloseServiceHandle(hSCManager); CloseServiceHandle(hSCManager);
if (Silent == FALSE) if (Silent == FALSE)
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK); MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
print("Bridge service started successfully\n");
} }
VOID HandleInstallButton() VOID HandleInstallButton()
@ -95,6 +126,44 @@ VOID HandleRemoveButton()
ExitProcess(0); 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) LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
switch (msg) switch (msg)
@ -112,6 +181,26 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
case 3: case 3:
HandleRemoveButton(); HandleRemoveButton();
break; break;
case IDM_VIEW_LOG:
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", logFilePath, 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: default:
break; break;
} }
@ -124,6 +213,12 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
PostQuitMessage(0); PostQuitMessage(0);
ExitProcess(0); ExitProcess(0);
break; break;
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC)wParam;
SetBkMode(hdcStatic, TRANSPARENT);
return (INT_PTR)(HBRUSH)GetStockObject(NULL_BRUSH);
}
default: default:
return DefWindowProc(hwnd, msg, wParam, lParam); return DefWindowProc(hwnd, msg, wParam, lParam);
} }
@ -132,16 +227,9 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle) VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
{ {
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *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 hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS); SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
@ -160,16 +248,13 @@ VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallSty
*btnStartStyle |= WS_DISABLED; *btnStartStyle |= WS_DISABLED;
} }
else else
{
*btnStartStyle |= WS_DISABLED;
*btnRemoveStyle |= WS_DISABLED; *btnRemoveStyle |= WS_DISABLED;
}
CloseServiceHandle(schService); CloseServiceHandle(schService);
CloseServiceHandle(hSCManager); CloseServiceHandle(hSCManager);
} }
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) LPSTR lpCmdLine, int nCmdShow)
{ {
INT btnStartStyle, btnRemoveStyle, btnInstallStyle; INT btnStartStyle, btnRemoveStyle, btnInstallStyle;
@ -193,7 +278,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
assert(RegisterClassEx(&wc)); assert(RegisterClassEx(&wc));
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, hwnd = CreateWindowEx(WS_EX_WINDOWEDGE,
szClassName, szClassName,
"Discord RPC Bridge", "Discord RPC Bridge",
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME, WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
@ -202,40 +287,64 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
400, 150, 400, 150,
NULL, NULL, hInstance, NULL); NULL, NULL, hInstance, NULL);
CreateWindow("STATIC", "Do you want to start, install or remove the bridge?", 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, WS_CHILD | WS_VISIBLE | SS_CENTER,
0, 0, 400, 50, 0, 15, 400, 25,
hwnd, (HMENU)4, hInstance, NULL); hwnd, (HMENU)4, hInstance, NULL);
CreateWindow("BUTTON", "Start", HWND hbtn1 = CreateWindow("BUTTON", "&Start",
btnStartStyle, btnStartStyle,
50, 50, 100, 30, 40, 60, 100, 30,
hwnd, (HMENU)1, hInstance, NULL); hwnd, (HMENU)1, hInstance, NULL);
CreateWindow("BUTTON", "Install", HWND hbtn2 = CreateWindow("BUTTON", "&Install",
btnInstallStyle, btnInstallStyle,
150, 50, 100, 30, 150, 60, 100, 30,
hwnd, (HMENU)2, hInstance, NULL); hwnd, (HMENU)2, hInstance, NULL);
CreateWindow("BUTTON", "Remove", HWND hbtn3 = CreateWindow("BUTTON", "&Remove",
btnRemoveStyle, btnRemoveStyle,
250, 50, 100, 30, 260, 60, 100, 30,
hwnd, (HMENU)3, hInstance, NULL); 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); ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd); UpdateWindow(hwnd);
MSG msg; MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) while (GetMessage(&msg, NULL, 0, 0) > 0)
{
if (!IsDialogMessage(hwnd, &msg))
{ {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
}
return msg.wParam; return msg.wParam;
} }
void CreateGUI() void CreateGUI()
{ {
ShowWindow(GetConsoleWindow(), SW_MINIMIZE); ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
ExitProcess(WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL)); 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

100
kvm.c Normal file
View File

@ -0,0 +1,100 @@
#if defined(__linux__)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <signal.h>
void transferData(int fromSock, int toSock)
{
char buffer[256];
ssize_t bytesRead;
while ((bytesRead = read(fromSock, buffer, sizeof(buffer))) > 0)
{
write(toSock, buffer, bytesRead);
}
}
int connectToSocket(const char *socketPath)
{
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
assert(sock >= 0);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socketPath, sizeof(addr.sun_path) - 1);
int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
assert(result == 0);
return sock;
}
int kvmSock;
int discordSock;
void sigintHandler(int sig)
{
close(kvmSock);
close(discordSock);
exit(0);
}
int main()
{
signal(SIGINT, sigintHandler);
const char *kvmSocketPath = "/tmp/kvm-rpc-bridge";
const char *discordSocketPath = getenv("XDG_RUNTIME_DIR");
if (discordSocketPath == NULL)
{
fprintf(stderr, "XDG_RUNTIME_DIR environment variable not set\n");
return EXIT_FAILURE;
}
char discordSocketFullPath[256];
snprintf(discordSocketFullPath, sizeof(discordSocketFullPath), "%s/discord-ipc-0", discordSocketPath);
printf("Trying Discord socket at %s\n", discordSocketFullPath);
discordSock = connectToSocket(discordSocketFullPath);
printf("Connected to Discord socket successfully\n");
// Connect to KVM socket
printf("Trying KVM socket at %s\n", kvmSocketPath);
kvmSock = connectToSocket(kvmSocketPath);
// Read specific message from KVM and send confirmation
const char kvmExpectedMsg[] = {0x69, 0x15, 0xBB};
const char kvmResponseMsg[] = {0x6E, 0x1D, 0xB4};
char buffer[3];
ssize_t bytesRead = read(kvmSock, buffer, sizeof(buffer));
printf("Read %ld bytes from KVM (%x %x %x)\n", bytesRead, buffer[0], buffer[1], buffer[2]);
assert(bytesRead == sizeof(kvmExpectedMsg));
assert(memcmp(buffer, kvmExpectedMsg, sizeof(kvmExpectedMsg)) == 0);
ssize_t bytesSent = write(kvmSock, kvmResponseMsg, sizeof(kvmResponseMsg));
assert(bytesSent == sizeof(kvmResponseMsg));
printf("Connected to KVM socket successfully\n");
// Transfer data between KVM and Discord sockets
if (fork() == 0)
{
// Child process: transfer data from KVM to Discord
transferData(kvmSock, discordSock);
}
else
{
// Parent process: transfer data from Discord to KVM
transferData(discordSock, kvmSock);
}
close(kvmSock);
close(discordSock);
return 0;
}
#endif // __linux__

96
main.c
View File

@ -3,8 +3,12 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#include "resource.h"
FILE *g_logFile = NULL; FILE *g_logFile = NULL;
BOOL RunningAsService = FALSE; BOOL RunningAsService = FALSE;
char *logFilePath = NULL;
void CreateGUI(); void CreateGUI();
void CreateBridge(); void CreateBridge();
@ -13,7 +17,7 @@ void ServiceMain(int argc, char *argv[]);
void InstallService(int ServiceStartType, LPCSTR Path); void InstallService(int ServiceStartType, LPCSTR Path);
char *native_getenv(const char *name); char *native_getenv(const char *name);
void RemoveService(); void RemoveService();
extern BOOL IsLinux; extern OS_INFO OSInfo;
LPTSTR GetErrorMessage() LPTSTR GetErrorMessage()
{ {
@ -55,11 +59,8 @@ void DetectWine()
} }
if (!GetProcAddress(hNTdll, "wine_get_version")) if (!GetProcAddress(hNTdll, "wine_get_version"))
{ return;
MessageBox(NULL, "This program is only intended to run under Wine.", OSInfo.IsWine = TRUE;
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
ExitProcess(1);
}
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release); static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version"); wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version");
@ -72,14 +73,12 @@ void DetectWine()
{ {
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?", int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
NULL, MB_YESNO | MB_ICONQUESTION); NULL, MB_YESNO | MB_ICONQUESTION);
if (result == IDYES) if (result == IDNO)
return;
else if (result == IDNO)
ExitProcess(1); ExitProcess(1);
} }
IsLinux = strcmp(__sysname, "Linux") == 0; OSInfo.IsLinux = strcmp(__sysname, "Linux") == 0;
printf("Running on %s\n", __sysname); OSInfo.IsDarwin = strcmp(__sysname, "Darwin") == 0;
} }
void print(char const *fmt, ...) void print(char const *fmt, ...)
@ -114,14 +113,13 @@ void HandleArguments(int argc, char *argv[])
} }
else if (strcmp(argv[1], "--steam") == 0) else if (strcmp(argv[1], "--steam") == 0)
{ {
assert(OSInfo.IsWine == TRUE);
/* All this mess just so when you close the game, /* All this mess just so when you close the game,
it automatically closes the bridge and Steam it automatically closes the bridge and Steam
will not say that the game is still running. */ will not say that the game is still running. */
print("Running as Steam\n"); print("Running as Steam\n");
if (IsLinux == FALSE)
CreateBridge();
if (argc > 2) if (argc > 2)
{ {
if (strcmp(argv[2], "--no-service") == 0) if (strcmp(argv[2], "--no-service") == 0)
@ -272,32 +270,40 @@ void HandleArguments(int argc, char *argv[])
CreateBridge(); CreateBridge();
ExitProcess(0); ExitProcess(0);
} }
else if (strcmp(argv[1], "--version") == 0)
{
printf("%s\n", VER_VERSION_STR);
ExitProcess(0);
}
else if (strcmp(argv[1], "--help") == 0) else if (strcmp(argv[1], "--help") == 0)
{ {
printf("Usage:\n"); printf("Usage:\n"
printf(" %s [args]\n\n", argv[0]); " %s [args]\n"
"\n"
printf("Arguments:\n"); "Arguments:\n"
printf(" --help Show this help\n\n"); " --help Show this help\n"
"\n"
printf(" --install Install service\n"); " --version Show version\n"
printf(" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n\n"); "\n"
" --install Install service\n"
printf(" --uninstall Uninstall service\n"); " This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n"
printf(" This will remove the service and delete C:\\windows\\bridge.exe\n\n"); "\n"
" --uninstall Uninstall service\n"
printf(" --steam Reserved for Steam\n"); " This will remove the service and delete C:\\windows\\bridge.exe\n"
printf(" This will start the service and exit (used with bridge.sh)\n\n"); "\n"
" --steam Reserved for Steam\n"
printf(" --no-service Do not run as service\n"); " This will start the service and exit (used with bridge.sh)\n"
printf(" (only for --steam)\n\n"); "\n"
" --no-service Do not run as service\n"
printf(" --service Reserved for service\n\n"); " (only for --steam)\n"
"\n"
printf(" --rpc <dir> Set RPC_PATH environment variable\n"); " --service Reserved for service\n"
printf(" This is used to specify the directory where 'discord-ipc-0' is located\n\n"); "\n"
" --rpc <dir> Set RPC_PATH environment variable\n"
printf("Note: If no arguments are provided, the GUI will be shown instead\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); ExitProcess(0);
} }
} }
@ -305,7 +311,14 @@ void HandleArguments(int argc, char *argv[])
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
DetectWine(); DetectWine();
char *logFilePath = "C:\\windows\\logs\\bridge.log"; if (OSInfo.IsWine)
logFilePath = "C:\\windows\\logs\\bridge.log";
else
{
logFilePath = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
GetTempPath(MAX_PATH, logFilePath);
strcat_s(logFilePath, MAX_PATH, "bridge.log");
}
g_logFile = fopen(logFilePath, "w"); g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL) if (g_logFile == NULL)
{ {
@ -327,3 +340,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, 1, 0, 0
#define VER_VERSION_STR "1.1\0"

View File

@ -8,7 +8,6 @@ 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)
{ {
@ -88,14 +87,6 @@ void InstallService(int ServiceStartType, LPCSTR Path)
{ {
print("Registering service\n"); print("Registering service\n");
if (IsLinux == FALSE)
{
/* 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);
}
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL) if (schSCManager == NULL)
{ {