refactor: move sources to src/

This commit is contained in:
2025-07-09 20:49:04 +03:00
parent c1078b5b8b
commit bb3a214f21
14 changed files with 38 additions and 31 deletions

26
src/Makefile Normal file
View File

@ -0,0 +1,26 @@
C_SOURCES = $(shell find ./ -type f -name '*.c')
C_OBJECTS = $(C_SOURCES:.c=.o)
GIT_COMMIT = $(shell git rev-parse --short HEAD)
GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD)
CWARNFLAGS = -Wno-int-conversion -Wno-incompatible-pointer-types
CFLAGS = -std=c17 -DGIT_COMMIT='"$(GIT_COMMIT)"' -DGIT_BRANCH='"$(GIT_BRANCH)"'
LFLAGS = -lgdi32 -lws2_32
# DBGFLAGS = -Wl,--export-all-symbols -g -O0 -ggdb3 -Wall
all: build
build: $(C_OBJECTS)
$(info Linking)
x86_64-w64-mingw32-windres bridge.rc -O coff -o bridge.res
x86_64-w64-mingw32-gcc $(C_OBJECTS) bridge.res $(LFLAGS) $(DBGFLAGS) -o ../build/bridge.exe
%.o: %.c
$(info Compiling $<)
x86_64-w64-mingw32-gcc $(CFLAGS) $(CWARNFLAGS) $(DBGFLAGS) -c $< -o $@
clean:
rm -f $(C_OBJECTS) ../build/bridge.exe bridge.res

670
src/bridge.c Normal file
View File

@ -0,0 +1,670 @@
#include <namedpipeapi.h>
#include <windows.h>
#include <winuser.h>
#include <assert.h>
#include <stdio.h>
#define __linux_read 3
#define __linux_write 4
#define __linux_open 5
#define __linux_close 6
#define __linux_munmap 91
#define __linux_socketcall 102
#define __linux_mmap2 192
#define __linux_socket 359
#define __linux_connect 362
#define __darwin_read 0x2000003
#define __darwin_write 0x2000004
#define __darwin_open 0x2000005
#define __darwin_close 0x2000006
#define __darwin_socket 0x2000061
#define __darwin_connect 0x2000062
#define __darwin_mmap 0x20000C5
#define __darwin_fcntl 0x200005C
#define __darwin_sysctl 0x20000CA
#define O_RDONLY 00
/* macos & linux are the same for PROT_READ, PROT_WRITE, MAP_FIXED & MAP_PRIVATE */
#define PROT_READ 1
#define PROT_WRITE 2
#define MAP_PRIVATE 0x02
#define MAP_FIXED 0x10
#define MAP_ANON 0x20
#define MAP_FAILED ((void *)-1)
#define __darwin_MAP_ANON 0x1000
#define SYS_SOCKET 1
#define SYS_CONNECT 3
#define likely(expr) (__builtin_expect(!!(expr), 1))
#define unlikely(expr) (__builtin_expect(!!(expr), 0))
#define force_inline \
__inline__ \
__attribute__((__always_inline__, __gnu_inline__))
#define naked __attribute__((naked))
#define BUFFER_LENGTH 2048
typedef unsigned short sa_family_t;
typedef char *caddr_t;
typedef unsigned socklen_t;
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
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;
BOOL IsLinux;
HANDLE hOut = NULL;
HANDLE hIn = NULL;
static force_inline int linux_syscall(int num,
int arg1, int arg2, int arg3,
int arg4, int arg5, int 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 naked int darwin_syscall(int num,
long arg1, long arg2, long arg3,
long arg4, long arg5, long arg6)
{
register long r10 __asm__("r10") = arg4;
register long r8 __asm__("r8") = arg5;
register long r9 __asm__("r9") = arg6;
__asm__ __volatile__(
"syscall\n"
"jae noerror\n"
"negq %%rax\n"
"noerror:\n"
"ret\n"
: "=a"(num)
: "a"(num), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), "r"(r9)
: "memory");
}
static inline int sys_read(int fd, void *buf, size_t count)
{
if (IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0);
}
static inline int sys_write(int fd, const void *buf, size_t count)
{
if (IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else
return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0);
}
static inline int sys_open(const char *pathname, int flags, int mode)
{
if (IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else
return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0);
}
static inline int sys_close(int fd)
{
if (IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else
return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0);
}
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
if (IsLinux)
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
else
{
if (flags & MAP_ANON)
{
flags &= ~MAP_ANON;
flags |= __darwin_MAP_ANON;
}
return darwin_syscall(__darwin_mmap, addr, length, prot, flags, fd, offset);
}
}
static inline int sys_munmap(unsigned int *addr, size_t length)
{
assert(IsLinux);
return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0);
}
static inline int sys_socketcall(int call, unsigned long *args)
{
assert(IsLinux);
return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0);
}
static inline int sys_socket(int domain, int type, int protocol)
{
if (IsLinux)
return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0);
else
return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0);
}
static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
{
if (IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
}
void *environStr = NULL;
char *native_getenv(const char *name)
{
static char lpBuffer[512];
DWORD ret = GetEnvironmentVariable("BRIDGE_RPC_PATH", lpBuffer, sizeof(lpBuffer));
if (ret != 0)
return lpBuffer;
if (!IsLinux)
{
char *value = getenv(name);
if (value == NULL)
{
print("Failed to get environment variable: %s\n", name);
/* Use GetEnvironmentVariable as a last resort */
DWORD ret = GetEnvironmentVariable(name, lpBuffer, sizeof(lpBuffer));
if (ret == 0)
{
print("GetEnvironmentVariable(\"%s\", ...) failed: %d\n", name, ret);
return NULL;
}
return lpBuffer;
}
return value;
}
/* I hope the 0x20000 is okay */
if (environStr == NULL)
environStr = sys_mmap(0x20000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
if ((uintptr_t)environStr > 0x7effffff)
print("Warning: environStr %#lx is above 2GB\n", environStr);
const char *linux_environ = "/proc/self/environ";
memcpy(environStr, linux_environ, strlen(linux_environ) + 1);
int fd = sys_open(environStr, O_RDONLY, 0);
if (fd < 0)
{
print("Failed to open /proc/self/environ: %d\n", fd);
return NULL;
}
char *buffer = sys_mmap(0x22000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
char *result = NULL;
int bytesRead;
while ((bytesRead = sys_read(fd, buffer, 0x1000 - 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;
if (IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR");
else
{
runtime = native_getenv("TMPDIR");
if (runtime == NULL)
{
runtime = "/tmp/rpc-bridge/tmpdir";
print("IPC directory not set, fallback to /tmp/rpc-bridge/tmpdir\n");
// Check if the directory exists
DWORD dwAttrib = GetFileAttributes(runtime);
if (dwAttrib == INVALID_FILE_ATTRIBUTES || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))
{
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);
/* TODO: check for multiple discord instances and create a pipe for each */
const char *discordUnixSockets[] = {
"%s/discord-ipc-%d",
"%s/app/com.discordapp.Discord/discord-ipc-%d",
"%s/.flatpak/dev.vencord.Vesktop/xdg-run/discord-ipc-%d",
"%s/snap.discord/discord-ipc-%d",
"%s/snap.discord-canary/discord-ipc-%d",
};
int sockRet = 0;
for (int i = 0; i < sizeof(discordUnixSockets) / sizeof(discordUnixSockets[0]); i++)
{
size_t pipePathLen = strlen(runtime) + strlen(discordUnixSockets[i]) + 1;
char *pipePath = malloc(pipePathLen);
for (int j = 0; j < 16; j++)
{
if (IsLinux)
{
struct sockaddr_un *socketAddr = sys_mmap(0x23000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
print("Socket address allocated at %#lx\n", socketAddr);
socketAddr->sun_family = AF_UNIX;
snprintf(pipePath, pipePathLen, discordUnixSockets[i], runtime, j);
strcpy_s(socketAddr->sun_path, sizeof(socketAddr->sun_path), pipePath);
print("Probing %s\n", pipePath);
// unsigned long socketArgs[] = {
// (unsigned long)fd,
// (unsigned long)(intptr_t)&socketAddr,
// sizeof(socketAddr)};
unsigned long *socketArgs = sys_mmap(0x24000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
socketArgs[0] = (unsigned long)fd;
socketArgs[1] = (unsigned long)(intptr_t)socketAddr;
socketArgs[2] = sizeof(struct sockaddr_un);
socketArgs[3] = 0;
sockRet = sys_socketcall(SYS_CONNECT, socketArgs);
}
else
{
struct sockaddr_un socketAddr;
socketAddr.sun_family = AF_UNIX;
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;
}
if (sockRet >= 0)
{
print("Connecting to %s\n", pipePath);
free(pipePath);
break;
}
free(pipePath);
}
if (sockRet < 0)
{
print("socketcall failed for: %d\n", sockRet);
if (!RunningAsService)
MessageBox(NULL, "Failed to connect to Discord",
"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);
int EOFCount = 0;
char *l_buffer;
if (IsLinux)
l_buffer = sys_mmap(0x25000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
else
l_buffer = malloc(BUFFER_LENGTH);
print("Buffer in thread allocated at %#lx\n", l_buffer);
while (TRUE)
{
char buffer[BUFFER_LENGTH];
int read = sys_read(bt->fd, l_buffer, BUFFER_LENGTH);
if (unlikely(read < 0))
{
print("Failed to read from unix pipe: %d\n", read);
Sleep(1000);
continue;
}
if (EOFCount > 4)
{
print("EOF count exceeded\n");
RetryNewConnection = TRUE;
TerminateThread(hOut, 0);
break;
}
if (unlikely(read == 0))
{
print("EOF\n");
Sleep(1000);
EOFCount++;
continue;
}
EOFCount = 0;
memcpy(buffer, l_buffer, read);
print("Reading %d bytes from unix pipe: \"", read);
for (int i = 0; i < read; i++)
print("%c", buffer[i]);
print("\"\n");
DWORD dwWritten;
WINBOOL bResult = WriteFile(bt->hPipe, buffer, read, &dwWritten, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (unlikely(dwWritten < 0))
{
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
while (dwWritten < read)
{
int last_written = dwWritten;
WINBOOL bResult = WriteFile(bt->hPipe, buffer + dwWritten, read - dwWritten, &dwWritten, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (unlikely(last_written == dwWritten))
{
print("Failed to write to pipe: %s\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);
char *l_buffer;
if (IsLinux)
l_buffer = sys_mmap(0x26000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
else
l_buffer = malloc(BUFFER_LENGTH);
print("Buffer out thread allocated at %#lx\n", l_buffer);
while (TRUE)
{
char buffer[BUFFER_LENGTH];
DWORD dwRead;
WINBOOL bResult = ReadFile(bt->hPipe, buffer, BUFFER_LENGTH, &dwRead, NULL);
if (unlikely(bResult == FALSE))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("Out Broken pipe\n");
break;
}
print("Failed to read from pipe: %s\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");
memcpy(l_buffer, buffer, dwRead);
int written = sys_write(bt->fd, l_buffer, dwRead);
if (unlikely(written < 0))
{
print("Failed to write to socket: %d\n", written);
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: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
}
}
}
void CreateBridge()
{
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
NewConnection:
if (GetNamedPipeInfo((HANDLE)lpszPipename,
NULL, NULL,
NULL, NULL))
{
print("Pipe already exists: %s\n",
GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Pipe already exists",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
HANDLE hPipe =
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES, BUFFER_LENGTH, BUFFER_LENGTH, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
print("Failed to create pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Failed to create pipe",
MB_OK | MB_ICONSTOP);
}
ExitProcess(1);
}
print("Pipe %s(%#x) created\n", lpszPipename, hPipe);
print("Waiting for pipe connection\n");
if (!ConnectNamedPipe(hPipe, NULL))
{
print("Failed to connect to pipe: %s\n",
GetErrorMessage());
if (!RunningAsService)
MessageBox(NULL, GetErrorMessage(),
NULL, MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
print("Pipe connected\n");
int fd;
if (IsLinux)
{
// unsigned long socketArgs[] = {
// (unsigned long)AF_UNIX,
// (unsigned long)SOCK_STREAM,
// 0};
unsigned long *socketArgs = sys_mmap(0x21000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
socketArgs[0] = (unsigned long)AF_UNIX;
socketArgs[1] = (unsigned long)SOCK_STREAM;
socketArgs[2] = 0;
fd = sys_socketcall(SYS_SOCKET, socketArgs);
/* FIXME: WSAEAFNOSUPPORT: https://gitlab.winehq.org/wine/wine/-/merge_requests/2786 */
// WSADATA wsaData;
// int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
// if (iResult != 0)
// printf("WSAStartup failed: %d\n", iResult);
// fd = socket(AF_UNIX, SOCK_STREAM, 0);
}
else
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == INVALID_SOCKET)
{
print("invalid socket: %d %d\n", fd, WSAGetLastError());
ExitProcess(1);
}
if (fd < 0)
{
print("Failed to create socket: %d\n", fd);
if (!RunningAsService)
MessageBox(NULL, "Failed to create socket",
NULL, MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
print("Socket %d created\n", fd);
ConnectToSocket(fd);
print("Connected to Discord\n");
bridge_thread bt = {fd, hPipe};
hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
(LPVOID)&bt,
0, NULL);
print("Created in thread %#lx\n", hIn);
hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
(LPVOID)&bt,
0, NULL);
print("Created out thread %#lx\n", hOut);
if (hIn == NULL || hOut == NULL)
{
print("Failed to create threads: %s\n", GetErrorMessage());
if (!RunningAsService)
{
MessageBox(NULL, GetErrorMessage(),
"Failed to create threads",
MB_OK | MB_ICONSTOP);
}
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);
}

BIN
src/bridge.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

32
src/bridge.manifest Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="amd64"
name="com.enderice2.rpc-bridge"
type="win32"
/>
<description>Simple bridge that allows you to use Discord Rich Presence with Wine games/software.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="false"
/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

53
src/bridge.rc Normal file
View File

@ -0,0 +1,53 @@
#include <windef.h>
#include <winuser.h>
#include <winresrc.h>
#include "resource.h"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_VERSION
PRODUCTVERSION VER_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "FileDescription", "Simple bridge that allows you to use Discord Rich Presence with Wine games/software."
VALUE "FileVersion", VER_VERSION_STR
VALUE "InternalName", "bridge"
VALUE "LegalCopyright", "Copyright (c) 2025 EnderIce2"
VALUE "OriginalFilename", "bridge.exe"
VALUE "ProductName", "rpc-bridge"
VALUE "ProductVersion", VER_VERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1252
END
END
IDR_MAINMENU MENU
BEGIN
POPUP "&View"
BEGIN
MENUITEM "&Log", IDM_VIEW_LOG
END
POPUP "&Help"
BEGIN
MENUITEM "&Documentation", IDM_HELP_DOCUMENTATION
MENUITEM "&License", IDM_HELP_LICENSE
MENUITEM "&About", IDM_HELP_ABOUT
END
END
IDR_LICENSE_TXT RCDATA "../LICENSE"
IDI_ICON_128 ICON "bridge.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST bridge.manifest

148
src/game.c Normal file
View File

@ -0,0 +1,148 @@
#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(),
"GetBinaryType",
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(),
"CreateProcess", 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(),
"CreateToolhelp32Snapshot", MB_OK | MB_ICONSTOP);
ExitProcess(0);
}
CloseHandle(game_pi.hProcess);
print("Game exited\n");
}

358
src/gui.c Normal file
View File

@ -0,0 +1,358 @@
#include <windowsx.h>
#include <windows.h>
#include <winuser.h>
#include <assert.h>
#include <stdio.h>
#include "resource.h"
/**
* The entire code could be better written, but at least it works.
*
* This will make installation and removal of the bridge WAY easier.
*/
LPTSTR GetErrorMessage();
void print(char const *fmt, ...);
void InstallService(int ServiceStartType, LPCSTR Path);
void RemoveService();
void CreateBridge();
extern BOOL IsLinux;
HWND hwnd = NULL;
HANDLE hBridge = NULL;
extern HANDLE hOut;
extern HANDLE hIn;
BOOL IsAlreadyRunning = FALSE;
VOID HandleStartButton(BOOL Silent)
{
if (IsAlreadyRunning)
{
HWND item = GetDlgItem(hwnd, 4);
SetWindowText(item, "Do you want to start, install or remove the bridge?");
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "&Start");
EnableWindow(item, FALSE);
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
print("Killing %#x, %#lx and waiting for %#lx\n", hIn, hOut, hBridge);
if (hIn != NULL)
TerminateThread(hIn, 0);
if (hOut != NULL)
TerminateThread(hOut, 0);
WaitForSingleObject(hBridge, INFINITE);
EnableWindow(item, TRUE);
IsAlreadyRunning = FALSE;
return;
}
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager == NULL)
{
print("OpenSCManager failed: %s\n", GetErrorMessage());
return;
}
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge",
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
if (schService == NULL)
{
print("Service doesn't exist: %s\n", GetErrorMessage());
/* Service doesn't exist; running without any service */
hBridge = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
NULL, 0, NULL);
HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "&Stop");
item = GetDlgItem(hwnd, 4);
SetWindowText(item, "Bridge is running...");
IsAlreadyRunning = TRUE;
ShowWindow(hwnd, SW_MINIMIZE);
return;
}
DWORD dwBytesNeeded;
QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded);
LPQUERY_SERVICE_CONFIG lpqsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, dwBytesNeeded);
if (lpqsc == NULL)
{
print("LocalAlloc failed: %s\n", GetErrorMessage());
return;
}
if (!QueryServiceConfig(schService, lpqsc, dwBytesNeeded, &dwBytesNeeded))
{
print("QueryServiceConfig failed: %s\n", GetErrorMessage());
return;
}
if (StartService(schService, 0, NULL) == FALSE)
{
if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
return;
print("StartService failed: %s\n", GetErrorMessage());
}
LocalFree(lpqsc);
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
if (Silent == FALSE)
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
print("Bridge service started successfully\n");
}
VOID HandleInstallButton()
{
char filename[MAX_PATH];
GetModuleFileName(NULL, filename, MAX_PATH);
CopyFile(filename, "C:\\windows\\bridge.exe", FALSE);
InstallService(SERVICE_AUTO_START, "C:\\windows\\bridge.exe --service");
MessageBox(NULL, "Bridge installed successfully", "Info", MB_OK);
HandleStartButton(TRUE);
ExitProcess(0);
}
VOID HandleRemoveButton()
{
RemoveService();
MessageBox(NULL, "Bridge removed successfully", "Info", MB_OK);
ExitProcess(0);
}
void ShowLicenseDialog()
{
HMODULE hModule = GetModuleHandle(NULL);
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_LICENSE_TXT), RT_RCDATA);
if (!hRes)
{
MessageBox(NULL, "Resource not found", "Error", MB_OK | MB_ICONERROR);
return;
}
HGLOBAL hResData = LoadResource(NULL, hRes);
if (!hResData)
{
MessageBox(NULL, "Resource failed to load", "Error", MB_OK | MB_ICONERROR);
return;
}
DWORD resSize = SizeofResource(NULL, hRes);
void *pRes = LockResource(hResData);
if (!pRes)
{
MessageBox(NULL, "Resource failed to lock", "Error", MB_OK | MB_ICONERROR);
return;
}
char *licenseText = (char *)malloc(resSize + 1);
if (!licenseText)
{
MessageBox(NULL, "Memory allocation failed", "Error", MB_OK | MB_ICONERROR);
return;
}
memcpy(licenseText, pRes, resSize);
licenseText[resSize] = '\0';
MessageBoxA(hwnd, licenseText, "About", MB_OK);
free(licenseText);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case 1:
HandleStartButton(FALSE);
break;
case 2:
HandleInstallButton();
break;
case 3:
HandleRemoveButton();
break;
case IDM_VIEW_LOG:
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", "C:\\windows\\logs\\bridge.log", NULL, SW_SHOW);
break;
case IDM_HELP_DOCUMENTATION:
ShellExecute(NULL, "open", "https://enderice2.github.io/rpc-bridge/index.html", NULL, NULL, SW_SHOWNORMAL);
break;
case IDM_HELP_LICENSE:
ShowLicenseDialog();
break;
case IDM_HELP_ABOUT:
{
char msg[256];
snprintf(msg, sizeof(msg),
"rpc-bridge v%s\n"
" branch: %s\n"
" commit: %s\n\n"
"Simple bridge that allows you to use Discord Rich Presence with Wine games/software.\n\n"
"Created by EnderIce2\n\n"
"Licensed under the MIT License",
VER_VERSION_STR, GIT_BRANCH, GIT_COMMIT);
MessageBox(NULL, msg, "About", MB_OK);
break;
}
default:
break;
}
break;
}
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
ExitProcess(0);
break;
case WM_CTLCOLORSTATIC:
{
HDC hdcStatic = (HDC)wParam;
SetBkMode(hdcStatic, TRANSPARENT);
return (INT_PTR)(HBRUSH)GetStockObject(NULL_BRUSH);
}
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
{
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
// if (!IsLinux)
// {
// *btnInstallStyle |= WS_DISABLED;
// *btnRemoveStyle |= WS_DISABLED;
// return;
// }
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
if (schService != NULL)
{
*btnInstallStyle |= WS_DISABLED;
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwBytesNeeded;
assert(QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssStatus,
sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded));
if (ssStatus.dwCurrentState == SERVICE_RUNNING ||
ssStatus.dwCurrentState == SERVICE_START_PENDING)
*btnStartStyle |= WS_DISABLED;
}
else
*btnRemoveStyle |= WS_DISABLED;
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
}
int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
INT btnStartStyle, btnRemoveStyle, btnInstallStyle;
SetButtonStyles(&btnStartStyle, &btnRemoveStyle, &btnInstallStyle);
const char szClassName[] = "BridgeWindowClass";
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = szClassName;
wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
assert(RegisterClassEx(&wc));
hwnd = CreateWindowEx(WS_EX_WINDOWEDGE,
szClassName,
"Discord RPC Bridge",
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME,
(GetSystemMetrics(SM_CXSCREEN) - 400) / 2,
(GetSystemMetrics(SM_CYSCREEN) - 150) / 2,
400, 150,
NULL, NULL, hInstance, NULL);
HICON hIcon = LoadIcon(hInstance, "IDI_ICON_128");
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
HWND hLbl4 = CreateWindowEx(WS_EX_TRANSPARENT,
"STATIC", "Do you want to start, install or remove the bridge?",
WS_CHILD | WS_VISIBLE | SS_CENTER,
0, 15, 400, 25,
hwnd, (HMENU)4, hInstance, NULL);
HWND hbtn1 = CreateWindow("BUTTON", "&Start",
btnStartStyle,
40, 60, 100, 30,
hwnd, (HMENU)1, hInstance, NULL);
HWND hbtn2 = CreateWindow("BUTTON", "&Install",
btnInstallStyle,
150, 60, 100, 30,
hwnd, (HMENU)2, hInstance, NULL);
HWND hbtn3 = CreateWindow("BUTTON", "&Remove",
btnRemoveStyle,
260, 60, 100, 30,
hwnd, (HMENU)3, hInstance, NULL);
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINMENU));
SetMenu(hwnd, hMenu);
HDC hDC = GetDC(hwnd);
int nHeight = -MulDiv(11, GetDeviceCaps(hDC, LOGPIXELSY), 72);
HFONT hFont = CreateFont(nHeight, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET,
OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH | FF_DONTCARE, TEXT("Segoe UI"));
ReleaseDC(hwnd, hDC);
SendMessage(hwnd, WM_SETFONT, hFont, TRUE);
SendMessage(hLbl4, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn1, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn2, WM_SETFONT, hFont, TRUE);
SendMessage(hbtn3, WM_SETFONT, hFont, TRUE);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
void CreateGUI()
{
ShowWindow(GetConsoleWindow(), SW_MINIMIZE);
ExitProcess(__WinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOWNORMAL));
}

340
src/main.c Normal file
View File

@ -0,0 +1,340 @@
#include <windows.h>
#include <winuser.h>
#include <assert.h>
#include <stdio.h>
#include "resource.h"
FILE *g_logFile = NULL;
BOOL RunningAsService = FALSE;
void CreateGUI();
void CreateBridge();
void LaunchGame(int argc, char **argv);
void ServiceMain(int argc, char *argv[]);
void InstallService(int ServiceStartType, LPCSTR Path);
char *native_getenv(const char *name);
void RemoveService();
extern BOOL IsLinux;
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)
{
MessageBox(NULL, "Failed to load ntdll.dll",
GetErrorMessage(), MB_OK | MB_ICONERROR);
ExitProcess(1);
}
if (!GetProcAddress(hNTdll, "wine_get_version"))
{
MessageBox(NULL, "This program is only intended to run under Wine.",
"Error", MB_OK | MB_ICONINFORMATION);
ExitProcess(1);
}
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version");
assert(wine_get_host_version);
const char *__sysname;
const char *__release;
wine_get_host_version(&__sysname, &__release);
if (strcmp(__sysname, "Linux") != 0 && strcmp(__sysname, "Darwin") != 0)
{
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
NULL, MB_YESNO | MB_ICONQUESTION);
if (result == IDNO)
ExitProcess(1);
}
IsLinux = strcmp(__sysname, "Linux") == 0;
}
void print(char const *fmt, ...)
{
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);
}
void HandleArguments(int argc, char *argv[])
{
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");
ExitProcess(1);
}
}
else if (strcmp(argv[1], "--steam") == 0)
{
/* All this mess just so when you close the game,
it automatically closes the bridge and Steam
will not say that the game is still running. */
print("Running as Steam\n");
if (IsLinux == FALSE)
CreateBridge();
if (argc > 2)
{
if (strcmp(argv[2], "--no-service") == 0)
CreateBridge();
}
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager == NULL)
{
print("(Steam) OpenSCManager: %s\n", GetErrorMessage());
ExitProcess(1);
}
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge",
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
if (schService == NULL)
{
if (GetLastError() != ERROR_SERVICE_DOES_NOT_EXIST)
{
print("(Steam) OpenService: %s\n", GetErrorMessage());
ExitProcess(1);
}
print("(Steam) Service does not exist, registering...\n");
WCHAR *(CDECL * wine_get_dos_file_name)(LPCSTR str) =
(void *)GetProcAddress(GetModuleHandleA("KERNEL32"),
"wine_get_dos_file_name");
char *unixPath = native_getenv("BRIDGE_PATH");
if (unixPath == NULL)
{
print("(Steam) BRIDGE_PATH not set\n");
ExitProcess(1);
}
WCHAR *dosPath = wine_get_dos_file_name(unixPath);
LPSTR asciiPath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
WideCharToMultiByte(CP_ACP, 0, dosPath, -1, asciiPath, MAX_PATH, NULL, NULL);
strcat_s(asciiPath, MAX_PATH, " --service");
print("(Steam) Binary path: %s\n", asciiPath);
InstallService(SERVICE_DEMAND_START, asciiPath);
HeapFree(GetProcessHeap(), 0, asciiPath);
/* Create handle for StartService below */
print("(Steam) Service registered, opening handle...\n");
/* FIXME: For some reason here it freezes??? */
schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START);
if (schService == NULL)
{
print("(Steam) Cannot open service after creation: %s\n", GetErrorMessage());
ExitProcess(1);
}
}
else
{
DWORD dwBytesNeeded;
QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded);
LPQUERY_SERVICE_CONFIG lpqsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR, dwBytesNeeded);
if (lpqsc == NULL)
{
print("(Steam) LocalAlloc: %s\n", GetErrorMessage());
ExitProcess(1);
}
if (!QueryServiceConfig(schService, lpqsc, dwBytesNeeded, &dwBytesNeeded))
{
print("(Steam) QueryServiceConfig: %s\n", GetErrorMessage());
ExitProcess(1);
}
WCHAR *(CDECL * wine_get_dos_file_name)(LPCSTR str) =
(void *)GetProcAddress(GetModuleHandleA("KERNEL32"),
"wine_get_dos_file_name");
char *unixPath = native_getenv("BRIDGE_PATH");
if (unixPath == NULL)
{
print("(Steam) BRIDGE_PATH not set\n");
ExitProcess(1);
}
WCHAR *dosPath = wine_get_dos_file_name(unixPath);
LPSTR asciiPath = (LPSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
WideCharToMultiByte(CP_ACP, 0, dosPath, -1, asciiPath, MAX_PATH, NULL, NULL);
strcat_s(asciiPath, MAX_PATH, " --service");
print("(Steam) Binary path: %s\n", asciiPath);
if (strcmp(lpqsc->lpBinaryPathName, asciiPath) != 0)
{
print("(Steam) Service binary path is not correct, updating...\n");
ChangeServiceConfig(schService, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
asciiPath, NULL, NULL, NULL, NULL, NULL, NULL);
}
else
print("(Steam) Service binary path is correct\n");
HeapFree(GetProcessHeap(), 0, asciiPath);
}
print("(Steam) Starting service and then exiting...\n");
fclose(g_logFile);
if (StartService(schService, 0, NULL) != FALSE)
{
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
ExitProcess(0);
}
else if (GetLastError() == ERROR_SERVICE_ALREADY_RUNNING)
{
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
ExitProcess(0);
}
MessageBox(NULL, GetErrorMessage(),
"StartService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
else if (strcmp(argv[1], "--install") == 0)
{
char filename[MAX_PATH];
GetModuleFileName(NULL, filename, MAX_PATH);
CopyFile(filename, "C:\\windows\\bridge.exe", FALSE);
InstallService(SERVICE_AUTO_START, "C:\\windows\\bridge.exe --service");
ExitProcess(0);
}
else if (strcmp(argv[1], "--uninstall") == 0)
{
RemoveService();
ExitProcess(0);
}
else if (strcmp(argv[1], "--rpc") == 0)
{
if (argc < 3)
{
print("No directory provided\n");
ExitProcess(1);
}
SetEnvironmentVariable("BRIDGE_RPC_PATH", argv[2]);
print("BRIDGE_RPC_PATH has been set to \"%s\"\n", argv[2]);
CreateBridge();
ExitProcess(0);
}
else if (strcmp(argv[1], "--version") == 0)
{
/* Already shows the version */
ExitProcess(0);
}
else if (strcmp(argv[1], "--help") == 0)
{
print("Usage:\n"
" %s [args]\n"
"\n"
"Arguments:\n"
" --help Show this help\n"
"\n"
" --version Show version\n"
"\n"
" --install Install service\n"
" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n"
"\n"
" --uninstall Uninstall service\n"
" This will remove the service and delete C:\\windows\\bridge.exe\n"
"\n"
" --steam Reserved for Steam\n"
" This will start the service and exit (used with bridge.sh)\n"
"\n"
" --no-service Do not run as service\n"
" (only for --steam)\n"
"\n"
" --service Reserved for service\n"
"\n"
" --rpc <dir> Set RPC_PATH environment variable\n"
" This is used to specify the directory where 'discord-ipc-0' is located\n"
"\n"
"Note: If no arguments are provided, the GUI will be shown instead\n",
argv[0]);
ExitProcess(0);
}
}
int main(int argc, char *argv[])
{
DetectWine();
char *logFilePath = "C:\\windows\\logs\\bridge.log";
g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL)
{
MessageBox(NULL, "Failed to open logs file", "Error", MB_OK | MB_ICONERROR);
printf("Failed to open logs file: %ld\n", GetLastError());
ExitProcess(1);
}
print("rpc-bridge v%s %s-%s\n", VER_VERSION_STR, GIT_BRANCH, GIT_COMMIT);
if (argc > 1)
HandleArguments(argc, argv);
else
CreateGUI();
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateBridge,
NULL, 0, NULL);
Sleep(500);
LaunchGame(argc, argv);
fclose(g_logFile);
ExitProcess(0);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
return main(__argc, __argv);
}

9
src/resource.h Normal file
View File

@ -0,0 +1,9 @@
#define IDR_MAINMENU 101
#define IDR_LICENSE_TXT 102
#define IDM_HELP_DOCUMENTATION 40001
#define IDM_HELP_LICENSE 40002
#define IDM_HELP_ABOUT 40003
#define IDM_VIEW_LOG 40004
#define VER_VERSION 1, 4, 0, 0
#define VER_VERSION_STR "1.4.0.0\0"

191
src/service.c Normal file
View File

@ -0,0 +1,191 @@
#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();
extern BOOL IsLinux;
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(int ServiceStartType, LPCSTR Path)
{
print("Registering service\n");
// if (IsLinux == FALSE)
// {
// /* FIXME: I don't know how to get the TMPDIR without getenv */
// MessageBox(NULL, "Registering as a service is not supported on macOS at the moment.",
// "Unsupported", MB_OK | MB_ICONINFORMATION);
// ExitProcess(1);
// }
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL)
{
print("Failed to open service manager\n");
MessageBox(NULL, GetErrorMessage(),
"OpenSCManager",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
DWORD dwTagId;
SC_HANDLE schService = CreateService(schSCManager,
"rpc-bridge", "Wine RPC Bridge",
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
ServiceStartType, SERVICE_ERROR_NORMAL,
Path, NULL, &dwTagId, NULL, NULL, NULL);
if (schService == NULL)
{
print("Failed to create service\n");
MessageBox(NULL, GetErrorMessage(),
"CreateService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
print("Service installed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
void RemoveService()
{
print("Unregistering from startup\n");
SC_HANDLE schSCManager, schService;
SERVICE_STATUS ssSvcStatus;
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (schSCManager == NULL)
{
MessageBox(NULL, GetErrorMessage(),
"OpenSCManager",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
schService = OpenService(schSCManager, "rpc-bridge",
SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);
if (schService == NULL)
{
MessageBox(NULL, GetErrorMessage(),
"OpenService",
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(),
"DeleteService",
MB_OK | MB_ICONSTOP);
ExitProcess(1);
}
DeleteFile("C:\\windows\\bridge.exe");
print("Service removed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}