Initial commit

This commit is contained in:
EnderIce2 2023-10-13 03:54:34 +03:00
commit fa88e36aee
Signed by untrusted user who does not match committer: enderice2
GPG Key ID: EACC3AD603BAB4DD
22 changed files with 1082 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
*.exe

15
.vscode/c_cpp_properties.json vendored Normal file
View 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
View 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
View 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
View 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
View 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`
- ![wine install](imgs/wine_install.gif)
- To remove, run `C:\> remove.bat`
- ![wine remove](imgs/wine_remove.gif)
- Note: Copying files are not required here.
##### Lutris
- Right click on the game and select `Browse files`
- ![lutris browse](imgs/lutris_browse.gif)
- Copy contents of `build` to the game's prefix `drive_c`
- ![lutris copy](imgs/lutris_copy.gif)
- To install open the console
- ![lutris console](imgs/lutris_console.gif)
- And run `C:\> install.bat` (make sure you are in `C:\`!)
- ![lutris install](imgs/lutris_install.gif)
##### Steam
- Open [Protontricks](https://github.com/Matoking/protontricks) and select the game you want to install the bridge to
- Select `Select the default wineprefix`
- Select `Browse files` and copy contents of `build` to the game's prefix `drive_c`
- Select `Run a Wine cmd shell` and run `C:\> install.bat`
- If you are not in `C:\`, type `c:` and press enter
- ![protontricks](imgs/steam_protontricks.png)
#### 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
- ![flatseal](imgs/flatseal_permission.png)
- ##### By using the terminal
- Per application
- `flatpak override --filesystem=xdg-run/discord-ipc-0 <flatpak app name>`
- Globally
- `flatpak override --user --filesystem=xdg-run/discord-ipc-0`
## Run without installing the service
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
![League of Legends running under Wine](imgs/lutris_lol.png)
![Among Us running from Steam](imgs/steam_amongus.png)
## Credits
This project is inspired by [wine-discord-ipc-bridge](https://github.com/0e4ef622/wine-discord-ipc-bridge).
---

438
bridge.c Normal file
View 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
View File

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

3
build/remove.bat Normal file
View File

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

145
game.c Normal file
View 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");
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
imgs/lutris_browse.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
imgs/lutris_console.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

BIN
imgs/lutris_copy.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 KiB

BIN
imgs/lutris_install.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
imgs/lutris_lol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
imgs/steam_amongus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

BIN
imgs/steam_protontricks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
imgs/wine_install.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

BIN
imgs/wine_remove.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

114
main.c Normal file
View 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
View 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);
}