mirror of
https://github.com/EnderIce2/rpc-bridge.git
synced 2025-07-10 23:09:13 +00:00
refactor: move sources to src/
This commit is contained in:
26
src/Makefile
Normal file
26
src/Makefile
Normal 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
670
src/bridge.c
Normal 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
BIN
src/bridge.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
32
src/bridge.manifest
Normal file
32
src/bridge.manifest
Normal 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
53
src/bridge.rc
Normal 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
148
src/game.c
Normal 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
358
src/gui.c
Normal 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
340
src/main.c
Normal 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
9
src/resource.h
Normal 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
191
src/service.c
Normal 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);
|
||||
}
|
Reference in New Issue
Block a user