67 Commits
v1.0 ... v1.3

Author SHA1 Message Date
9bec33c358 Update bug_report.md 2025-04-08 11:36:51 +03:00
3e6b5dbb43 Merge branch 'master' of https://github.com/EnderIce2/rpc-bridge 2025-04-08 11:14:41 +03:00
4c432a0e50 Fix #12, socket -14 (EFAULT) 2025-04-08 11:14:32 +03:00
65b5625a57 Update error message for Wine compatibility check 2025-04-08 08:21:03 +03:00
9d2440f616 Update LICENSE
Some checks failed
Build and Deploy docs / deploy (push) Failing after 12m12s
Build and Deploy docs / build (push) Failing after 6m5s
2025-01-04 06:55:43 +02:00
51cbb666f9 Update build-deploy.yml
Some checks failed
Build and Deploy docs / build (push) Failing after 4m46s
Build and Deploy docs / deploy (push) Failing after 48s
2024-12-30 00:27:21 +02:00
1deccc5a63 Update CODE_OF_CONDUCT.md
Some checks failed
Build and Deploy docs / build (push) Failing after 28s
Build and Deploy docs / deploy (push) Failing after 34s
2024-12-11 05:54:45 +02:00
3412c3c011 Update issue templates
Some checks failed
Build and Deploy docs / build (push) Has been cancelled
Build and Deploy docs / deploy (push) Has been cancelled
2024-10-30 16:52:46 +02:00
c15c4df29e Create CODE_OF_CONDUCT.md 2024-10-30 16:39:46 +02:00
3a0b9ddb2a Ensure log file is properly closed
Some checks are pending
Build and Deploy docs / build (push) Waiting to run
Build and Deploy docs / deploy (push) Waiting to run
* Closing the log before service starts prevents log file to be corrupted (like in #7)
2024-10-30 01:08:12 +02:00
8786efd144 Fix formatting and add game links 2024-06-02 17:52:52 +03:00
2024de9408 Add "Known Issues" section
* Using Vesktop may cause issues with some games #4
2024-06-02 17:45:48 +03:00
e8926edb41 Add more screenshots
* Content Warning
* Hades
* Lethal Company
* vivid/stasis
2024-06-02 17:41:16 +03:00
9f1a9de3d7 Update actions/checkout and actions/upload-artifact to v4 2024-06-01 19:57:30 +03:00
ccf09806c9 Print version on program start 2024-06-01 19:39:39 +03:00
7363ee64d5 Update bridge.manifest with correct assembly name 2024-06-01 19:35:01 +03:00
a3023e349e Update gui.png 2024-06-01 02:56:12 +03:00
39966d7149 Update Discord socket search
* Test multiple instances
* Add support for Discord Canary (Snap)
* Add support for Vesktop (Flatpak)
2024-06-01 01:37:40 +03:00
a8904bf3f1 Update version 2024-06-01 00:57:06 +03:00
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
44 changed files with 1720 additions and 291 deletions

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: EnderIce2
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Log File**
Located in `/path/to/prefix/drive_c/windows/logs/bridge.log` or a screenshot with the terminal should suffice.
**System Info (please complete the following information):**
- OS: [e.g. SteamOS 3.0, Ubuntu 22.04, macOS 15, or output from command `uname -srm`]
- Wine: [e.g. 10.5]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: EnderIce2
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

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@v4
- name: dependencies
run: sudo apt update && sudo apt -y install gcc-mingw-w64 make
- name: make
run: make
- name: artifact
uses: actions/upload-artifact@v4
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
*.exe
*.res
*.elf

View File

@ -5,10 +5,13 @@
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"defines": [
"GIT_COMMIT",
"GIT_BRANCH"
],
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "windows-gcc-x86"
"intelliSenseMode": "windows-gcc-x64"
}
],
"version": 4

View File

@ -1,8 +1,3 @@
{
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc",
"files.associations": {
"*.su": "tsv",
"windows.h": "c",
"namedpipeapi.h": "c"
}
"C_Cpp.default.compilerPath": "/usr/bin/x86_64-w64-mingw32-gcc",
}

128
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
enderice2@protonmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -1,4 +1,4 @@
Copyright (c) 2023 EnderIce2
Copyright (c) 2025 EnderIce2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,24 +1,24 @@
C_SOURCES = $(shell find ./ -type f -name '*.c')
C_OBJECTS = $(C_SOURCES:.c=.o)
CFLAGS = -std=c17 -Wno-int-conversion
LFLAGS =
GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
CFLAGS = -std=c17 -Wno-int-conversion -DGIT_COMMIT='"$(GIT_COMMIT)"' -DGIT_BRANCH='"$(GIT_BRANCH)"'
LFLAGS = -lgdi32 -lws2_32
# DBGFLAGS = -Wl,--export-all-symbols -g -O0 -ggdb3 -Wall
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)
$(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
$(info Compiling $<)
i686-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
x86_64-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
clean:
rm -f $(C_OBJECTS) build/bridge.exe
rm -f $(C_OBJECTS) build/bridge.exe bridge.res

102
README.md
View File

@ -1,5 +1,9 @@
# 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.
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 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.
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.
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)
- Open terminal in `build`
- Run `$ wine cmd` and `C:\> install.bat`
- ![wine install](imgs/wine_install.gif)
- To remove, run `C:\> remove.bat`
- ![wine remove](imgs/wine_remove.gif)
- Note: Copying files are not required here.
- Double click `bridge.exe` and click `Install`.
- ![gui](docs/assets/gui.png)
- To remove, the same process can be followed, but click `Remove` instead.
*Note, an [extra step](https://github.com/EnderIce2/rpc-bridge?tab=readme-ov-file#macos) is needed on MacOS*
##### Lutris
- Right click on the game and select `Browse files`
- ![lutris browse](imgs/lutris_browse.gif)
- Copy contents of `build` to the game's prefix `drive_c`
- ![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)
- Click on a game and select `Run EXE inside Wine prefix`.
- ![lutris](docs/assets/lutris.png)
- The same process can be followed as in Wine.
##### Steam
- 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](imgs/steam_protontricks.png)
- Right click on the game and select `Properties`.
- Under `Set Launch Options`, add the following:
- ![bridge.sh](docs/assets/steam_script.png "Set Launch Options to the path of the bridge.sh")
- The `bridge.sh` script must be in the same directory as `bridge.exe`.
#### 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)
- Add `xdg-run/discord-ipc-0` under `Filesystems` category
- ![flatseal](imgs/flatseal_permission.png)
- ![flatseal](docs/assets/flatseal_permission.png)
- ##### By using the terminal
- Per application
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
- Globally
- `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.
This method is compatible with both Wine and Lutris.
The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you will have to install a **LaunchAgent**.
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.
Example:
- 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`
- 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`.
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
- 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`.
- 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
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).

407
bridge.c
View File

@ -4,22 +4,50 @@
#include <assert.h>
#include <stdio.h>
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_socketcall 102
#define __linux_read 3
#define __linux_write 4
#define __linux_open 5
#define __linux_close 6
#define __linux_munmap 91
#define __linux_socketcall 102
#define __linux_mmap2 192
#define __linux_socket 359
#define __linux_connect 362
#define __darwin_read 0x2000003
#define __darwin_write 0x2000004
#define __darwin_open 0x2000005
#define __darwin_close 0x2000006
#define __darwin_socket 0x2000061
#define __darwin_connect 0x2000062
#define __darwin_fcntl 0x200005C
#define __darwin_sysctl 0x20000CA
#define O_RDONLY 00
#define PROT_READ 1
#define PROT_WRITE 2
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANON 0x20
#define MAP_FAILED ((void *)-1)
#define SYS_SOCKET 1
#define SYS_CONNECT 3
#define likely(expr) (__builtin_expect(!!(expr), 1))
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
#define force_inline \
__inline__ \
__attribute__((__always_inline__, __gnu_inline__))
#define naked __attribute__((naked))
#define BUFFER_LENGTH 2048
typedef unsigned short sa_family_t;
typedef char *caddr_t;
typedef unsigned socklen_t;
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
@ -36,10 +64,13 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage();
extern BOOL RunningAsService;
BOOL RetryNewConnection;
BOOL IsLinux;
HANDLE hOut = NULL;
HANDLE hIn = NULL;
static force_inline int syscall(int num,
intptr_t arg1, intptr_t arg2, intptr_t arg3,
intptr_t arg4, intptr_t arg5, intptr_t arg6)
static force_inline int linux_syscall(int num,
int arg1, int arg2, int arg3,
int arg4, int arg5, int arg6)
{
int ret;
__asm__ __volatile__(
@ -48,49 +79,143 @@ static force_inline int syscall(int num,
: "0"(num), "b"(arg1), "c"(arg2),
"d"(arg3), "S"(arg4), "D"(arg5)
: "memory");
return ret;
}
static naked int darwin_syscall(int num,
long arg1, long arg2, long arg3,
long arg4, long arg5, long arg6)
{
register long r10 __asm__("r10") = arg4;
register long r8 __asm__("r8") = arg5;
register long r9 __asm__("r9") = arg6;
__asm__ __volatile__(
"syscall\n"
"jae noerror\n"
"negq %%rax\n"
"noerror:\n"
"ret\n"
: "=a"(num)
: "a"(num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), "r"(r9)
: "memory");
}
static inline int sys_read(int fd, void *buf, size_t count)
{
return syscall(__NR_read, fd, (intptr_t)buf, count, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0);
}
static inline int sys_write(int fd, const void *buf, size_t count)
{
return syscall(__NR_write, fd, (intptr_t)buf, count, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0);
}
static inline int sys_open(const char *pathname, int flags, int mode)
{
return syscall(__NR_open, (intptr_t)pathname, flags, mode, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else
return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0);
}
static inline int sys_close(int fd)
{
return syscall(__NR_close, fd, 0, 0, 0, 0, 0);
if (IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else
return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0);
}
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
assert(IsLinux);
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
}
static inline int sys_munmap(unsigned int *addr, size_t length)
{
assert(IsLinux);
return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0);
}
static inline int sys_socketcall(int call, unsigned long *args)
{
return syscall(__NR_socketcall, call, (intptr_t)args, 0, 0, 0, 0);
assert(IsLinux);
return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0);
}
char *linux_getenv(const char *name)
static inline int sys_socket(int domain, int type, int protocol)
{
int fd = sys_open("/proc/self/environ", O_RDONLY, 0);
if (IsLinux)
return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0);
else
return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0);
}
static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
{
if (IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
}
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, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
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);
if (fd < 0)
{
print("Failed to open /proc/self/environ: %d\n", fd);
return NULL;
}
char buffer[4096];
char *buffer = sys_mmap(0x22000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
char *result = NULL;
int bytesRead;
while ((bytesRead = sys_read(fd, buffer, sizeof(buffer) - 1)) > 0)
while ((bytesRead = sys_read(fd, buffer, 0x1000 - 1)) > 0)
{
buffer[bytesRead] = '\0';
char *env = buffer;
@ -120,64 +245,99 @@ char *linux_getenv(const char *name)
void ConnectToSocket(int fd)
{
print("Connecting to socket\n");
const char *runtime = linux_getenv("XDG_RUNTIME_DIR");
const char *runtime;
if (IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR");
else
{
runtime = native_getenv("TMPDIR");
if (runtime == NULL)
{
print("XDG_RUNTIME_DIR not set\n");
if (!RunningAsService)
runtime = "/tmp/rpc-bridge/tmpdir";
print("IPC directory not set, fallback to /tmp/rpc-bridge/tmpdir\n");
// Check if the directory exists
DWORD dwAttrib = GetFileAttributes(runtime);
if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
{
MessageBox(NULL,
"XDG_RUNTIME_DIR not set",
"Environment variable not set",
MB_OK | MB_ICONSTOP);
}
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);
}
}
}
print("XDG_RUNTIME_DIR: %s\n", runtime);
print("IPC directory: %s\n", runtime);
const char *discordUnixPipes[] = {
"/discord-ipc-0",
"/snap.discord/discord-ipc-0",
"/app/com.discordapp.Discord/discord-ipc-0",
/* TODO: check for multiple discord instances and create a pipe for each */
const char *discordUnixSockets[] = {
"%s/discord-ipc-%d",
"%s/app/com.discordapp.Discord/discord-ipc-%d",
"%s/.flatpak/dev.vencord.Vesktop/xdg-run/discord-ipc-%d",
"%s/snap.discord/discord-ipc-%d",
"%s/snap.discord-canary/discord-ipc-%d",
};
struct sockaddr_un socketAddr;
socketAddr.sun_family = AF_UNIX;
char *pipePath = NULL;
int sockRet = -1;
struct sockaddr_un *socketAddr = sys_mmap(0x23000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
socketAddr->sun_family = AF_UNIX;
for (int i = 0; i < sizeof(discordUnixPipes) / sizeof(discordUnixPipes[0]); i++)
int sockRet = 0;
for (int i = 0; i < sizeof(discordUnixSockets) / sizeof(discordUnixSockets[0]); i++)
{
pipePath = malloc(strlen(runtime) + strlen(discordUnixPipes[i]) + 1);
strcpy(pipePath, runtime);
strcat(pipePath, discordUnixPipes[i]);
strcpy_s(socketAddr.sun_path, sizeof(socketAddr.sun_path), pipePath);
size_t pipePathLen = strlen(runtime) + strlen(discordUnixSockets[i]) + 1;
char *pipePath = malloc(pipePathLen);
print("Connecting to %s\n", pipePath);
for (int j = 0; j < 16; j++)
{
snprintf(pipePath, pipePathLen, discordUnixSockets[i], runtime, j);
strcpy_s(socketAddr->sun_path, sizeof(socketAddr->sun_path), pipePath);
print("Probing %s\n", pipePath);
unsigned long socketArgs[] = {
(unsigned long)fd,
(unsigned long)&socketAddr,
sizeof(socketAddr)};
if (IsLinux)
{
// unsigned long socketArgs[] = {
// (unsigned long)fd,
// (unsigned long)(intptr_t)&socketAddr,
// sizeof(socketAddr)};
unsigned long *socketArgs = sys_mmap(0x24000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
socketArgs[0] = (unsigned long)fd;
socketArgs[1] = (unsigned long)(intptr_t)socketAddr;
socketArgs[2] = sizeof(struct sockaddr_un);
socketArgs[3] = 0;
sockRet = sys_socketcall(3, socketArgs);
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
}
else
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
free(pipePath);
print(" error: %d\n", sockRet);
if (sockRet >= 0)
break;
}
if (sockRet >= 0)
{
print("Connecting to %s\n", pipePath);
free(pipePath);
break;
}
free(pipePath);
}
if (sockRet < 0)
{
print("socketcall failed for: %d\n", sockRet);
if (!RunningAsService)
{
MessageBox(NULL,
"Failed to connect to Discord",
MessageBox(NULL, "Failed to connect to Discord",
"Socket Connection failed",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
}
@ -185,27 +345,39 @@ void ConnectToSocket(int fd)
void PipeBufferInThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("In thread started using fd %d and pipe %#x\n",
bt->fd, bt->hPipe);
print("In thread started using fd %d and pipe %#x\n", bt->fd, bt->hPipe);
int EOFCount = 0;
char *l_buffer = sys_mmap(0x25000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
while (TRUE)
{
char buffer[1024];
int read = sys_read(bt->fd, buffer, sizeof(buffer));
char buffer[BUFFER_LENGTH];
int read = sys_read(bt->fd, l_buffer, BUFFER_LENGTH);
if (unlikely(read < 0))
{
print("Failed to read from unix pipe: %d\n",
GetErrorMessage());
print("Failed to read from unix pipe: %d\n", read);
Sleep(1000);
continue;
}
if (EOFCount > 4)
{
print("EOF count exceeded\n");
RetryNewConnection = TRUE;
TerminateThread(hOut, 0);
break;
}
if (unlikely(read == 0))
{
print("EOF\n");
Sleep(1000);
EOFCount++;
continue;
}
EOFCount = 0;
memcpy(buffer, l_buffer, read);
print("Reading %d bytes from unix pipe: \"", read);
for (int i = 0; i < read; i++)
@ -213,8 +385,8 @@ void PipeBufferInThread(LPVOID lpParam)
print("\"\n");
DWORD dwWritten;
if (unlikely(!WriteFile(bt->hPipe, buffer, read,
&dwWritten, NULL)))
WINBOOL bResult = WriteFile(bt->hPipe, buffer, read, &dwWritten, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
@ -223,16 +395,14 @@ void PipeBufferInThread(LPVOID lpParam)
break;
}
print("Failed to read from pipe: %d\n",
GetErrorMessage());
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (unlikely(dwWritten < 0))
{
print("Failed to write to pipe: %d\n",
GetErrorMessage());
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
@ -240,8 +410,8 @@ void PipeBufferInThread(LPVOID lpParam)
while (dwWritten < read)
{
int last_written = dwWritten;
if (unlikely(!WriteFile(bt->hPipe, buffer + dwWritten,
read - dwWritten, &dwWritten, NULL)))
WINBOOL bResult = WriteFile(bt->hPipe, buffer + dwWritten, read - dwWritten, &dwWritten, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
@ -250,16 +420,14 @@ void PipeBufferInThread(LPVOID lpParam)
break;
}
print("Failed to read from pipe: %d\n",
GetErrorMessage());
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (unlikely(last_written == dwWritten))
{
print("Failed to write to pipe: %d\n",
GetErrorMessage());
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
@ -270,15 +438,14 @@ void PipeBufferInThread(LPVOID lpParam)
void PipeBufferOutThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("Out thread started using fd %d and pipe %#x\n",
bt->fd, bt->hPipe);
print("Out thread started using fd %d and pipe %#x\n", bt->fd, bt->hPipe);
char *l_buffer = sys_mmap(0x25000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
while (TRUE)
{
char buffer[1024];
char buffer[BUFFER_LENGTH];
DWORD dwRead;
if (unlikely(!ReadFile(bt->hPipe, buffer, sizeof(buffer),
&dwRead, NULL)))
WINBOOL bResult = ReadFile(bt->hPipe, buffer, BUFFER_LENGTH, &dwRead, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
@ -287,8 +454,7 @@ void PipeBufferOutThread(LPVOID lpParam)
break;
}
print("Failed to read from pipe: %d\n",
GetErrorMessage());
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
@ -298,11 +464,11 @@ void PipeBufferOutThread(LPVOID lpParam)
print("%c", buffer[i]);
print("\"\n");
int written = sys_write(bt->fd, buffer, dwRead);
memcpy(l_buffer, buffer, dwRead);
int written = sys_write(bt->fd, l_buffer, dwRead);
if (unlikely(written < 0))
{
print("Failed to write to socket: %d\n",
written);
print("Failed to write to socket: %d\n", written);
continue;
}
@ -312,8 +478,7 @@ void PipeBufferOutThread(LPVOID lpParam)
written += sys_write(bt->fd, buffer + written, dwRead - written);
if (unlikely(last_written == written))
{
print("Failed to write to socket: %d\n",
GetErrorMessage());
print("Failed to write to socket: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
@ -330,11 +495,14 @@ NewConnection:
NULL, NULL,
NULL, NULL))
{
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Pipe already exists: %s\n",
GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Pipe already exists",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
@ -342,15 +510,18 @@ NewConnection:
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
PIPE_ACCESS_DUPLEX,
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 (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to create pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Failed to create pipe",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
@ -358,53 +529,81 @@ NewConnection:
print("Waiting for pipe connection\n");
if (!ConnectNamedPipe(hPipe, NULL))
{
print("Failed to connect to pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to connect to pipe: %s\n",
GetErrorMessage());
ExitProcess(1);
}
print("Pipe connected\n");
unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
(unsigned long)SOCK_STREAM,
0};
int fd;
if (IsLinux)
{
// unsigned long socketArgs[] = {
// (unsigned long)AF_UNIX,
// (unsigned long)SOCK_STREAM,
// 0};
unsigned long *socketArgs = sys_mmap(0x21000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
socketArgs[0] = (unsigned long)AF_UNIX;
socketArgs[1] = (unsigned long)SOCK_STREAM;
socketArgs[2] = 0;
fd = sys_socketcall(SYS_SOCKET, socketArgs);
int fd = sys_socketcall(1, socketArgs);
/* FIXME: WSAEAFNOSUPPORT: https://gitlab.winehq.org/wine/wine/-/merge_requests/2786 */
// WSADATA wsaData;
// int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
// if (iResult != 0)
// printf("WSAStartup failed: %d\n", iResult);
// fd = socket(AF_UNIX, SOCK_STREAM, 0);
}
else
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
print("Socket %d created\n", fd);
if (fd == INVALID_SOCKET)
{
print("invalid socket: %d %d\n", fd, WSAGetLastError());
ExitProcess(1);
}
if (fd < 0)
{
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
print("Failed to create socket: %d\n", fd);
if (!RunningAsService)
MessageBox(NULL, "Failed to create socket",
NULL, MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
print("Socket %d created\n", fd);
ConnectToSocket(fd);
print("Connected to Discord\n");
bridge_thread bt = {fd, hPipe};
HANDLE hIn = CreateThread(NULL, 0,
hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
(LPVOID)&bt,
0, NULL);
print("Created in thread %#lx\n", hIn);
HANDLE hOut = CreateThread(NULL, 0,
hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
(LPVOID)&bt,
0, NULL);
print("Created out thread %#lx\n", hOut);
if (hIn == NULL || hOut == NULL)
{
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
print("Failed to create threads: %s\n", GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Failed to create threads",
MB_OK | MB_ICONSTOP);
}
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="com.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/app/com.discordapp.Discord $TEMP_PATH/.flatpak/dev.vencord.Vesktop/xdg-run $TEMP_PATH/snap.discord $TEMP_PATH/snap.discord-canary"
for discord_ipc in $IPC_PATHS; do
if [ -S "$discord_ipc"/discord-ipc-? ]; then
VESSEL_PATH="$BRIDGE_PATH:$(echo "$discord_ipc"/discord-ipc-?)"
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

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: 11 KiB

BIN
docs/assets/hades.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 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

BIN
docs/assets/vividstasis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 KiB

54
docs/index.md Normal file
View File

@ -0,0 +1,54 @@
# 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).
---
## Known Issues
- If you use **Vesktop**
Some games may not show up in Discord. This is because Vesktop uses arRPC, which it doesn't work with some games [#4](https://github.com/EnderIce2/rpc-bridge/issues/4#issuecomment-2143549407). This is not an issue with the bridge.
---
## Examples
[**League Of Legends**](https://www.leagueoflegends.com/en-us/) running under Wine using Lutris
![image](assets/lutris_lol.png){ width="600" }
[**Among Us**](https://store.steampowered.com/app/945360/Among_Us/) on Steam
![image](assets/steam_amongus.png){ width="600" }
[**Content Warning**](https://store.steampowered.com/app/2881650/Content_Warning/) on Steam
![image](assets/contentwarning.png){ width="600" }
[**Hades**](https://store.steampowered.com/app/1145360/Hades/) on Steam
![image](assets/hades.png){ width="600" }
[**Lethal Company**](https://store.steampowered.com/app/1966720/Lethal_Company/) ([modded](https://thunderstore.io/c/lethal-company/p/mrov/LethalRichPresence/)) on Steam
![image](assets/lethalcompany.png){ width="600" }
[**vivid/stasis**](https://store.steampowered.com/app/2093940/vividstasis/) on Steam
![image](assets/vividstasis.png){ 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))
{
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"GetBinaryType",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
print("Executable type: %d\n", dwBinaryType);
@ -104,7 +105,8 @@ void LaunchGame(int argc, char **argv)
if (!CreateProcess(gamePath, gameArgs, NULL, NULL, FALSE,
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);
}
LocalFree(gameArgs);
@ -136,7 +138,8 @@ void LaunchGame(int argc, char **argv)
}
else
{
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
MessageBox(NULL, GetErrorMessage(),
"CreateToolhelp32Snapshot", MB_OK | MB_ICONSTOP);
ExitProcess(0);
}

358
gui.c Normal file
View File

@ -0,0 +1,358 @@
#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];
snprintf(msg, sizeof(msg),
"rpc-bridge v%s\n"
" branch: %s\n"
" commit: %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, GIT_BRANCH, GIT_COMMIT);
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

268
main.c
View File

@ -3,14 +3,19 @@
#include <assert.h>
#include <stdio.h>
#include "resource.h"
FILE *g_logFile = NULL;
BOOL RunningAsService = FALSE;
void CreateGUI();
void CreateBridge();
void LaunchGame(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();
extern BOOL IsLinux;
LPTSTR GetErrorMessage()
{
@ -45,13 +50,35 @@ void DetectWine()
{
HMODULE hNTdll = GetModuleHandle("ntdll.dll");
if (!hNTdll)
{
MessageBox(NULL, "Failed to load ntdll.dll",
GetErrorMessage(), MB_OK | MB_ICONERROR);
ExitProcess(1);
}
if (!GetProcAddress(hNTdll, "wine_get_version"))
{
MessageBox(NULL, "This program is only intended to run under Wine.",
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
"Error", MB_OK | MB_ICONINFORMATION);
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, ...)
@ -65,20 +92,8 @@ void print(char const *fmt, ...)
va_end(args);
}
int main(int argc, char *argv[])
void HandleArguments(int argc, char *argv[])
{
DetectWine();
char *logFilePath = "C:\\bridge.log";
g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL)
{
printf("Failed to open logs file: %ld\n",
GetLastError());
ExitProcess(1);
}
if (argc > 1)
{
if (strcmp(argv[1], "--service") == 0)
{
RunningAsService = TRUE;
@ -93,17 +108,223 @@ int main(int argc, char *argv[])
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
{
print("Service failed to start\n");
return GetLastError();
ExitProcess(1);
}
return 0;
}
else if (strcmp(argv[1], "--steam") == 0)
{
/* All this mess just so when you close the game,
it automatically closes the bridge and Steam
will not say that the game is still running. */
print("Running as Steam\n");
if (IsLinux == FALSE)
CreateBridge();
if (argc > 2)
{
if (strcmp(argv[2], "--no-service") == 0)
CreateBridge();
}
if (strcmp(argv[1], "--install") == 0)
InstallService();
else if (strcmp(argv[1], "--uninstall") == 0)
RemoveService();
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 and then exiting...\n");
fclose(g_logFile);
if (StartService(schService, 0, NULL) != FALSE)
{
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
ExitProcess(0);
}
else if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
{
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
ExitProcess(0);
}
MessageBox(NULL, GetErrorMessage(),
"StartService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
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)
{
/* Already shows the version */
ExitProcess(0);
}
else if (strcmp(argv[1], "--help") == 0)
{
print("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[])
{
DetectWine();
char *logFilePath = "C:\\windows\\logs\\bridge.log";
g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL)
{
MessageBox(NULL, "Failed to open logs file", "Error", MB_OK | MB_ICONERROR);
printf("Failed to open logs file: %ld\n", GetLastError());
ExitProcess(1);
}
print("rpc-bridge v%s %s-%s\n", VER_VERSION_STR, GIT_BRANCH, GIT_COMMIT);
if (argc > 1)
HandleArguments(argc, argv);
else
CreateGUI();
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
NULL, 0, NULL);
Sleep(500);
@ -112,3 +333,8 @@ int main(int argc, char *argv[])
fclose(g_logFile);
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, 3, 0, 0
#define VER_VERSION_STR "1.3\0"

View File

@ -8,6 +8,7 @@ SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
void print(char const *fmt, ...);
void CreateBridge();
LPTSTR GetErrorMessage();
extern BOOL IsLinux;
void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
{
@ -83,47 +84,47 @@ void ServiceMain(DWORD argc, LPTSTR *argv)
return;
}
void InstallService()
void InstallService(int ServiceStartType, LPCSTR Path)
{
print("Registering to run on startup\n");
print("Registering service\n");
SC_HANDLE schSCManager, schService;
DWORD dwTagId;
// 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);
// }
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL)
{
print("Failed to open service manager\n");
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"OpenSCManager",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
schService =
CreateService(schSCManager,
DWORD dwTagId;
SC_HANDLE schService = CreateService(schSCManager,
"rpc-bridge", "Wine RPC Bridge",
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
"C:\\bridge.exe --service",
NULL, &dwTagId, NULL, NULL, NULL);
ServiceStartType, SERVICE_ERROR_NORMAL,
Path, NULL, &dwTagId, NULL, NULL, NULL);
if (schService == NULL)
{
print("Failed to create service\n");
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"CreateService",
MB_OK | MB_ICONSTOP);
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);
}
CloseServiceHandle(schSCManager);
ExitProcess(0);
}
void RemoveService()
@ -133,11 +134,12 @@ void RemoveService()
SC_HANDLE schSCManager, schService;
SERVICE_STATUS ssSvcStatus;
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (schSCManager == NULL)
{
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"OpenSCManager",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
@ -147,7 +149,8 @@ void RemoveService()
if (schService == NULL)
{
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"OpenService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
@ -176,13 +179,13 @@ void RemoveService()
if (!DeleteService(schService))
{
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
"DeleteService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
DeleteFile("C:\\bridge.exe");
DeleteFile("C:\\windows\\bridge.exe");
print("Service removed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
ExitProcess(0);
}