Compare commits
No commits in common. "master" and "v1.1-rc4" have entirely different histories.
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
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
@ -1,35 +0,0 @@
|
|||||||
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
|
|
24
.github/workflows/c-cpp.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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
@ -1,39 +0,0 @@
|
|||||||
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
|
|
4
.gitignore
vendored
@ -1,4 +1,2 @@
|
|||||||
*.o
|
*.o
|
||||||
*.exe
|
*.exe
|
||||||
*.res
|
|
||||||
*.elf
|
|
7
.vscode/c_cpp_properties.json
vendored
@ -5,13 +5,10 @@
|
|||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/**"
|
"${workspaceFolder}/**"
|
||||||
],
|
],
|
||||||
"defines": [
|
"defines": [],
|
||||||
"GIT_COMMIT",
|
|
||||||
"GIT_BRANCH"
|
|
||||||
],
|
|
||||||
"cStandard": "c17",
|
"cStandard": "c17",
|
||||||
"cppStandard": "gnu++17",
|
"cppStandard": "gnu++17",
|
||||||
"intelliSenseMode": "windows-gcc-x64"
|
"intelliSenseMode": "windows-gcc-x86"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": 4
|
"version": 4
|
||||||
|
11
.vscode/settings.json
vendored
@ -1,3 +1,12 @@
|
|||||||
{
|
{
|
||||||
"C_Cpp.default.compilerPath": "/usr/bin/x86_64-w64-mingw32-gcc",
|
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc",
|
||||||
|
"files.associations": {
|
||||||
|
"*.su": "tsv",
|
||||||
|
"thread": "cpp",
|
||||||
|
"bitset": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"windows.h": "c",
|
||||||
|
"namedpipeapi.h": "c",
|
||||||
|
"*.rh": "c"
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,128 +0,0 @@
|
|||||||
# 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.
|
|
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2025 EnderIce2
|
Copyright (c) 2024 EnderIce2
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
20
Makefile
@ -1,26 +1,24 @@
|
|||||||
C_SOURCES = $(shell find ./ -type f -name '*.c')
|
C_SOURCES = $(shell find ./ -type f -name '*.c')
|
||||||
C_OBJECTS = $(C_SOURCES:.c=.o)
|
C_OBJECTS = $(C_SOURCES:.c=.o)
|
||||||
|
|
||||||
GIT_COMMIT = $(shell git rev-parse --short HEAD)
|
CFLAGS = -std=c17 -Wno-int-conversion
|
||||||
GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
|
LFLAGS =
|
||||||
|
|
||||||
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
|
# DBGFLAGS = -Wl,--export-all-symbols -g -O0 -ggdb3 -Wall
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
# This is valid only if this directory is a subdirectory of drive_c
|
||||||
|
install: build
|
||||||
|
cp build/bridge.exe ../bridge.exe
|
||||||
|
|
||||||
build: $(C_OBJECTS)
|
build: $(C_OBJECTS)
|
||||||
$(info Linking)
|
$(info Linking)
|
||||||
x86_64-w64-mingw32-windres bridge.rc -O coff -o bridge.res
|
x86_64-w64-mingw32-gcc $(C_OBJECTS) $(LFLAGS) $(DBGFLAGS) -o build/bridge.exe
|
||||||
x86_64-w64-mingw32-gcc $(C_OBJECTS) bridge.res $(LFLAGS) $(DBGFLAGS) -o build/bridge.exe
|
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(info Compiling $<)
|
$(info Compiling $<)
|
||||||
x86_64-w64-mingw32-gcc $(CFLAGS) $(CWARNFLAGS) $(DBGFLAGS) -c $< -o $@
|
x86_64-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(C_OBJECTS) build/bridge.exe bridge.res
|
rm -f $(C_OBJECTS) build/bridge.exe
|
||||||
|
111
README.md
@ -1,8 +1,8 @@
|
|||||||
# Discord RPC Bridge for Wine
|
# Discord RPC Bridge for Wine
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Simple bridge that allows you to use Discord Rich Presence with Wine games/software.
|
Simple bridge that allows you to use Discord Rich Presence with Wine games/software.
|
||||||
|
|
||||||
@ -12,66 +12,121 @@ This bridge takes advantage of the Windows service implementation in Wine, elimi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Discord RPC Bridge for Wine](#discord-rpc-bridge-for-wine)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
|
- [Installation \& Usage](#installation--usage)
|
||||||
|
- [Installing inside a prefix](#installing-inside-a-prefix)
|
||||||
|
- [Wine (~/.wine)](#wine-wine)
|
||||||
|
- [Lutris](#lutris)
|
||||||
|
- [Steam](#steam)
|
||||||
|
- [If you use Flatpak](#if-you-use-flatpak)
|
||||||
|
- [Run without installing the service](#run-without-installing-the-service)
|
||||||
|
- [About macOS](#about-macos)
|
||||||
|
- [Compiling from source](#compiling-from-source)
|
||||||
|
- [Command line arguments](#command-line-arguments)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
- [Demo](#demo)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
## Installation & Usage
|
## Installation & Usage
|
||||||
|
|
||||||
Installation will copy itself to `C:\windows\bridge.exe` and create a Windows service.
|
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.
|
||||||
Logs are stored in `C:\windows\logs\bridge.log`.
|
|
||||||
|
If you prefer not to use the service for any reason, please refer to the [Run without installing the service](#run-without-installing-the-service) section.
|
||||||
|
|
||||||
#### Installing inside a prefix
|
#### Installing inside a prefix
|
||||||
|
|
||||||
##### Wine (~/.wine)
|
##### Wine (~/.wine)
|
||||||
|
|
||||||
- Double click `bridge.exe` and click `Install`.
|
- Open terminal in `build`
|
||||||
- 
|
- Run `$ wine cmd` and `C:\> install.bat`
|
||||||
- To remove, the same process can be followed, but click `Remove` instead.
|
- 
|
||||||
|
- To remove, run `C:\> remove.bat`
|
||||||
*Note, an [extra step](https://github.com/EnderIce2/rpc-bridge?tab=readme-ov-file#macos) is needed on MacOS*
|
- 
|
||||||
|
- Note: Copying files are not required here.
|
||||||
|
|
||||||
##### Lutris
|
##### Lutris
|
||||||
|
|
||||||
- Click on a game and select `Run EXE inside Wine prefix`.
|
- Right click on the game and select `Browse files`
|
||||||
- 
|
- 
|
||||||
- The same process can be followed as in Wine.
|
- Copy contents of `build` to the game's prefix `drive_c`
|
||||||
|
- 
|
||||||
|
- To install open the console
|
||||||
|
- 
|
||||||
|
- And run `C:\> install.bat` (make sure you are in `C:\`!)
|
||||||
|
- 
|
||||||
|
|
||||||
##### Steam
|
##### Steam
|
||||||
|
|
||||||
- Right click on the game and select `Properties`.
|
- Open [Protontricks](https://github.com/Matoking/protontricks) and select the game you want to install the bridge to
|
||||||
- Under `Set Launch Options`, add the following:
|
- Select `Select the default wineprefix`
|
||||||
- 
|
- Select `Browse files` and copy contents of `build` to the game's prefix `drive_c`
|
||||||
- The `bridge.sh` script must be in the same directory as `bridge.exe`.
|
- Select `Run a Wine cmd shell` and run `C:\> install.bat`
|
||||||
|
- If you are not in `C:\`, type `c:` and press enter
|
||||||
|
- 
|
||||||
|
|
||||||
#### If you use Flatpak
|
#### If you use Flatpak
|
||||||
|
|
||||||
- If you are running Steam, Lutris, etc in a Flatpak, you will need to allow the bridge to access the `/run/user/1000/discord-ipc-0` file.
|
- If you are running Steam, Lutris, etc. in a Flatpak, you will need to allow the bridge to access the `/run/user/1000/discord-ipc-0` file.
|
||||||
- ##### By using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal)
|
- ##### By using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal)
|
||||||
- Add `xdg-run/discord-ipc-0` under `Filesystems` category
|
- Add `xdg-run/discord-ipc-0` under `Filesystems` category
|
||||||
- 
|
- 
|
||||||
- ##### By using the terminal
|
- ##### By using the terminal
|
||||||
- Per application
|
- Per application
|
||||||
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
|
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
|
||||||
- Globally
|
- Globally
|
||||||
- `flatpak override --user --filesystem=xdg-run/discord-ipc-0`
|
- `flatpak override --user --filesystem=xdg-run/discord-ipc-0`
|
||||||
|
|
||||||
##### MacOS
|
## Run without installing the service
|
||||||
|
|
||||||
The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you will have to install a **LaunchAgent**.
|
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.
|
||||||
|
|
||||||
- Download the latest build from the [releases](https://github.com/EnderIce2/rpc-bridge/releases)
|
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.
|
||||||
- Open the archive and make the `launchd.sh` script executable by doing: `chmod +x launchd.sh`
|
Example:
|
||||||
- To **install** the LaunchAgent, run `./launchd.sh install` and to **remove** it simply run `./launchd.sh remove`.
|
- 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`
|
||||||
|
|
||||||
The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
|
In Wine, all you need to do is run `bridge.exe`.
|
||||||
|
|
||||||
*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.*
|
- When running the program manually without providing any arguments, it will simply initiate the bridge and wait indefinitely until it's closed.
|
||||||
|
|
||||||
More details on how to install the LaunchAgent can be found in the [documentation](https://enderice2.github.io/rpc-bridge/).
|
## About macOS
|
||||||
|
|
||||||
|
The bridge works similarly on macOS as it does on Linux, but it can't be registered as a service due to TMPDIR limitations. macOS users must manually run `bridge.exe` when needed.
|
||||||
|
|
||||||
|
To run `bridge.exe` on macOS, navigate to its directory in the terminal and execute `wine bridge.exe`. (or double click it in the Finder)
|
||||||
|
|
||||||
## Compiling from source
|
## Compiling from source
|
||||||
|
|
||||||
- Install the `wine`, `gcc-mingw-w64` and `make` packages.
|
- Install the `wine`, `x86_64-w64-mingw32-gcc` and `make` packages.
|
||||||
- Open a terminal in the directory that contains this file and run `make`.
|
- Open a terminal in the directory that contains this file and run `make`.
|
||||||
- The compiled executable will be located in `build/bridge.exe`.
|
- The compiled executable will be located in `build/bridge.exe`.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
|
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
|
||||||
|
255
bridge.c
@ -20,13 +20,11 @@
|
|||||||
#define __darwin_close 0x2000006
|
#define __darwin_close 0x2000006
|
||||||
#define __darwin_socket 0x2000061
|
#define __darwin_socket 0x2000061
|
||||||
#define __darwin_connect 0x2000062
|
#define __darwin_connect 0x2000062
|
||||||
#define __darwin_mmap 0x20000C5
|
|
||||||
#define __darwin_fcntl 0x200005C
|
#define __darwin_fcntl 0x200005C
|
||||||
#define __darwin_sysctl 0x20000CA
|
#define __darwin_sysctl 0x20000CA
|
||||||
|
|
||||||
#define O_RDONLY 00
|
#define O_RDONLY 00
|
||||||
|
|
||||||
/* macos & linux are the same for PROT_READ, PROT_WRITE, MAP_FIXED & MAP_PRIVATE */
|
|
||||||
#define PROT_READ 1
|
#define PROT_READ 1
|
||||||
#define PROT_WRITE 2
|
#define PROT_WRITE 2
|
||||||
#define MAP_PRIVATE 0x02
|
#define MAP_PRIVATE 0x02
|
||||||
@ -34,8 +32,6 @@
|
|||||||
#define MAP_ANON 0x20
|
#define MAP_ANON 0x20
|
||||||
#define MAP_FAILED ((void *)-1)
|
#define MAP_FAILED ((void *)-1)
|
||||||
|
|
||||||
#define __darwin_MAP_ANON 0x1000
|
|
||||||
|
|
||||||
#define SYS_SOCKET 1
|
#define SYS_SOCKET 1
|
||||||
#define SYS_CONNECT 3
|
#define SYS_CONNECT 3
|
||||||
|
|
||||||
@ -47,8 +43,6 @@
|
|||||||
__attribute__((__always_inline__, __gnu_inline__))
|
__attribute__((__always_inline__, __gnu_inline__))
|
||||||
#define naked __attribute__((naked))
|
#define naked __attribute__((naked))
|
||||||
|
|
||||||
#define BUFFER_LENGTH 2048
|
|
||||||
|
|
||||||
typedef unsigned short sa_family_t;
|
typedef unsigned short sa_family_t;
|
||||||
typedef char *caddr_t;
|
typedef char *caddr_t;
|
||||||
typedef unsigned socklen_t;
|
typedef unsigned socklen_t;
|
||||||
@ -69,8 +63,6 @@ LPTSTR GetErrorMessage();
|
|||||||
extern BOOL RunningAsService;
|
extern BOOL RunningAsService;
|
||||||
BOOL RetryNewConnection;
|
BOOL RetryNewConnection;
|
||||||
BOOL IsLinux;
|
BOOL IsLinux;
|
||||||
HANDLE hOut = NULL;
|
|
||||||
HANDLE hIn = NULL;
|
|
||||||
|
|
||||||
static force_inline int linux_syscall(int num,
|
static force_inline int linux_syscall(int num,
|
||||||
int arg1, int arg2, int arg3,
|
int arg1, int arg2, int arg3,
|
||||||
@ -138,17 +130,8 @@ static inline int sys_close(int fd)
|
|||||||
|
|
||||||
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
|
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
|
||||||
{
|
{
|
||||||
if (IsLinux)
|
assert(IsLinux);
|
||||||
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
|
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)
|
static inline int sys_munmap(unsigned int *addr, size_t length)
|
||||||
@ -179,7 +162,6 @@ static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
|
|||||||
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
|
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void *environStr = NULL;
|
|
||||||
char *native_getenv(const char *name)
|
char *native_getenv(const char *name)
|
||||||
{
|
{
|
||||||
static char lpBuffer[512];
|
static char lpBuffer[512];
|
||||||
@ -206,29 +188,35 @@ char *native_getenv(const char *name)
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* I hope the 0x20000 is okay */
|
/* I hope the 0x10000 is okay */
|
||||||
if (environStr == NULL)
|
void *environStr = sys_mmap(0x10000, 4096, PROT_READ | PROT_WRITE,
|
||||||
environStr = sys_mmap(0x20000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
||||||
|
if (environStr == MAP_FAILED)
|
||||||
|
{
|
||||||
|
print("Failed to allocate environ string: %d\n", (intptr_t)environStr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if ((uintptr_t)environStr > 0x7effffff)
|
if ((uintptr_t)environStr > 0x7effffff)
|
||||||
print("Warning: environStr %#lx is above 2GB\n", environStr);
|
print("Warning: environStr %p is above 2GB\n", environStr);
|
||||||
|
|
||||||
const char *linux_environ = "/proc/self/environ";
|
const char *linux_environ = "/proc/self/environ";
|
||||||
memcpy(environStr, linux_environ, strlen(linux_environ) + 1);
|
memcpy(environStr, linux_environ, strlen(linux_environ) + 1);
|
||||||
|
|
||||||
int fd = sys_open(environStr, O_RDONLY, 0);
|
int fd = sys_open(environStr, O_RDONLY, 0);
|
||||||
|
|
||||||
|
sys_munmap(environStr, 4096);
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
print("Failed to open /proc/self/environ: %d\n", fd);
|
print("Failed to open /proc/self/environ: %d\n", fd);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *buffer = sys_mmap(0x22000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
char buffer[4096];
|
||||||
char *result = NULL;
|
char *result = NULL;
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
||||||
while ((bytesRead = sys_read(fd, buffer, 0x1000 - 1)) > 0)
|
while ((bytesRead = sys_read(fd, buffer, sizeof(buffer) - 1)) > 0)
|
||||||
{
|
{
|
||||||
buffer[bytesRead] = '\0';
|
buffer[bytesRead] = '\0';
|
||||||
char *env = buffer;
|
char *env = buffer;
|
||||||
@ -262,96 +250,55 @@ void ConnectToSocket(int fd)
|
|||||||
if (IsLinux)
|
if (IsLinux)
|
||||||
runtime = native_getenv("XDG_RUNTIME_DIR");
|
runtime = native_getenv("XDG_RUNTIME_DIR");
|
||||||
else
|
else
|
||||||
{
|
|
||||||
runtime = native_getenv("TMPDIR");
|
runtime = native_getenv("TMPDIR");
|
||||||
if (runtime == NULL)
|
if (runtime == NULL)
|
||||||
{
|
{
|
||||||
runtime = "/tmp/rpc-bridge/tmpdir";
|
print("IPC directory not set\n");
|
||||||
print("IPC directory not set, fallback to /tmp/rpc-bridge/tmpdir\n");
|
if (!RunningAsService)
|
||||||
|
MessageBox(NULL, "IPC directory not set",
|
||||||
// Check if the directory exists
|
"Environment variable not set",
|
||||||
DWORD dwAttrib = GetFileAttributes(runtime);
|
MB_OK | MB_ICONSTOP);
|
||||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
|
ExitProcess(1);
|
||||||
{
|
|
||||||
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("IPC directory: %s\n", runtime);
|
print("IPC directory: %s\n", runtime);
|
||||||
|
|
||||||
/* TODO: check for multiple discord instances and create a pipe for each */
|
/* TODO: check for multiple discord instances and create a pipe for each */
|
||||||
const char *discordUnixSockets[] = {
|
const char *discordUnixPipes[] = {
|
||||||
"%s/discord-ipc-%d",
|
"/discord-ipc-0",
|
||||||
"%s/app/com.discordapp.Discord/discord-ipc-%d",
|
"/snap.discord/discord-ipc-0",
|
||||||
"%s/.flatpak/dev.vencord.Vesktop/xdg-run/discord-ipc-%d",
|
"/app/com.discordapp.Discord/discord-ipc-0",
|
||||||
"%s/snap.discord/discord-ipc-%d",
|
|
||||||
"%s/snap.discord-canary/discord-ipc-%d",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int sockRet = 0;
|
struct sockaddr_un socketAddr;
|
||||||
for (int i = 0; i < sizeof(discordUnixSockets) / sizeof(discordUnixSockets[0]); i++)
|
socketAddr.sun_family = AF_UNIX;
|
||||||
|
char *pipePath = NULL;
|
||||||
|
int sockRet = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(discordUnixPipes) / sizeof(discordUnixPipes[0]); i++)
|
||||||
{
|
{
|
||||||
size_t pipePathLen = strlen(runtime) + strlen(discordUnixSockets[i]) + 1;
|
pipePath = malloc(strlen(runtime) + strlen(discordUnixPipes[i]) + 1);
|
||||||
char *pipePath = malloc(pipePathLen);
|
strcpy(pipePath, runtime);
|
||||||
|
strcat(pipePath, discordUnixPipes[i]);
|
||||||
|
strcpy_s(socketAddr.sun_path, sizeof(socketAddr.sun_path), pipePath);
|
||||||
|
|
||||||
for (int j = 0; j < 16; j++)
|
print("Connecting to %s\n", pipePath);
|
||||||
|
|
||||||
|
if (IsLinux)
|
||||||
{
|
{
|
||||||
if (IsLinux)
|
unsigned long socketArgs[] = {
|
||||||
{
|
(unsigned long)fd,
|
||||||
struct sockaddr_un *socketAddr = sys_mmap(0x23000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
|
(unsigned long)(intptr_t)&socketAddr,
|
||||||
print("Socket address allocated at %#lx\n", socketAddr);
|
sizeof(socketAddr)};
|
||||||
socketAddr->sun_family = AF_UNIX;
|
|
||||||
|
|
||||||
snprintf(pipePath, pipePathLen, discordUnixSockets[i], runtime, j);
|
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
|
||||||
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;
|
|
||||||
|
|
||||||
snprintf(pipePath, pipePathLen, discordUnixSockets[i], runtime, j);
|
|
||||||
strcpy_s(socketAddr.sun_path, sizeof(socketAddr.sun_path), pipePath);
|
|
||||||
print("Probing %s\n", pipePath);
|
|
||||||
|
|
||||||
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
|
|
||||||
}
|
|
||||||
|
|
||||||
print(" error: %d\n", sockRet);
|
|
||||||
if (sockRet >= 0)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
sockRet = sys_connect(fd, (caddr_t)&socketAddr, sizeof(socketAddr));
|
||||||
|
|
||||||
if (sockRet >= 0)
|
|
||||||
{
|
|
||||||
print("Connecting to %s\n", pipePath);
|
|
||||||
free(pipePath);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
free(pipePath);
|
free(pipePath);
|
||||||
|
if (sockRet >= 0)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sockRet < 0)
|
if (sockRet < 0)
|
||||||
@ -365,25 +312,22 @@ void ConnectToSocket(int fd)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HANDLE hOut = NULL;
|
||||||
void PipeBufferInThread(LPVOID lpParam)
|
void PipeBufferInThread(LPVOID lpParam)
|
||||||
{
|
{
|
||||||
bridge_thread *bt = (bridge_thread *)lpParam;
|
bridge_thread *bt = (bridge_thread *)lpParam;
|
||||||
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;
|
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)
|
while (TRUE)
|
||||||
{
|
{
|
||||||
char buffer[BUFFER_LENGTH];
|
char buffer[1024];
|
||||||
int read = sys_read(bt->fd, l_buffer, BUFFER_LENGTH);
|
int read = sys_read(bt->fd, buffer, sizeof(buffer));
|
||||||
|
|
||||||
if (unlikely(read < 0))
|
if (unlikely(read < 0))
|
||||||
{
|
{
|
||||||
print("Failed to read from unix pipe: %d\n", read);
|
print("Failed to read from unix pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -405,16 +349,14 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
}
|
}
|
||||||
EOFCount = 0;
|
EOFCount = 0;
|
||||||
|
|
||||||
memcpy(buffer, l_buffer, read);
|
|
||||||
|
|
||||||
print("Reading %d bytes from unix pipe: \"", read);
|
print("Reading %d bytes from unix pipe: \"", read);
|
||||||
for (int i = 0; i < read; i++)
|
for (int i = 0; i < read; i++)
|
||||||
print("%c", buffer[i]);
|
print("%c", buffer[i]);
|
||||||
print("\"\n");
|
print("\"\n");
|
||||||
|
|
||||||
DWORD dwWritten;
|
DWORD dwWritten;
|
||||||
WINBOOL bResult = WriteFile(bt->hPipe, buffer, read, &dwWritten, NULL);
|
if (unlikely(!WriteFile(bt->hPipe, buffer, read,
|
||||||
if (unlikely(bResult == FALSE))
|
&dwWritten, NULL)))
|
||||||
{
|
{
|
||||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||||
{
|
{
|
||||||
@ -423,14 +365,16 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Failed to read from pipe: %s\n", GetErrorMessage());
|
print("Failed to read from pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(dwWritten < 0))
|
if (unlikely(dwWritten < 0))
|
||||||
{
|
{
|
||||||
print("Failed to write to pipe: %s\n", GetErrorMessage());
|
print("Failed to write to pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -438,8 +382,8 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
while (dwWritten < read)
|
while (dwWritten < read)
|
||||||
{
|
{
|
||||||
int last_written = dwWritten;
|
int last_written = dwWritten;
|
||||||
WINBOOL bResult = WriteFile(bt->hPipe, buffer + dwWritten, read - dwWritten, &dwWritten, NULL);
|
if (unlikely(!WriteFile(bt->hPipe, buffer + dwWritten,
|
||||||
if (unlikely(bResult == FALSE))
|
read - dwWritten, &dwWritten, NULL)))
|
||||||
{
|
{
|
||||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||||
{
|
{
|
||||||
@ -448,14 +392,16 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Failed to read from pipe: %s\n", GetErrorMessage());
|
print("Failed to read from pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(last_written == dwWritten))
|
if (unlikely(last_written == dwWritten))
|
||||||
{
|
{
|
||||||
print("Failed to write to pipe: %s\n", GetErrorMessage());
|
print("Failed to write to pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -466,19 +412,15 @@ void PipeBufferInThread(LPVOID lpParam)
|
|||||||
void PipeBufferOutThread(LPVOID lpParam)
|
void PipeBufferOutThread(LPVOID lpParam)
|
||||||
{
|
{
|
||||||
bridge_thread *bt = (bridge_thread *)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",
|
||||||
char *l_buffer;
|
bt->fd, bt->hPipe);
|
||||||
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)
|
while (TRUE)
|
||||||
{
|
{
|
||||||
char buffer[BUFFER_LENGTH];
|
char buffer[1024];
|
||||||
DWORD dwRead;
|
DWORD dwRead;
|
||||||
WINBOOL bResult = ReadFile(bt->hPipe, buffer, BUFFER_LENGTH, &dwRead, NULL);
|
|
||||||
if (unlikely(bResult == FALSE))
|
if (unlikely(!ReadFile(bt->hPipe, buffer, sizeof(buffer),
|
||||||
|
&dwRead, NULL)))
|
||||||
{
|
{
|
||||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||||
{
|
{
|
||||||
@ -487,7 +429,8 @@ void PipeBufferOutThread(LPVOID lpParam)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Failed to read from pipe: %s\n", GetErrorMessage());
|
print("Failed to read from pipe: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -497,11 +440,11 @@ void PipeBufferOutThread(LPVOID lpParam)
|
|||||||
print("%c", buffer[i]);
|
print("%c", buffer[i]);
|
||||||
print("\"\n");
|
print("\"\n");
|
||||||
|
|
||||||
memcpy(l_buffer, buffer, dwRead);
|
int written = sys_write(bt->fd, buffer, dwRead);
|
||||||
int written = sys_write(bt->fd, l_buffer, dwRead);
|
|
||||||
if (unlikely(written < 0))
|
if (unlikely(written < 0))
|
||||||
{
|
{
|
||||||
print("Failed to write to socket: %d\n", written);
|
print("Failed to write to socket: %d\n",
|
||||||
|
written);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,7 +454,8 @@ void PipeBufferOutThread(LPVOID lpParam)
|
|||||||
written += sys_write(bt->fd, buffer + written, dwRead - written);
|
written += sys_write(bt->fd, buffer + written, dwRead - written);
|
||||||
if (unlikely(last_written == written))
|
if (unlikely(last_written == written))
|
||||||
{
|
{
|
||||||
print("Failed to write to socket: %s\n", GetErrorMessage());
|
print("Failed to write to socket: %d\n",
|
||||||
|
GetErrorMessage());
|
||||||
Sleep(1000);
|
Sleep(1000);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -543,7 +487,7 @@ NewConnection:
|
|||||||
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
|
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
|
||||||
PIPE_ACCESS_DUPLEX,
|
PIPE_ACCESS_DUPLEX,
|
||||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||||
PIPE_UNLIMITED_INSTANCES, BUFFER_LENGTH, BUFFER_LENGTH, 0, NULL);
|
1, 1024, 1024, 0, NULL);
|
||||||
|
|
||||||
if (hPipe == INVALID_HANDLE_VALUE)
|
if (hPipe == INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
@ -574,31 +518,16 @@ NewConnection:
|
|||||||
int fd;
|
int fd;
|
||||||
if (IsLinux)
|
if (IsLinux)
|
||||||
{
|
{
|
||||||
// unsigned long socketArgs[] = {
|
unsigned long socketArgs[] = {
|
||||||
// (unsigned long)AF_UNIX,
|
(unsigned long)AF_UNIX,
|
||||||
// (unsigned long)SOCK_STREAM,
|
(unsigned long)SOCK_STREAM,
|
||||||
// 0};
|
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);
|
fd = sys_socketcall(SYS_SOCKET, 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
|
else
|
||||||
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
|
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
|
||||||
if (fd == INVALID_SOCKET)
|
print("Socket %d created\n", fd);
|
||||||
{
|
|
||||||
print("invalid socket: %d %d\n", fd, WSAGetLastError());
|
|
||||||
ExitProcess(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
{
|
{
|
||||||
@ -609,24 +538,20 @@ NewConnection:
|
|||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Socket %d created\n", fd);
|
|
||||||
|
|
||||||
ConnectToSocket(fd);
|
ConnectToSocket(fd);
|
||||||
print("Connected to Discord\n");
|
print("Connected to Discord\n");
|
||||||
|
|
||||||
bridge_thread bt = {fd, hPipe};
|
bridge_thread bt = {fd, hPipe};
|
||||||
|
|
||||||
hIn = CreateThread(NULL, 0,
|
HANDLE hIn = CreateThread(NULL, 0,
|
||||||
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
|
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
|
||||||
(LPVOID)&bt,
|
(LPVOID)&bt,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
print("Created in thread %#lx\n", hIn);
|
|
||||||
|
|
||||||
hOut = CreateThread(NULL, 0,
|
hOut = CreateThread(NULL, 0,
|
||||||
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
|
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
|
||||||
(LPVOID)&bt,
|
(LPVOID)&bt,
|
||||||
0, NULL);
|
0, NULL);
|
||||||
print("Created out thread %#lx\n", hOut);
|
|
||||||
|
|
||||||
if (hIn == NULL || hOut == NULL)
|
if (hIn == NULL || hOut == NULL)
|
||||||
{
|
{
|
||||||
|
BIN
bridge.ico
Before Width: | Height: | Size: 16 KiB |
@ -1,32 +0,0 @@
|
|||||||
<?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
@ -1,53 +0,0 @@
|
|||||||
#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
|
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#! /bin/sh
|
||||||
|
|
||||||
# This script is used to run Steam Play with the bridge.
|
# This script is used to run Steam Play with the bridge.
|
||||||
# Usage: /path/to/bridge.sh %command%
|
# Usage: /path/to/bridge.sh %command%
|
||||||
@ -21,10 +21,10 @@ TEMP_PATH="$XDG_RUNTIME_DIR"
|
|||||||
TEMP_PATH=${TEMP_PATH:-"$TMPDIR"}
|
TEMP_PATH=${TEMP_PATH:-"$TMPDIR"}
|
||||||
|
|
||||||
VESSEL_PATH="$BRIDGE_PATH"
|
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"
|
IPC_PATHS="$TEMP_PATH /run/user/$UID $TEMP_PATH/snap.discord $TEMP_PATH/app/com.discordapp.Discord"
|
||||||
for discord_ipc in $IPC_PATHS; do
|
for discord_ipc in $IPC_PATHS; do
|
||||||
if [ -S "$discord_ipc"/discord-ipc-? ]; then
|
if [ -S "$discord_ipc"/discord-ipc-0 ]; then
|
||||||
VESSEL_PATH="$BRIDGE_PATH:$(echo "$discord_ipc"/discord-ipc-?)"
|
VESSEL_PATH="$BRIDGE_PATH:$(echo "$discord_ipc"/discord-ipc-0)"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
3
build/install.bat
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
START /WAIT bridge.exe --install
|
||||||
|
|
100
build/launchd.sh
@ -1,100 +0,0 @@
|
|||||||
#!/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
|
|
3
build/remove.bat
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@echo off
|
||||||
|
START /WAIT bridge.exe --uninstall
|
||||||
|
|
@ -1,35 +0,0 @@
|
|||||||
# 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.
|
|
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 1.7 MiB |
Before Width: | Height: | Size: 551 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 945 KiB |
@ -1,83 +0,0 @@
|
|||||||
# Discord RPC Bridge for Wine
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
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 .
|
|
||||||
|
|
||||||
### 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
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
[**Among Us**](https://store.steampowered.com/app/945360/Among_Us/) on Steam
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
[**Content Warning**](https://store.steampowered.com/app/2881650/Content_Warning/) on Steam
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
[**Hades**](https://store.steampowered.com/app/1145360/Hades/) on Steam
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
[**Lethal Company**](https://store.steampowered.com/app/1966720/Lethal_Company/) ([modded](https://thunderstore.io/c/lethal-company/p/mrov/LethalRichPresence/)) on Steam
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
[**vivid/stasis**](https://store.steampowered.com/app/2093940/vividstasis/) on Steam
|
|
||||||
{ width="600" }
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
|
|
||||||
|
|
||||||
---
|
|
@ -1,88 +0,0 @@
|
|||||||
# 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`.
|
|
||||||
- 
|
|
||||||
- To remove, the same process can be followed, but click `Remove` instead.
|
|
||||||
|
|
||||||
### Lutris
|
|
||||||
|
|
||||||
- Click on a game and select `Run EXE inside Wine prefix`.
|
|
||||||
- 
|
|
||||||
- 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:
|
|
||||||
- 
|
|
||||||
<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
|
|
||||||
|
|
||||||
, 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
|
|
||||||

|
|
||||||
|
|
||||||
=== "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).
|
|
@ -1,28 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
{: style='width: 66%; height: 20vw;'}
|
|
||||||
|
|
||||||
## Wine (~/.wine)
|
|
||||||
|
|
||||||
- Double click `bridge.exe` and click `Install`.
|
|
||||||
- 
|
|
||||||
- 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.
|
|
@ -1,35 +0,0 @@
|
|||||||
# Usage
|
|
||||||
|
|
||||||
## GUI
|
|
||||||
|
|
||||||
- When running the program manually without providing any arguments it will show a 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
|
|
203
gui.c
@ -1,11 +1,8 @@
|
|||||||
#include <windowsx.h>
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winuser.h>
|
#include <winuser.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "resource.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The entire code could be better written, but at least it works.
|
* The entire code could be better written, but at least it works.
|
||||||
*
|
*
|
||||||
@ -20,32 +17,19 @@ void CreateBridge();
|
|||||||
extern BOOL IsLinux;
|
extern BOOL IsLinux;
|
||||||
|
|
||||||
HWND hwnd = NULL;
|
HWND hwnd = NULL;
|
||||||
HANDLE hBridge = NULL;
|
|
||||||
extern HANDLE hOut;
|
|
||||||
extern HANDLE hIn;
|
|
||||||
|
|
||||||
BOOL IsAlreadyRunning = FALSE;
|
|
||||||
VOID HandleStartButton(BOOL Silent)
|
VOID HandleStartButton(BOOL Silent)
|
||||||
{
|
{
|
||||||
if (IsAlreadyRunning)
|
if (!IsLinux)
|
||||||
{
|
{
|
||||||
HWND item = GetDlgItem(hwnd, 4);
|
ShowWindow(hwnd, SW_MINIMIZE);
|
||||||
SetWindowText(item, "Do you want to start, install or remove the bridge?");
|
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
||||||
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
|
NULL, 0, NULL);
|
||||||
item = GetDlgItem(hwnd, /* Start Button */ 1);
|
|
||||||
Button_SetText(item, "&Start");
|
HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
|
||||||
EnableWindow(item, FALSE);
|
EnableWindow(item, FALSE);
|
||||||
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
|
item = GetDlgItem(hwnd, 4);
|
||||||
|
SetWindowText(item, "Bridge is running...");
|
||||||
print("Killing %#x, %#lx and waiting for %#lx\n", hIn, hOut, hBridge);
|
|
||||||
if (hIn != NULL)
|
|
||||||
TerminateThread(hIn, 0);
|
|
||||||
if (hOut != NULL)
|
|
||||||
TerminateThread(hOut, 0);
|
|
||||||
WaitForSingleObject(hBridge, INFINITE);
|
|
||||||
|
|
||||||
EnableWindow(item, TRUE);
|
|
||||||
IsAlreadyRunning = FALSE;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,19 +44,7 @@ VOID HandleStartButton(BOOL Silent)
|
|||||||
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
|
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
|
||||||
if (schService == NULL)
|
if (schService == NULL)
|
||||||
{
|
{
|
||||||
print("Service doesn't exist: %s\n", GetErrorMessage());
|
print("OpenService failed: %s\n", GetErrorMessage());
|
||||||
|
|
||||||
/* Service doesn't exist; running without any service */
|
|
||||||
|
|
||||||
hBridge = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
|
||||||
NULL, 0, NULL);
|
|
||||||
|
|
||||||
HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
|
|
||||||
Button_SetText(item, "&Stop");
|
|
||||||
item = GetDlgItem(hwnd, 4);
|
|
||||||
SetWindowText(item, "Bridge is running...");
|
|
||||||
IsAlreadyRunning = TRUE;
|
|
||||||
ShowWindow(hwnd, SW_MINIMIZE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +75,6 @@ VOID HandleStartButton(BOOL Silent)
|
|||||||
CloseServiceHandle(hSCManager);
|
CloseServiceHandle(hSCManager);
|
||||||
if (Silent == FALSE)
|
if (Silent == FALSE)
|
||||||
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
|
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
|
||||||
print("Bridge service started successfully\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VOID HandleInstallButton()
|
VOID HandleInstallButton()
|
||||||
@ -124,44 +95,6 @@ VOID HandleRemoveButton()
|
|||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowLicenseDialog()
|
|
||||||
{
|
|
||||||
HMODULE hModule = GetModuleHandle(NULL);
|
|
||||||
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_LICENSE_TXT), RT_RCDATA);
|
|
||||||
if (!hRes)
|
|
||||||
{
|
|
||||||
MessageBox(NULL, "Resource not found", "Error", MB_OK | MB_ICONERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HGLOBAL hResData = LoadResource(NULL, hRes);
|
|
||||||
if (!hResData)
|
|
||||||
{
|
|
||||||
MessageBox(NULL, "Resource failed to load", "Error", MB_OK | MB_ICONERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD resSize = SizeofResource(NULL, hRes);
|
|
||||||
void *pRes = LockResource(hResData);
|
|
||||||
if (!pRes)
|
|
||||||
{
|
|
||||||
MessageBox(NULL, "Resource failed to lock", "Error", MB_OK | MB_ICONERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *licenseText = (char *)malloc(resSize + 1);
|
|
||||||
if (!licenseText)
|
|
||||||
{
|
|
||||||
MessageBox(NULL, "Memory allocation failed", "Error", MB_OK | MB_ICONERROR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(licenseText, pRes, resSize);
|
|
||||||
licenseText[resSize] = '\0';
|
|
||||||
MessageBoxA(hwnd, licenseText, "About", MB_OK);
|
|
||||||
free(licenseText);
|
|
||||||
}
|
|
||||||
|
|
||||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
{
|
{
|
||||||
switch (msg)
|
switch (msg)
|
||||||
@ -179,29 +112,6 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|||||||
case 3:
|
case 3:
|
||||||
HandleRemoveButton();
|
HandleRemoveButton();
|
||||||
break;
|
break;
|
||||||
case IDM_VIEW_LOG:
|
|
||||||
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", "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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -214,12 +124,6 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
break;
|
break;
|
||||||
case WM_CTLCOLORSTATIC:
|
|
||||||
{
|
|
||||||
HDC hdcStatic = (HDC)wParam;
|
|
||||||
SetBkMode(hdcStatic, TRANSPARENT);
|
|
||||||
return (INT_PTR)(HBRUSH)GetStockObject(NULL_BRUSH);
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||||
}
|
}
|
||||||
@ -228,16 +132,16 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|||||||
|
|
||||||
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
|
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
|
||||||
{
|
{
|
||||||
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
|
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
|
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
|
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON;
|
||||||
|
|
||||||
// if (!IsLinux)
|
if (!IsLinux)
|
||||||
// {
|
{
|
||||||
// *btnInstallStyle |= WS_DISABLED;
|
*btnInstallStyle |= WS_DISABLED;
|
||||||
// *btnRemoveStyle |= WS_DISABLED;
|
*btnRemoveStyle |= WS_DISABLED;
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
|
||||||
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
|
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
|
||||||
@ -256,14 +160,17 @@ VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallSty
|
|||||||
*btnStartStyle |= WS_DISABLED;
|
*btnStartStyle |= WS_DISABLED;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
*btnStartStyle |= WS_DISABLED;
|
||||||
*btnRemoveStyle |= WS_DISABLED;
|
*btnRemoveStyle |= WS_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
CloseServiceHandle(schService);
|
CloseServiceHandle(schService);
|
||||||
CloseServiceHandle(hSCManager);
|
CloseServiceHandle(hSCManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
||||||
LPSTR lpCmdLine, int nCmdShow)
|
LPSTR lpCmdLine, int nCmdShow)
|
||||||
{
|
{
|
||||||
INT btnStartStyle, btnRemoveStyle, btnInstallStyle;
|
INT btnStartStyle, btnRemoveStyle, btnInstallStyle;
|
||||||
SetButtonStyles(&btnStartStyle, &btnRemoveStyle, &btnInstallStyle);
|
SetButtonStyles(&btnStartStyle, &btnRemoveStyle, &btnInstallStyle);
|
||||||
@ -286,7 +193,7 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||||||
|
|
||||||
assert(RegisterClassEx(&wc));
|
assert(RegisterClassEx(&wc));
|
||||||
|
|
||||||
hwnd = CreateWindowEx(WS_EX_WINDOWEDGE,
|
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE,
|
||||||
szClassName,
|
szClassName,
|
||||||
"Discord RPC Bridge",
|
"Discord RPC Bridge",
|
||||||
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
|
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
|
||||||
@ -295,46 +202,25 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||||||
400, 150,
|
400, 150,
|
||||||
NULL, NULL, hInstance, NULL);
|
NULL, NULL, hInstance, NULL);
|
||||||
|
|
||||||
HICON hIcon = LoadIcon(hInstance, "IDI_ICON_128");
|
CreateWindow("STATIC", "Do you want to start, install or remove the bridge?",
|
||||||
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
|
WS_CHILD | WS_VISIBLE | SS_CENTER,
|
||||||
|
0, 0, 400, 50,
|
||||||
|
hwnd, (HMENU)4, hInstance, NULL);
|
||||||
|
|
||||||
HWND hLbl4 = CreateWindowEx(WS_EX_TRANSPARENT,
|
CreateWindow("BUTTON", "Start",
|
||||||
"STATIC", "Do you want to start, install or remove the bridge?",
|
btnStartStyle,
|
||||||
WS_CHILD | WS_VISIBLE | SS_CENTER,
|
50, 50, 100, 30,
|
||||||
0, 15, 400, 25,
|
hwnd, (HMENU)1, hInstance, NULL);
|
||||||
hwnd, (HMENU)4, hInstance, NULL);
|
|
||||||
|
|
||||||
HWND hbtn1 = CreateWindow("BUTTON", "&Start",
|
CreateWindow("BUTTON", "Install",
|
||||||
btnStartStyle,
|
btnInstallStyle,
|
||||||
40, 60, 100, 30,
|
150, 50, 100, 30,
|
||||||
hwnd, (HMENU)1, hInstance, NULL);
|
hwnd, (HMENU)2, hInstance, NULL);
|
||||||
|
|
||||||
HWND hbtn2 = CreateWindow("BUTTON", "&Install",
|
CreateWindow("BUTTON", "Remove",
|
||||||
btnInstallStyle,
|
btnRemoveStyle,
|
||||||
150, 60, 100, 30,
|
250, 50, 100, 30,
|
||||||
hwnd, (HMENU)2, hInstance, NULL);
|
hwnd, (HMENU)3, 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);
|
ShowWindow(hwnd, nCmdShow);
|
||||||
UpdateWindow(hwnd);
|
UpdateWindow(hwnd);
|
||||||
@ -342,11 +228,8 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||||||
MSG msg;
|
MSG msg;
|
||||||
while (GetMessage(&msg, NULL, 0, 0) > 0)
|
while (GetMessage(&msg, NULL, 0, 0) > 0)
|
||||||
{
|
{
|
||||||
if (!IsDialogMessage(hwnd, &msg))
|
TranslateMessage(&msg);
|
||||||
{
|
DispatchMessage(&msg);
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return msg.wParam;
|
return msg.wParam;
|
||||||
}
|
}
|
||||||
@ -354,5 +237,5 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
|
|||||||
void CreateGUI()
|
void CreateGUI()
|
||||||
{
|
{
|
||||||
ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
|
ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
|
||||||
ExitProcess(__WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL));
|
ExitProcess(WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL));
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
BIN
imgs/lutris_browse.gif
Normal file
After Width: | Height: | Size: 321 KiB |
BIN
imgs/lutris_console.gif
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
imgs/lutris_copy.gif
Normal file
After Width: | Height: | Size: 609 KiB |
BIN
imgs/lutris_install.gif
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
BIN
imgs/wine_install.gif
Normal file
After Width: | Height: | Size: 541 KiB |
BIN
imgs/wine_remove.gif
Normal file
After Width: | Height: | Size: 193 KiB |
111
main.c
@ -3,8 +3,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "resource.h"
|
|
||||||
|
|
||||||
FILE *g_logFile = NULL;
|
FILE *g_logFile = NULL;
|
||||||
BOOL RunningAsService = FALSE;
|
BOOL RunningAsService = FALSE;
|
||||||
|
|
||||||
@ -59,7 +57,7 @@ void DetectWine()
|
|||||||
if (!GetProcAddress(hNTdll, "wine_get_version"))
|
if (!GetProcAddress(hNTdll, "wine_get_version"))
|
||||||
{
|
{
|
||||||
MessageBox(NULL, "This program is only intended to run under Wine.",
|
MessageBox(NULL, "This program is only intended to run under Wine.",
|
||||||
"Error", MB_OK | MB_ICONINFORMATION);
|
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +72,14 @@ void DetectWine()
|
|||||||
{
|
{
|
||||||
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
|
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
|
||||||
NULL, MB_YESNO | MB_ICONQUESTION);
|
NULL, MB_YESNO | MB_ICONQUESTION);
|
||||||
if (result == IDNO)
|
if (result == IDYES)
|
||||||
|
return;
|
||||||
|
else if (result == IDNO)
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
IsLinux = strcmp(__sysname, "Linux") == 0;
|
IsLinux = strcmp(__sysname, "Linux") == 0;
|
||||||
|
printf("Running on %s\n", __sysname);
|
||||||
}
|
}
|
||||||
|
|
||||||
void print(char const *fmt, ...)
|
void print(char const *fmt, ...)
|
||||||
@ -221,26 +222,28 @@ void HandleArguments(int argc, char *argv[])
|
|||||||
HeapFree(GetProcessHeap(), 0, asciiPath);
|
HeapFree(GetProcessHeap(), 0, asciiPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("(Steam) Starting service and then exiting...\n");
|
print("(Steam) Starting service...\n");
|
||||||
fclose(g_logFile);
|
if (StartService(schService, 0, NULL) == FALSE)
|
||||||
|
{
|
||||||
|
if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
|
||||||
|
{
|
||||||
|
print("(Steam) Service is already running\n");
|
||||||
|
CloseServiceHandle(schService);
|
||||||
|
CloseServiceHandle(hSCManager);
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (StartService(schService, 0, NULL) != FALSE)
|
print("StartService: %s\n", GetErrorMessage());
|
||||||
{
|
MessageBox(NULL, GetErrorMessage(),
|
||||||
CloseServiceHandle(schService);
|
"StartService",
|
||||||
CloseServiceHandle(hSCManager);
|
MB_OK | MB_ICONSTOP);
|
||||||
ExitProcess(0);
|
ExitProcess(1);
|
||||||
}
|
|
||||||
else if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
|
|
||||||
{
|
|
||||||
CloseServiceHandle(schService);
|
|
||||||
CloseServiceHandle(hSCManager);
|
|
||||||
ExitProcess(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageBox(NULL, GetErrorMessage(),
|
print("(Steam) Service started successfully, exiting...\n");
|
||||||
"StartService",
|
CloseServiceHandle(schService);
|
||||||
MB_OK | MB_ICONSTOP);
|
CloseServiceHandle(hSCManager);
|
||||||
ExitProcess(1);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
else if (strcmp(argv[1], "--install") == 0)
|
else if (strcmp(argv[1], "--install") == 0)
|
||||||
{
|
{
|
||||||
@ -269,40 +272,32 @@ void HandleArguments(int argc, char *argv[])
|
|||||||
CreateBridge();
|
CreateBridge();
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
else if (strcmp(argv[1], "--version") == 0)
|
|
||||||
{
|
|
||||||
/* Already shows the version */
|
|
||||||
ExitProcess(0);
|
|
||||||
}
|
|
||||||
else if (strcmp(argv[1], "--help") == 0)
|
else if (strcmp(argv[1], "--help") == 0)
|
||||||
{
|
{
|
||||||
print("Usage:\n"
|
printf("Usage:\n");
|
||||||
" %s [args]\n"
|
printf(" %s [args]\n\n", argv[0]);
|
||||||
"\n"
|
|
||||||
"Arguments:\n"
|
printf("Arguments:\n");
|
||||||
" --help Show this help\n"
|
printf(" --help Show this help\n\n");
|
||||||
"\n"
|
|
||||||
" --version Show version\n"
|
printf(" --install Install service\n");
|
||||||
"\n"
|
printf(" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n\n");
|
||||||
" --install Install service\n"
|
|
||||||
" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n"
|
printf(" --uninstall Uninstall service\n");
|
||||||
"\n"
|
printf(" This will remove the service and delete C:\\windows\\bridge.exe\n\n");
|
||||||
" --uninstall Uninstall service\n"
|
|
||||||
" This will remove the service and delete C:\\windows\\bridge.exe\n"
|
printf(" --steam Reserved for Steam\n");
|
||||||
"\n"
|
printf(" This will start the service and exit (used with bridge.sh)\n\n");
|
||||||
" --steam Reserved for Steam\n"
|
|
||||||
" This will start the service and exit (used with bridge.sh)\n"
|
printf(" --no-service Do not run as service\n");
|
||||||
"\n"
|
printf(" (only for --steam)\n\n");
|
||||||
" --no-service Do not run as service\n"
|
|
||||||
" (only for --steam)\n"
|
printf(" --service Reserved for service\n\n");
|
||||||
"\n"
|
|
||||||
" --service Reserved for service\n"
|
printf(" --rpc <dir> Set RPC_PATH environment variable\n");
|
||||||
"\n"
|
printf(" This is used to specify the directory where 'discord-ipc-0' is located\n\n");
|
||||||
" --rpc <dir> Set RPC_PATH environment variable\n"
|
|
||||||
" This is used to specify the directory where 'discord-ipc-0' is located\n"
|
printf("Note: If no arguments are provided, the GUI will be shown instead\n");
|
||||||
"\n"
|
|
||||||
"Note: If no arguments are provided, the GUI will be shown instead\n",
|
|
||||||
argv[0]);
|
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,12 +309,11 @@ int main(int argc, char *argv[])
|
|||||||
g_logFile = fopen(logFilePath, "w");
|
g_logFile = fopen(logFilePath, "w");
|
||||||
if (g_logFile == NULL)
|
if (g_logFile == NULL)
|
||||||
{
|
{
|
||||||
MessageBox(NULL, "Failed to open logs file", "Error", MB_OK | MB_ICONERROR);
|
printf("Failed to open logs file: %ld\n",
|
||||||
printf("Failed to open logs file: %ld\n", GetLastError());
|
GetLastError());
|
||||||
ExitProcess(1);
|
ExitProcess(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("rpc-bridge v%s %s-%s\n", VER_VERSION_STR, GIT_BRANCH, GIT_COMMIT);
|
|
||||||
if (argc > 1)
|
if (argc > 1)
|
||||||
HandleArguments(argc, argv);
|
HandleArguments(argc, argv);
|
||||||
else
|
else
|
||||||
@ -333,8 +327,3 @@ int main(int argc, char *argv[])
|
|||||||
fclose(g_logFile);
|
fclose(g_logFile);
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
||||||
{
|
|
||||||
return main(__argc, __argv);
|
|
||||||
}
|
|
||||||
|
82
mkdocs.yml
@ -1,82 +0,0 @@
|
|||||||
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
|
|
@ -1,9 +0,0 @@
|
|||||||
#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"
|
|
14
service.c
@ -88,13 +88,13 @@ void InstallService(int ServiceStartType, LPCSTR Path)
|
|||||||
{
|
{
|
||||||
print("Registering service\n");
|
print("Registering service\n");
|
||||||
|
|
||||||
// if (IsLinux == FALSE)
|
if (IsLinux == FALSE)
|
||||||
// {
|
{
|
||||||
// /* FIXME: I don't know how to get the TMPDIR without getenv */
|
/* 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.",
|
MessageBox(NULL, "Registering as a service is not supported on macOS at the moment.",
|
||||||
// "Unsupported", MB_OK | MB_ICONINFORMATION);
|
"Unsupported", MB_OK | MB_ICONINFORMATION);
|
||||||
// ExitProcess(1);
|
ExitProcess(1);
|
||||||
// }
|
}
|
||||||
|
|
||||||
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||||
if (schSCManager == NULL)
|
if (schSCManager == NULL)
|
||||||
|