Initial commit
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
*.exe
|
15
.vscode/c_cpp_properties.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Windows",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"defines": [],
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "gnu++17",
|
||||
"intelliSenseMode": "windows-gcc-x86"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"C_Cpp.default.compilerPath": "/usr/bin/i686-w64-mingw32-gcc",
|
||||
"files.associations": {
|
||||
"*.su": "tsv",
|
||||
"windows.h": "c",
|
||||
"namedpipeapi.h": "c"
|
||||
}
|
||||
}
|
19
LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2023 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
24
Makefile
Normal file
@ -0,0 +1,24 @@
|
||||
C_SOURCES = $(shell find ./ -type f -name '*.c')
|
||||
C_OBJECTS = $(C_SOURCES:.c=.o)
|
||||
|
||||
CFLAGS = -std=c17 -Wno-int-conversion
|
||||
LFLAGS =
|
||||
|
||||
# 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
|
||||
|
||||
%.o: %.c
|
||||
$(info Compiling $<)
|
||||
i686-w64-mingw32-gcc $(CFLAGS) $(DBGFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(C_OBJECTS) build/bridge.exe
|
123
README.md
Normal file
@ -0,0 +1,123 @@
|
||||
# Discord RPC Bridge for Wine
|
||||
|
||||
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`.
|
||||
|
||||
This bridge takes advantage of the Windows service implementation in Wine, eliminating the need to manually run any programs.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
#### Installing inside a prefix
|
||||
|
||||
##### Wine (~/.wine)
|
||||
|
||||
- Open terminal in `build`
|
||||
- Run `$ wine cmd` and `C:\> install.bat`
|
||||
- 
|
||||
- To remove, run `C:\> remove.bat`
|
||||
- 
|
||||
- Note: Copying files are not required here.
|
||||
|
||||
##### Lutris
|
||||
|
||||
- Right click on the game and select `Browse files`
|
||||
- 
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- 
|
||||
|
||||
#### 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.
|
||||
- ##### By using [Flatseal](https://flathub.org/apps/details/com.github.tchx84.Flatseal)
|
||||
- Add `xdg-run/discord-ipc-0` under `Filesystems` category
|
||||
- 
|
||||
- ##### 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
|
||||
|
||||
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.
|
||||
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`
|
||||
|
||||
In Wine, all you need to do is run `bridge.exe`.
|
||||
|
||||
- When running the program manually without providing any arguments, it will simply initiate the bridge and wait indefinitely until it's closed.
|
||||
|
||||
## Compiling from source
|
||||
|
||||
- Install the `wine`, `i686-w64-mingw32-gcc` 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
|
||||
|
||||

|
||||

|
||||
|
||||
## Credits
|
||||
|
||||
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
|
||||
|
||||
---
|
438
bridge.c
Normal file
@ -0,0 +1,438 @@
|
||||
#include <namedpipeapi.h>
|
||||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
#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 O_RDONLY 00
|
||||
|
||||
#define likely(expr) (__builtin_expect(!!(expr), 1))
|
||||
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
|
||||
|
||||
#define force_inline \
|
||||
__inline__ \
|
||||
__attribute__((__always_inline__, __gnu_inline__))
|
||||
|
||||
typedef unsigned short sa_family_t;
|
||||
struct sockaddr_un
|
||||
{
|
||||
sa_family_t sun_family; /* AF_UNIX */
|
||||
char sun_path[108]; /* Pathname */
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int fd;
|
||||
HANDLE hPipe;
|
||||
} bridge_thread;
|
||||
|
||||
void print(char const *fmt, ...);
|
||||
LPTSTR GetErrorMessage();
|
||||
extern BOOL RunningAsService;
|
||||
BOOL RetryNewConnection;
|
||||
|
||||
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)
|
||||
{
|
||||
int ret;
|
||||
__asm__ __volatile__(
|
||||
"int $0x80\n"
|
||||
: "=a"(ret)
|
||||
: "0"(num), "b"(arg1), "c"(arg2),
|
||||
"d"(arg3), "S"(arg4), "D"(arg5)
|
||||
: "memory");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int sys_read(int fd, void *buf, size_t count)
|
||||
{
|
||||
return syscall(__NR_read, fd, (intptr_t)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);
|
||||
}
|
||||
|
||||
static inline int sys_open(const char *pathname, int flags, int mode)
|
||||
{
|
||||
return syscall(__NR_open, (intptr_t)pathname, flags, mode, 0, 0, 0);
|
||||
}
|
||||
|
||||
static inline int sys_close(int fd)
|
||||
{
|
||||
return syscall(__NR_close, fd, 0, 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);
|
||||
}
|
||||
|
||||
char *linux_getenv(const char *name)
|
||||
{
|
||||
int fd = sys_open("/proc/self/environ", O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
{
|
||||
print("Failed to open /proc/self/environ: %d\n", fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char buffer[4096];
|
||||
char *result = NULL;
|
||||
int bytesRead;
|
||||
|
||||
while ((bytesRead = sys_read(fd, buffer, sizeof(buffer) - 1)) > 0)
|
||||
{
|
||||
buffer[bytesRead] = '\0';
|
||||
char *env = buffer;
|
||||
while (*env)
|
||||
{
|
||||
if (strstr(env, name) == env)
|
||||
{
|
||||
env += strlen(name);
|
||||
if (*env == '=')
|
||||
{
|
||||
env++;
|
||||
result = strdup(env);
|
||||
break;
|
||||
}
|
||||
}
|
||||
env += strlen(env) + 1;
|
||||
}
|
||||
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
|
||||
sys_close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ConnectToSocket(int fd)
|
||||
{
|
||||
print("Connecting to socket\n");
|
||||
const char *runtime = linux_getenv("XDG_RUNTIME_DIR");
|
||||
if (runtime == NULL)
|
||||
{
|
||||
print("XDG_RUNTIME_DIR not set\n");
|
||||
if (!RunningAsService)
|
||||
{
|
||||
MessageBox(NULL,
|
||||
"XDG_RUNTIME_DIR not set",
|
||||
"Environment variable not set",
|
||||
MB_OK | MB_ICONSTOP);
|
||||
}
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
print("XDG_RUNTIME_DIR: %s\n", runtime);
|
||||
|
||||
const char *discordUnixPipes[] = {
|
||||
"/discord-ipc-0",
|
||||
"/snap.discord/discord-ipc-0",
|
||||
"/app/com.discordapp.Discord/discord-ipc-0",
|
||||
};
|
||||
|
||||
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]);
|
||||
strcpy_s(socketAddr.sun_path, sizeof(socketAddr.sun_path), pipePath);
|
||||
|
||||
print("Connecting to %s\n", pipePath);
|
||||
|
||||
unsigned long socketArgs[] = {
|
||||
(unsigned long)fd,
|
||||
(unsigned long)&socketAddr,
|
||||
sizeof(socketAddr)};
|
||||
|
||||
sockRet = sys_socketcall(3, socketArgs);
|
||||
|
||||
free(pipePath);
|
||||
if (sockRet >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (sockRet < 0)
|
||||
{
|
||||
print("socketcall failed for: %d\n", sockRet);
|
||||
if (!RunningAsService)
|
||||
{
|
||||
MessageBox(NULL,
|
||||
"Failed to connect to Discord",
|
||||
"Socket Connection failed",
|
||||
MB_OK | MB_ICONSTOP);
|
||||
}
|
||||
ExitProcess(1);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
while (TRUE)
|
||||
{
|
||||
char buffer[1024];
|
||||
int read = sys_read(bt->fd, buffer, sizeof(buffer));
|
||||
|
||||
if (unlikely(read < 0))
|
||||
{
|
||||
print("Failed to read from unix pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unlikely(read == 0))
|
||||
{
|
||||
print("EOF\n");
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
print("Reading %d bytes from unix pipe: \"", read);
|
||||
for (int i = 0; i < read; i++)
|
||||
print("%c", buffer[i]);
|
||||
print("\"\n");
|
||||
|
||||
DWORD dwWritten;
|
||||
if (unlikely(!WriteFile(bt->hPipe, buffer, read,
|
||||
&dwWritten, NULL)))
|
||||
{
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
RetryNewConnection = TRUE;
|
||||
print("In Broken pipe\n");
|
||||
break;
|
||||
}
|
||||
|
||||
print("Failed to read from pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unlikely(dwWritten < 0))
|
||||
{
|
||||
print("Failed to write to pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (dwWritten < read)
|
||||
{
|
||||
int last_written = dwWritten;
|
||||
if (unlikely(!WriteFile(bt->hPipe, buffer + dwWritten,
|
||||
read - dwWritten, &dwWritten, NULL)))
|
||||
{
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
RetryNewConnection = TRUE;
|
||||
print("In Broken pipe\n");
|
||||
break;
|
||||
}
|
||||
|
||||
print("Failed to read from pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unlikely(last_written == dwWritten))
|
||||
{
|
||||
print("Failed to write to pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
while (TRUE)
|
||||
{
|
||||
char buffer[1024];
|
||||
DWORD dwRead;
|
||||
|
||||
if (unlikely(!ReadFile(bt->hPipe, buffer, sizeof(buffer),
|
||||
&dwRead, NULL)))
|
||||
{
|
||||
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||
{
|
||||
RetryNewConnection = TRUE;
|
||||
print("Out Broken pipe\n");
|
||||
break;
|
||||
}
|
||||
|
||||
print("Failed to read from pipe: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
print("Writing %d bytes to unix pipe: \"", dwRead);
|
||||
for (int i = 0; i < dwRead; i++)
|
||||
print("%c", buffer[i]);
|
||||
print("\"\n");
|
||||
|
||||
int written = sys_write(bt->fd, buffer, dwRead);
|
||||
if (unlikely(written < 0))
|
||||
{
|
||||
print("Failed to write to socket: %d\n",
|
||||
written);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (written < dwRead)
|
||||
{
|
||||
int last_written = written;
|
||||
written += sys_write(bt->fd, buffer + written, dwRead - written);
|
||||
if (unlikely(last_written == written))
|
||||
{
|
||||
print("Failed to write to socket: %d\n",
|
||||
GetErrorMessage());
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CreateBridge()
|
||||
{
|
||||
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
|
||||
|
||||
NewConnection:
|
||||
if (GetNamedPipeInfo((HANDLE)lpszPipename,
|
||||
NULL, NULL,
|
||||
NULL, NULL))
|
||||
{
|
||||
if (!RunningAsService)
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
print("Pipe already exists: %s\n",
|
||||
GetErrorMessage());
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
HANDLE hPipe =
|
||||
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
1, 1024, 1024, 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());
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
print("Pipe %s(%#x) created\n", lpszPipename, hPipe);
|
||||
print("Waiting for pipe connection\n");
|
||||
if (!ConnectNamedPipe(hPipe, NULL))
|
||||
{
|
||||
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 = sys_socketcall(1, socketArgs);
|
||||
|
||||
print("Socket %d created\n", fd);
|
||||
|
||||
if (fd < 0)
|
||||
{
|
||||
if (!RunningAsService)
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
print("Failed to create socket: %d\n", fd);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
ConnectToSocket(fd);
|
||||
print("Connected to Discord\n");
|
||||
|
||||
bridge_thread bt = {fd, hPipe};
|
||||
|
||||
HANDLE hIn = CreateThread(NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
|
||||
(LPVOID)&bt,
|
||||
0, NULL);
|
||||
|
||||
HANDLE hOut = CreateThread(NULL, 0,
|
||||
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
|
||||
(LPVOID)&bt,
|
||||
0, NULL);
|
||||
|
||||
if (hIn == NULL || hOut == NULL)
|
||||
{
|
||||
if (!RunningAsService)
|
||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
||||
print("Failed to create threads: %s\n", GetErrorMessage());
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
print("Waiting for threads to exit\n");
|
||||
WaitForSingleObject(hOut, INFINITE);
|
||||
print("Buffer out thread exited\n");
|
||||
|
||||
if (RetryNewConnection)
|
||||
{
|
||||
RetryNewConnection = FALSE;
|
||||
print("Retrying new connection\n");
|
||||
if (!TerminateThread(hIn, 0))
|
||||
print("Failed to terminate thread: %s\n",
|
||||
GetErrorMessage());
|
||||
|
||||
if (!TerminateThread(hOut, 0))
|
||||
print("Failed to terminate thread: %s\n",
|
||||
GetErrorMessage());
|
||||
|
||||
sys_close(fd);
|
||||
CloseHandle(hOut);
|
||||
CloseHandle(hIn);
|
||||
CloseHandle(hPipe);
|
||||
Sleep(1000);
|
||||
goto NewConnection;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hIn, INFINITE);
|
||||
print("Buffer in thread exited\n");
|
||||
CloseHandle(hPipe);
|
||||
}
|
3
build/install.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
START /WAIT bridge.exe --install
|
||||
|
3
build/remove.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
START /WAIT bridge.exe --uninstall
|
||||
|
145
game.c
Normal file
@ -0,0 +1,145 @@
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <winbase.h>
|
||||
#include <winuser.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void print(char const *fmt, ...);
|
||||
LPTSTR GetErrorMessage();
|
||||
|
||||
BOOL IsChildProcess(DWORD parentID, DWORD childID)
|
||||
{
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
PROCESSENTRY32 pe32;
|
||||
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE)
|
||||
return FALSE;
|
||||
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(hSnapshot, &pe32))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (pe32.th32ParentProcessID == parentID && pe32.th32ProcessID == childID)
|
||||
{
|
||||
CloseHandle(hSnapshot);
|
||||
return TRUE;
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL FileExists(LPCTSTR szPath)
|
||||
{
|
||||
DWORD dwAttrib = GetFileAttributes(szPath);
|
||||
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
|
||||
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
|
||||
}
|
||||
|
||||
void LaunchGame(int argc, char **argv)
|
||||
{
|
||||
char *gamePath = "";
|
||||
int startArg = 1;
|
||||
if (argc > 1)
|
||||
{
|
||||
print("Checking if %s is a valid file\n", argv[1]);
|
||||
if (FileExists(argv[1]))
|
||||
{
|
||||
print("Checking if %s is a valid executable\n", argv[1]);
|
||||
DWORD dwBinaryType;
|
||||
if (!GetBinaryType(argv[1], &dwBinaryType))
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
print("Executable type: %d\n", dwBinaryType);
|
||||
|
||||
gamePath = argv[1];
|
||||
startArg = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox(NULL, "Invalid game path specified",
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
print("%s is not a valid file\n", argv[1]);
|
||||
ExitProcess(1);
|
||||
}
|
||||
}
|
||||
else if (argc == 1)
|
||||
{
|
||||
print("No game path specified. Idling...\n");
|
||||
while (TRUE)
|
||||
Sleep(1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox(NULL, "No game path specified",
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
STARTUPINFO game_si;
|
||||
PROCESS_INFORMATION game_pi;
|
||||
|
||||
ZeroMemory(&game_si, sizeof(STARTUPINFO));
|
||||
game_si.cb = sizeof(STARTUPINFO);
|
||||
game_si.hStdOutput = INVALID_HANDLE_VALUE;
|
||||
game_si.hStdError = INVALID_HANDLE_VALUE;
|
||||
game_si.dwFlags |= STARTF_USESTDHANDLES;
|
||||
|
||||
char *gameArgs = LocalAlloc(LPTR, 512);
|
||||
for (int i = startArg; i < argc; i++)
|
||||
{
|
||||
assert(strlen(gameArgs) + strlen(argv[i]) < 512);
|
||||
gameArgs = strcat(gameArgs, argv[i]);
|
||||
gameArgs = strcat(gameArgs, " ");
|
||||
}
|
||||
print("Launching \"%s\" with arguments \"%s\"\n", gamePath, gameArgs);
|
||||
if (!CreateProcess(gamePath, gameArgs, NULL, NULL, FALSE,
|
||||
0, NULL, NULL, &game_si, &game_pi))
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
LocalFree(gameArgs);
|
||||
DWORD parentID = game_pi.dwProcessId;
|
||||
print("Waiting for PID %d to exit...\n", game_pi.dwProcessId);
|
||||
WaitForSingleObject(game_pi.hProcess, INFINITE);
|
||||
print("PID %d exited\n", game_pi.dwProcessId);
|
||||
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
PROCESSENTRY32 pe32;
|
||||
|
||||
if (hSnapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
if (Process32First(hSnapshot, &pe32))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (IsChildProcess(parentID, pe32.th32ProcessID))
|
||||
{
|
||||
WaitForSingleObject(OpenProcess(SYNCHRONIZE, FALSE,
|
||||
pe32.th32ProcessID),
|
||||
INFINITE);
|
||||
print("Waiting for PID %d\n", pe32.th32ProcessID);
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe32));
|
||||
}
|
||||
CloseHandle(hSnapshot);
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(), NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(0);
|
||||
}
|
||||
|
||||
CloseHandle(game_pi.hProcess);
|
||||
print("Game exited\n");
|
||||
}
|
BIN
imgs/flatseal_permission.png
Normal file
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 |
BIN
imgs/lutris_lol.png
Normal file
After Width: | Height: | Size: 160 KiB |
BIN
imgs/steam_amongus.png
Normal file
After Width: | Height: | Size: 203 KiB |
BIN
imgs/steam_protontricks.png
Normal file
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 |
114
main.c
Normal file
@ -0,0 +1,114 @@
|
||||
#include <windows.h>
|
||||
#include <winuser.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
FILE *g_logFile = NULL;
|
||||
BOOL RunningAsService = FALSE;
|
||||
|
||||
void CreateBridge();
|
||||
void LaunchGame(int argc, char **argv);
|
||||
void ServiceMain(int argc, char *argv[]);
|
||||
void InstallService();
|
||||
void RemoveService();
|
||||
|
||||
LPTSTR GetErrorMessage()
|
||||
{
|
||||
DWORD err = GetLastError();
|
||||
if (err == 0)
|
||||
return "Error";
|
||||
|
||||
WORD wLangID = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
|
||||
|
||||
LPSTR buffer = NULL;
|
||||
size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, err, wLangID, (LPSTR)&buffer, 0, NULL);
|
||||
|
||||
LPTSTR message = NULL;
|
||||
if (size > 0)
|
||||
{
|
||||
message = (LPTSTR)LocalAlloc(LPTR, (size + 1) * sizeof(TCHAR));
|
||||
if (message != NULL)
|
||||
{
|
||||
memcpy(message, buffer, size * sizeof(TCHAR));
|
||||
message[size] = '\0';
|
||||
}
|
||||
LocalFree(buffer);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
void DetectWine()
|
||||
{
|
||||
HMODULE hNTdll = GetModuleHandle("ntdll.dll");
|
||||
if (!hNTdll)
|
||||
ExitProcess(1);
|
||||
if (!GetProcAddress(hNTdll, "wine_get_version"))
|
||||
{
|
||||
MessageBox(NULL, "This program is only intended to run under Wine.",
|
||||
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
|
||||
ExitProcess(1);
|
||||
}
|
||||
}
|
||||
|
||||
void print(char const *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vprintf(fmt, args);
|
||||
va_end(args);
|
||||
va_start(args, fmt);
|
||||
vfprintf(g_logFile, fmt, args);
|
||||
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)
|
||||
{
|
||||
if (strcmp(argv[1], "--service") == 0)
|
||||
{
|
||||
RunningAsService = TRUE;
|
||||
print("Running as service\n");
|
||||
|
||||
SERVICE_TABLE_ENTRY ServiceTable[] =
|
||||
{
|
||||
{"rpc-bridge", (LPSERVICE_MAIN_FUNCTION)ServiceMain},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE)
|
||||
{
|
||||
print("Service failed to start\n");
|
||||
return GetLastError();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "--install") == 0)
|
||||
InstallService();
|
||||
else if (strcmp(argv[1], "--uninstall") == 0)
|
||||
RemoveService();
|
||||
}
|
||||
|
||||
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
|
||||
NULL, 0, NULL);
|
||||
Sleep(500);
|
||||
LaunchGame(argc, argv);
|
||||
|
||||
fclose(g_logFile);
|
||||
ExitProcess(0);
|
||||
}
|
188
service.c
Normal file
@ -0,0 +1,188 @@
|
||||
#include <windows.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
SERVICE_STATUS g_ServiceStatus;
|
||||
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
||||
|
||||
void print(char const *fmt, ...);
|
||||
void CreateBridge();
|
||||
LPTSTR GetErrorMessage();
|
||||
|
||||
void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
|
||||
{
|
||||
switch (CtrlCode)
|
||||
{
|
||||
case SERVICE_CONTROL_STOP:
|
||||
case SERVICE_ACCEPT_SHUTDOWN:
|
||||
{
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
print("Stopping service\n");
|
||||
|
||||
/* ... */
|
||||
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
print("Unrecognized service control code %d\n", CtrlCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
|
||||
{
|
||||
print("Service started\n");
|
||||
CreateBridge();
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
void ServiceMain(DWORD argc, LPTSTR *argv)
|
||||
{
|
||||
print("Starting service\n");
|
||||
g_StatusHandle = RegisterServiceCtrlHandler("rpc-bridge",
|
||||
ServiceCtrlHandler);
|
||||
if (g_StatusHandle == NULL)
|
||||
{
|
||||
print("Failed to register service control handler\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
|
||||
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
||||
SERVICE_ACCEPT_SHUTDOWN;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread,
|
||||
NULL, 0, NULL);
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 3;
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
print("Service stopped.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
void InstallService()
|
||||
{
|
||||
print("Registering to run on startup\n");
|
||||
|
||||
SC_HANDLE schSCManager, schService;
|
||||
DWORD dwTagId;
|
||||
|
||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
|
||||
if (schSCManager == NULL)
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (schService == NULL)
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, 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()
|
||||
{
|
||||
print("Unregistering from startup\n");
|
||||
|
||||
SC_HANDLE schSCManager, schService;
|
||||
SERVICE_STATUS ssSvcStatus;
|
||||
|
||||
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (schSCManager == NULL)
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
schService = OpenService(schSCManager, "rpc-bridge",
|
||||
SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);
|
||||
|
||||
if (schService == NULL)
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
if (ControlService(schService, SERVICE_CONTROL_STOP, &ssSvcStatus))
|
||||
{
|
||||
print("Stopping service\n");
|
||||
Sleep(1000);
|
||||
|
||||
while (QueryServiceStatus(schService, &ssSvcStatus))
|
||||
{
|
||||
if (ssSvcStatus.dwCurrentState == SERVICE_STOP_PENDING)
|
||||
{
|
||||
print("Waiting for service to stop\n");
|
||||
Sleep(1000);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if (ssSvcStatus.dwCurrentState == SERVICE_STOPPED)
|
||||
print("Service stopped\n");
|
||||
else
|
||||
print("Service failed to stop\n");
|
||||
}
|
||||
|
||||
if (!DeleteService(schService))
|
||||
{
|
||||
MessageBox(NULL, GetErrorMessage(),
|
||||
NULL, MB_OK | MB_ICONSTOP);
|
||||
ExitProcess(1);
|
||||
}
|
||||
|
||||
DeleteFile("C:\\bridge.exe");
|
||||
print("Service removed successfully\n");
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
ExitProcess(0);
|
||||
}
|