Compare commits

...

85 Commits
v1.0 ... master

Author SHA1 Message Date
c1078b5b8b
fix: 🐛 mp4 is detected as audio
Some checks failed
Build Project / build (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
Changed video to webm, maybe this will work.
2025-04-25 06:45:30 +03:00
1de80cdbb9
fix: 🐛 update mkdocs-video plugin configuration to force mp4 type 2025-04-25 06:35:52 +03:00
975ace70f9
fix: 🎨 correct shebang formatting in bridge.sh 2025-04-25 04:31:47 +03:00
ad076dfac7
docs: 📝 improve documentation to be more detailed
Included video tutorial link for installing the LaunchAgent and bridge inside CrossOver.
2025-04-25 04:29:35 +03:00
8c54d5bb2f
Fix theme toggle names and icons in light and dark modes 2025-04-25 03:43:35 +03:00
a0615229ef
Add installation checks to launchd.sh for improved user feedback 2025-04-25 03:42:34 +03:00
18c422101b
Remove obsolete installation and removal scripts 2025-04-25 03:41:09 +03:00
77a2a748ad
Fix macos version
Some checks failed
Build Project / build (push) Has been cancelled
2025-04-09 10:03:54 +03:00
0d4f46e536
Fix build error #14 2025-04-09 06:38:25 +03:00
d065019137
Fix formatting in documentation
Some checks failed
Build Project / build (push) Has been cancelled
Deploy Documentation / deploy (push) Has been cancelled
2025-04-08 12:11:52 +03:00
e03dce1a7c
Improve formatting of installation instructions in documentation 2025-04-08 12:09:38 +03:00
7c806779d2
Add link to latest pre-release in documentation 2025-04-08 12:05:58 +03:00
885cf775f9
Remove link to latest pre-release in documentation 2025-04-08 12:05:04 +03:00
7c07952df1
Add paths-ignore to GitHub Actions for build workflow 2025-04-08 12:04:16 +03:00
ea566cfdeb
Update documentation links in index.md 2025-04-08 12:00:56 +03:00
d56de21e74
Refactor GitHub Actions workflows 2025-04-08 11:58:50 +03:00
26a016cb26
Bump version 2025-04-08 11:51:50 +03:00
edfcce2ae9
Update copyright year in bridge.rc to 2025
[no ci]
2025-04-08 11:51:02 +03:00
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
EnderIce2
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
EnderIce2
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
EnderIce2
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
EnderIce2
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
EnderIce2
c15c4df29e
Create CODE_OF_CONDUCT.md 2024-10-30 16:39:46 +02:00
EnderIce2
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
EnderIce2
8786efd144
Fix formatting and add game links 2024-06-02 17:52:52 +03:00
EnderIce2
2024de9408
Add "Known Issues" section
* Using Vesktop may cause issues with some games #4
2024-06-02 17:45:48 +03:00
EnderIce2
e8926edb41
Add more screenshots
* Content Warning
* Hades
* Lethal Company
* vivid/stasis
2024-06-02 17:41:16 +03:00
EnderIce2
9f1a9de3d7
Update actions/checkout and actions/upload-artifact to v4 2024-06-01 19:57:30 +03:00
EnderIce2
ccf09806c9
Print version on program start 2024-06-01 19:39:39 +03:00
EnderIce2
7363ee64d5
Update bridge.manifest with correct assembly name 2024-06-01 19:35:01 +03:00
EnderIce2
a3023e349e
Update gui.png 2024-06-01 02:56:12 +03:00
EnderIce2
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
EnderIce2
a8904bf3f1
Update version 2024-06-01 00:57:06 +03:00
EnderIce2
08feef776b
Update version 2024-05-31 01:18:38 +03:00
EnderIce2
8545467a85
Add .elf file to .gitignore 2024-05-30 05:33:28 +03:00
EnderIce2
20728818d2
Update pipe creation parameters 2024-05-30 04:19:19 +03:00
EnderIce2
751d9ab3b7
Add menu bar and tab navigation for GUI, --version command 2024-05-30 03:28:18 +03:00
EnderIce2
b906c009f4
Update docs 2024-05-29 23:18:53 +03:00
EnderIce2
38e620836e
Update shebang to use bash instead of sh
"function" is a bash builtin
2024-05-29 23:11:14 +03:00
EnderIce2
a640dd5af1
Remove unnecessary code for starting the bridge service 2024-05-29 23:06:16 +03:00
EnderIce2
8fbe00b555
Display installation guide prompt for missing temp directory 2024-05-29 23:02:48 +03:00
EnderIce2
bf98b8784c
Fix formatting and remove unnecessary recursive rm's 2024-05-29 22:55:02 +03:00
EnderIce2
3021f8e4ad
Update installation instructions for MacOS 2024-05-29 21:01:05 +03:00
EnderIce2
655caa2934
Update README.md 2024-05-29 20:35:52 +03:00
EnderIce2
da2a006ffb
Merge pull request #3 from OrigamingWasTaken/master
Add support for the windows service on MacOS
2024-05-29 20:32:54 +03:00
OrigamingWasTaken
e61fbf556e Added github markdown link 2024-05-29 17:04:26 +02:00
OrigamingWasTaken
2dce976038 Clearer instructions (pt1) 2024-05-29 17:02:13 +02:00
Origaming
2fe1dd5ece
Update README.md 2024-05-27 22:13:16 +02:00
EnderIce2
c23af8300c
Increase pipe buffer 2024-05-23 06:00:45 +03:00
OrigamingWasTaken
14226489d7 Modified readme and script 2024-05-22 16:41:15 +02:00
OrigamingWasTaken
66f3bc53f1 readme 2024-05-21 23:48:12 +02:00
OrigamingWasTaken
dcd9cf520e Modified README and added warning in bridge.c 2024-05-21 23:46:19 +02:00
OrigamingWasTaken
8dd5952a64 MacOS Fix 2024-05-21 23:39:51 +02:00
EnderIce2
81aa5d8d18
Remove unused macro 2024-05-13 08:42:23 +03:00
EnderIce2
3679108829
Add logo to docs site 2024-05-13 08:31:00 +03:00
EnderIce2
77c3dabf79
Enable Visual Styles 2024-05-13 08:28:03 +03:00
EnderIce2
a6a12720ad
Update .vscode files 2024-05-13 08:03:15 +03:00
EnderIce2
231658ff2b
Update compiling instructions 2024-05-13 06:48:14 +03:00
EnderIce2
6e99e11250
Add Stop option in GUI 2024-05-13 06:37:23 +03:00
EnderIce2
8cd44da368
Use mmap() once 2024-05-13 06:25:37 +03:00
EnderIce2
ac590912bc
Merge GitHub workflows 2024-05-13 05:39:15 +03:00
EnderIce2
1ba2faeb25
Add docs 2024-05-13 05:30:59 +03:00
EnderIce2
bedd99a593
Let the bridge to start without being installed from GUI 2024-05-13 03:45:12 +03:00
EnderIce2
e9f6b969b6
Add --rpc command to manually specify the location of discord-ipc-0 2024-05-13 02:39:19 +03:00
EnderIce2
5d0e6c4026
Retry connection after EOF count exceeded 2024-05-07 22:09:04 +03:00
EnderIce2
418e53e7f7
Update LICENSE 2024-04-18 17:30:22 +03:00
EnderIce2
03d5e8dbc8
Add macOS support in GUI 2024-04-05 23:06:32 +03:00
EnderIce2
e5a09024ff
Add GUI creation if no command line arguments are provided 2024-04-05 22:39:14 +03:00
EnderIce2
aeda375cba
Update file paths 2024-04-05 22:36:05 +03:00
EnderIce2
7af098ab26
Add script for Steam (#2) 2024-04-05 21:11:45 +03:00
EnderIce2
7461479cbb
Added captions to MessageBoxes 2024-04-04 04:05:53 +03:00
EnderIce2
299408d10e
Update Makefile to use x86_64-w64-mingw32-gcc 2024-03-07 06:48:54 +02:00
EnderIce2
8b356b65b8
Add macOS support 2024-03-07 06:48:50 +02:00
EnderIce2
66a20f51c4
Update platform check and add help option 2024-03-07 06:48:29 +02:00
EnderIce2
d5af2277c5
Prevent installing as service on macOS 2024-03-07 06:47:56 +02:00
EnderIce2
16291ec0fd
Add macOS instructions 2024-03-07 06:47:35 +02:00
EnderIce2
56168a6d3d
Add socket system call constants 2024-01-26 03:35:34 +02:00
EnderIce2
d2b23eb2e8
Check if it's running under Linux 2024-01-26 03:34:00 +02:00
EnderIce2
cba4fa1e2b
Update README.md 2023-12-17 01:25:19 +02:00
EnderIce2
8c7b336c0e
Update README.md 2023-10-13 19:20:39 +03:00
EnderIce2
334c4e0479
Update README.md 2023-10-13 04:21:42 +03:00
50 changed files with 1878 additions and 298 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.

35
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: Build Project
on:
push:
branches: [ "master" ]
paths-ignore:
- 'ISSUE_TEMPLATE/**'
- 'workflows/**'
- '.vsocde/**'
- 'docs/**'
pull_request:
branches: [ "master" ]
paths-ignore:
- 'ISSUE_TEMPLATE/**'
- 'workflows/**'
- '.vsocde/**'
- 'docs/**'
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

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

39
.github/workflows/docs.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Deploy Documentation
on:
push:
branches: [ master ]
paths:
- docs/**
- mkdocs.yml
pull_request:
branches: [ "master" ]
paths:
- docs/**
- mkdocs.yml
permissions:
contents: write
jobs:
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: pip install mkdocs-video
- run: mkdocs gh-deploy --force

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,26 @@
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)
CWARNFLAGS = -Wno-int-conversion -Wno-incompatible-pointer-types
CFLAGS = -std=c17 -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) $(CWARNFLAGS) $(DBGFLAGS) -c $< -o $@
clean:
rm -f $(C_OBJECTS) build/bridge.exe
rm -f $(C_OBJECTS) build/bridge.exe bridge.res

104
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,66 @@ 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.sh install` and to **remove** it simply run `./launchd.sh 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.*
More details on how to install the LaunchAgent can be found in the [documentation](https://enderice2.github.io/rpc-bridge/).
## 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).

440
bridge.c
View File

@ -4,22 +4,54 @@
#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_mmap 0x20000C5
#define __darwin_fcntl 0x200005C
#define __darwin_sysctl 0x20000CA
#define O_RDONLY 00
/* macos & linux are the same for PROT_READ, PROT_WRITE, MAP_FIXED & MAP_PRIVATE */
#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 __darwin_MAP_ANON 0x1000
#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 +68,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 +83,152 @@ 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)
{
if (IsLinux)
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
else
{
if (flags & MAP_ANON)
{
flags &= ~MAP_ANON;
flags |= __darwin_MAP_ANON;
}
return darwin_syscall(__darwin_mmap, 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 +258,109 @@ 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",
};
int sockRet = 0;
for (int i = 0; i < sizeof(discordUnixSockets) / sizeof(discordUnixSockets[0]); i++)
{
size_t pipePathLen = strlen(runtime) + strlen(discordUnixSockets[i]) + 1;
char *pipePath = malloc(pipePathLen);
for (int j = 0; j < 16; j++)
{
if (IsLinux)
{
struct sockaddr_un *socketAddr = sys_mmap(0x23000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
print("Socket address allocated at %#lx\n", socketAddr);
socketAddr->sun_family = AF_UNIX;
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)(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(SYS_CONNECT, socketArgs);
}
else
{
struct sockaddr_un socketAddr;
socketAddr.sun_family = AF_UNIX;
char *pipePath = NULL;
int sockRet = -1;
for (int i = 0; i < sizeof(discordUnixPipes) / sizeof(discordUnixPipes[0]); i++)
{
pipePath = malloc(strlen(runtime) + strlen(discordUnixPipes[i]) + 1);
strcpy(pipePath, runtime);
strcat(pipePath, discordUnixPipes[i]);
snprintf(pipePath, pipePathLen, discordUnixSockets[i], runtime, j);
strcpy_s(socketAddr.sun_path, sizeof(socketAddr.sun_path), pipePath);
print("Probing %s\n", pipePath);
print("Connecting to %s\n", pipePath);
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
}
unsigned long socketArgs[] = {
(unsigned long)fd,
(unsigned long)&socketAddr,
sizeof(socketAddr)};
sockRet = sys_socketcall(3, socketArgs);
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 +368,44 @@ 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;
if (IsLinux)
l_buffer = sys_mmap(0x25000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
else
l_buffer = malloc(BUFFER_LENGTH);
print("Buffer in thread allocated at %#lx\n", l_buffer);
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 +413,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 +423,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 +438,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 +448,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 +466,19 @@ 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;
if (IsLinux)
l_buffer = sys_mmap(0x26000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
else
l_buffer = malloc(BUFFER_LENGTH);
print("Buffer out thread allocated at %#lx\n", l_buffer);
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 +487,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 +497,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 +511,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 +528,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 +543,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 +562,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) 2025 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" "$@"

View File

@ -1,3 +0,0 @@
@echo off
START /WAIT bridge.exe --install

100
build/launchd.sh Executable file
View File

@ -0,0 +1,100 @@
#!/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 is_installed() {
if [ -f "$AGENT" ]; then
launchctl list | grep -q "com.enderice2.rpc-bridge"
if [ $? -eq 0 ]; then
return 0
fi
fi
return 1
}
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)
if is_installed; then
echo "LaunchAgent is already installed."
exit 0
fi
install
;;
remove)
if ! is_installed; then
echo "LaunchAgent is not installed. Continuing anyway."
fi
remove
;;
*)
echo "Invalid argument. Please use 'install' or 'remove'."
exit 1
;;
esac

View File

@ -1,3 +0,0 @@
@echo off
START /WAIT bridge.exe --uninstall

35
docs/README.md Normal file
View File

@ -0,0 +1,35 @@
# Documentation
> **Note:** This documentation is built and deployed using [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) (with mkdocs-video) via GitHub Actions. For the best experience, view it on the [published site](https://enderice2.github.io/rpc-bridge/) or with MkDocs locally. Some features (such as tabs, videos, or special formatting) may not display correctly in plain Markdown viewers.
This repository contains the documentation for the project. It is written in Markdown and rendered using MkDocs.
## Getting Started
To view the documentation locally, install the required Python packages:
```bash
pip install mkdocs mkdocs-material mkdocs-video
```
Once installed, you can serve the documentation locally by running:
```bash
mkdocs serve
```
This will start a local web server and you can view the documentation in your browser at `http://127.0.0.1:8000`.
## Contributing
If you want to contribute to the documentation, please follow these steps:
1. Fork the repository.
2. Make your changes in a branch.
3. Submit a pull request.
Please ensure that your changes are consistent with the existing style and structure of the documentation.
## License
This documentation is licensed under the MIT License. See the LICENSE file for more information.

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

Binary file not shown.

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

83
docs/index.md Normal file
View File

@ -0,0 +1,83 @@
# 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/workflows/build.yml "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.
---
## My game is not showing up in Discord
If your game is not showing up in Discord, please check the following:
- The game you are playing has [Rich Presence](https://discord.com/developers/docs/rich-presence/overview) support!
- Some games may not have this feature. It's up to developers of the game to implement it.
This is not an issue related to the bridge.
- You followed the installation steps correctly.
- You are using the latest version of the bridge. Currently is ![GitHub Release](https://img.shields.io/github/v/release/EnderIce2/rpc-bridge?style=flat-square&label=%20).
### I still want to see the game in Discord!
This is outside the scope of this project, but here are some workarounds:
- You can manually add the game to Discord by going to `User Settings >` under `Activity Settings` in `Registered Games` tab. [Official Article](https://support.discord.com/hc/en-us/articles/7931156448919-Activity-Status-Recent-Activity#h_01HTJA8QV5ABSA6FY6GEPMA946)
- Tip: You can rename the game to whatever you want.
---
## 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`.
---
## 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).
---

88
docs/linux.md Normal file
View File

@ -0,0 +1,88 @@
# 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`
## 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`.
[^1]: As requested [here](https://github.com/EnderIce2/rpc-bridge/issues/2).

28
docs/macos.md Normal file
View File

@ -0,0 +1,28 @@
# Installation
Installation will copy itself to `C:\windows\bridge.exe` and create a Windows service.
Logs are stored in `C:\windows\logs\bridge.log`.
## Preparing macOS for Installation
Before proceeding with the installation, you need to set up a **LaunchAgent** due to the way `$TMPDIR` works on macOS.
- 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.sh install` and to **remove** it simply run `./launchd.sh remove`.
The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
## Video Tutorial on how to install the LaunchAgent + bridge inside CrossOver
![type:video](assets/macos-crossover.webm){: style='width: 66%; height: 20vw;'}
## 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.
## Run without installing the service
If you prefer not to use the service, you can manually run `bridge.exe` within the prefix, and click on `Start` in the GUI.

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.
## CLI
- `--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,19 +92,7 @@ void print(char const *fmt, ...)
va_end(args);
}
int main(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)
void HandleArguments(int argc, char *argv[])
{
if (strcmp(argv[1], "--service") == 0)
{
@ -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);
}

82
mkdocs.yml Normal file
View File

@ -0,0 +1,82 @@
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/brightness-auto
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: black
accent: indigo
toggle:
icon: material/brightness-4
name: Switch to light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: indigo
accent: indigo
toggle:
icon: material/brightness-7
name: Switch to system preference
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
- mkdocs-video:
is_video: true
video_controls: true
video_loop: false
video_muted: false
nav:
- Home: index.md
- Linux: linux.md
- macOS: macos.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, 4, 0, 0
#define VER_VERSION_STR "1.4.0.0\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);
}