9 Commits

15 changed files with 586 additions and 168 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
*.o *.o
*.exe *.exe
*.res *.res
*.elf

View File

@ -9,6 +9,17 @@
"cStandard": "c17", "cStandard": "c17",
"cppStandard": "gnu++17", "cppStandard": "gnu++17",
"intelliSenseMode": "windows-gcc-x64" "intelliSenseMode": "windows-gcc-x64"
},
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64",
"compilerPath": "/usr/bin/gcc"
} }
], ],
"version": 4 "version": 4

View File

@ -12,6 +12,7 @@ build: $(C_OBJECTS)
$(info Linking) $(info Linking)
x86_64-w64-mingw32-windres bridge.rc -O coff -o bridge.res 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 x86_64-w64-mingw32-gcc $(C_OBJECTS) bridge.res $(LFLAGS) $(DBGFLAGS) -o build/bridge.exe
gcc kvm.c -o build/rpc-bridge.elf
%.o: %.c %.o: %.c
$(info Compiling $<) $(info Compiling $<)

View File

@ -62,7 +62,7 @@ The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you
The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`. The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
*Note: You will need to launch the `bridge.exe` file manually in Wine atleast one time for it to register and launch automatically the next times.* *Note: You will need to launch the `bridge.exe` file manually in Wine at least once for it to register and launch automatically the next time.*
## Compiling from source ## Compiling from source

252
bridge.c
View File

@ -4,6 +4,8 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#define __linux_read 3 #define __linux_read 3
#define __linux_write 4 #define __linux_write 4
#define __linux_open 5 #define __linux_open 5
@ -64,7 +66,7 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage(); LPTSTR GetErrorMessage();
extern BOOL RunningAsService; extern BOOL RunningAsService;
BOOL RetryNewConnection; BOOL RetryNewConnection;
BOOL IsLinux; OS_INFO OSInfo = {0};
HANDLE hOut = NULL; HANDLE hOut = NULL;
HANDLE hIn = NULL; HANDLE hIn = NULL;
@ -102,7 +104,7 @@ static naked int darwin_syscall(int num,
static inline int sys_read(int fd, void *buf, size_t count) static inline int sys_read(int fd, void *buf, size_t count)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0); return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else else
return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0); return darwin_syscall(__darwin_read, fd, buf, count, 0, 0, 0);
@ -110,7 +112,7 @@ static inline int sys_read(int fd, void *buf, size_t count)
static inline int sys_write(int fd, const void *buf, size_t count) static inline int sys_write(int fd, const void *buf, size_t count)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0); return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else else
return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0); return darwin_syscall(__darwin_write, fd, buf, count, 0, 0, 0);
@ -118,7 +120,7 @@ static inline int sys_write(int fd, const void *buf, size_t count)
static inline int sys_open(const char *pathname, int flags, int mode) static inline int sys_open(const char *pathname, int flags, int mode)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0); return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else else
return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0); return darwin_syscall(__darwin_open, pathname, flags, mode, 0, 0, 0);
@ -126,7 +128,7 @@ static inline int sys_open(const char *pathname, int flags, int mode)
static inline int sys_close(int fd) static inline int sys_close(int fd)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0); return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else else
return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0); return darwin_syscall(__darwin_close, fd, 0, 0, 0, 0, 0);
@ -134,25 +136,25 @@ static inline int sys_close(int fd)
static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset) static inline unsigned int *sys_mmap(unsigned int *addr, size_t length, int prot, int flags, int fd, off_t offset)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset); return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
} }
static inline int sys_munmap(unsigned int *addr, size_t length) static inline int sys_munmap(unsigned int *addr, size_t length)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0); return linux_syscall(__linux_munmap, addr, length, 0, 0, 0, 0);
} }
static inline int sys_socketcall(int call, unsigned long *args) static inline int sys_socketcall(int call, unsigned long *args)
{ {
assert(IsLinux); assert(OSInfo.IsLinux);
return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0); return linux_syscall(__linux_socketcall, call, args, 0, 0, 0, 0);
} }
static inline int sys_socket(int domain, int type, int protocol) static inline int sys_socket(int domain, int type, int protocol)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0); return linux_syscall(__linux_socket, domain, type, protocol, 0, 0, 0);
else else
return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0); return darwin_syscall(__darwin_socket, domain, type, protocol, 0, 0, 0);
@ -160,7 +162,7 @@ static inline int sys_socket(int domain, int type, int protocol)
static inline int sys_connect(int s, caddr_t name, socklen_t namelen) static inline int sys_connect(int s, caddr_t name, socklen_t namelen)
{ {
if (IsLinux) if (OSInfo.IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0); return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0); return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
@ -174,7 +176,7 @@ char *native_getenv(const char *name)
if (ret != 0) if (ret != 0)
return lpBuffer; return lpBuffer;
if (!IsLinux) if (!OSInfo.IsLinux)
{ {
char *value = getenv(name); char *value = getenv(name);
if (value == NULL) if (value == NULL)
@ -251,9 +253,10 @@ void ConnectToSocket(int fd)
{ {
print("Connecting to socket\n"); print("Connecting to socket\n");
const char *runtime; const char *runtime;
if (IsLinux) if (OSInfo.IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR"); runtime = native_getenv("XDG_RUNTIME_DIR");
else else if (OSInfo.IsDarwin)
{
runtime = native_getenv("TMPDIR"); runtime = native_getenv("TMPDIR");
if (runtime == NULL) if (runtime == NULL)
{ {
@ -267,13 +270,21 @@ void ConnectToSocket(int fd)
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); 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 // Handle the case where the directory doesn't exist
// For example, create the directory // For example, create the directory
if (!RunningAsService)
MessageBox(NULL, "IPC directory does not exist", int result = MessageBox(NULL, "IPC directory does not exist\nDo you want to open the installation guide?",
"Directory not found", "Directory not found",
MB_OK | MB_ICONSTOP); 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); ExitProcess(1);
} }
} }
}
else
{
print("Unsupported OS\n");
ExitProcess(1);
}
print("IPC directory: %s\n", runtime); print("IPC directory: %s\n", runtime);
@ -298,7 +309,7 @@ void ConnectToSocket(int fd)
print("Connecting to %s\n", pipePath); print("Connecting to %s\n", pipePath);
if (IsLinux) if (OSInfo.IsLinux)
{ {
unsigned long socketArgs[] = { unsigned long socketArgs[] = {
(unsigned long)fd, (unsigned long)fd,
@ -476,6 +487,162 @@ void PipeBufferOutThread(LPVOID lpParam)
} }
} }
void COMPipeBufferInThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("COM In thread started using COM port and pipe %#x\n", bt->hPipe);
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComPort == INVALID_HANDLE_VALUE)
{
print("Failed to open COM2 port: %s\n", GetErrorMessage());
return;
}
int EOFCount = 0;
while (TRUE)
{
char buffer[BUFFER_LENGTH];
DWORD bytesRead;
if (!ReadFile(hComPort, buffer, sizeof(buffer), &bytesRead, NULL))
{
print("Failed to read from COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (EOFCount > 4)
{
print("EOF count exceeded\n");
RetryNewConnection = TRUE;
TerminateThread(hOut, 0);
break;
}
if (bytesRead == 0)
{
print("EOF\n");
Sleep(1000);
EOFCount++;
continue;
}
EOFCount = 0;
print("Reading %d bytes from COM2 port: \"", bytesRead);
for (DWORD i = 0; i < bytesRead; i++)
print("%c", buffer[i]);
print("\"\n");
DWORD dwWritten;
if (!WriteFile(bt->hPipe, buffer, bytesRead, &dwWritten, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
while (dwWritten < bytesRead)
{
int last_written = dwWritten;
if (!WriteFile(bt->hPipe, buffer + dwWritten, bytesRead - dwWritten, &dwWritten, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("In Broken pipe\n");
break;
}
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (last_written == dwWritten)
{
print("Failed to write to pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
}
}
CloseHandle(hComPort);
}
void COMPipeBufferOutThread(LPVOID lpParam)
{
bridge_thread *bt = (bridge_thread *)lpParam;
print("COM Out thread started using COM port and pipe %#x\n", bt->hPipe);
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hComPort == INVALID_HANDLE_VALUE)
{
print("Failed to open COM2 port: %s\n", GetErrorMessage());
return;
}
while (TRUE)
{
char buffer[BUFFER_LENGTH];
DWORD dwRead;
if (!ReadFile(bt->hPipe, buffer, sizeof(buffer), &dwRead, NULL))
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
RetryNewConnection = TRUE;
print("Out Broken pipe\n");
break;
}
print("Failed to read from pipe: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
print("Writing %d bytes to COM2 port: \"", dwRead);
for (DWORD i = 0; i < dwRead; i++)
print("%c", buffer[i]);
print("\"\n");
DWORD bytesWritten;
if (!WriteFile(hComPort, buffer, dwRead, &bytesWritten, NULL))
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
continue;
}
while (bytesWritten < dwRead)
{
int last_written = bytesWritten;
if (!WriteFile(hComPort, buffer + bytesWritten, dwRead - bytesWritten, &bytesWritten, NULL))
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
if (last_written == bytesWritten)
{
print("Failed to write to COM2 port: %s\n", GetErrorMessage());
Sleep(1000);
continue;
}
}
}
CloseHandle(hComPort);
}
void CreateBridge() void CreateBridge()
{ {
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0"); LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
@ -500,7 +667,7 @@ NewConnection:
CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0", CreateNamedPipe("\\\\.\\pipe\\discord-ipc-0",
PIPE_ACCESS_DUPLEX, PIPE_ACCESS_DUPLEX,
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
1, 1024, 1024, 0, NULL); PIPE_UNLIMITED_INSTANCES, BUFFER_LENGTH, BUFFER_LENGTH, 0, NULL);
if (hPipe == INVALID_HANDLE_VALUE) if (hPipe == INVALID_HANDLE_VALUE)
{ {
@ -529,7 +696,49 @@ NewConnection:
print("Pipe connected\n"); print("Pipe connected\n");
int fd; int fd;
if (IsLinux) if (!OSInfo.IsWine)
{
print("Running on Windows\n");
/* KVM, send 0xBB1569 and wait with timeout (TODO) for response 0xB41D6E */
print("Trying \\\\.\\COM2\n");
HANDLE hComPort = CreateFile("\\\\.\\COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
assert(hComPort != INVALID_HANDLE_VALUE);
DWORD dwWritten;
print("Writing to COM port\n");
char buffer[4] = "\x69\x15\xBB";
assert(WriteFile(hComPort, buffer, sizeof(buffer), &dwWritten, NULL));
print("Wrote %d bytes to COM port\n", dwWritten);
DWORD dwRead;
print("Reading from COM port\n");
assert(ReadFile(hComPort, buffer, sizeof(buffer), &dwRead, NULL));
print("Read %d bytes from COM port\n", dwRead);
if (dwRead != 4 || memcmp(buffer, "\x6E\x1D\xB4", 4) != 0)
{
CloseHandle(hComPort);
print("Failed to connect to COM2\n");
return;
}
CloseHandle(hComPort);
print("Connected to COM2\n");
bridge_thread bt = {0, hPipe};
hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)COMPipeBufferInThread,
(LPVOID)&bt,
0, NULL);
hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)COMPipeBufferOutThread,
(LPVOID)&bt,
0, NULL);
}
else
{
if (OSInfo.IsLinux)
{ {
unsigned long socketArgs[] = { unsigned long socketArgs[] = {
(unsigned long)AF_UNIX, (unsigned long)AF_UNIX,
@ -560,12 +769,14 @@ NewConnection:
(LPTHREAD_START_ROUTINE)PipeBufferInThread, (LPTHREAD_START_ROUTINE)PipeBufferInThread,
(LPVOID)&bt, (LPVOID)&bt,
0, NULL); 0, NULL);
print("Created in thread %#lx\n", hIn);
hOut = CreateThread(NULL, 0, hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferOutThread, (LPTHREAD_START_ROUTINE)PipeBufferOutThread,
(LPVOID)&bt, (LPVOID)&bt,
0, NULL); 0, NULL);
}
print("Created in thread %#lx\n", hIn);
print("Created out thread %#lx\n", hOut); print("Created out thread %#lx\n", hOut);
if (hIn == NULL || hOut == NULL) if (hIn == NULL || hOut == NULL)
@ -596,6 +807,7 @@ NewConnection:
print("Failed to terminate thread: %s\n", print("Failed to terminate thread: %s\n",
GetErrorMessage()); GetErrorMessage());
if (OSInfo.IsWine)
sys_close(fd); sys_close(fd);
CloseHandle(hOut); CloseHandle(hOut);
CloseHandle(hIn); CloseHandle(hIn);

13
bridge.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef _BRIDGE_H
#define _BRIDGE_H
#include <windows.h>
typedef struct _OS_INFO
{
BOOL IsLinux;
BOOL IsDarwin;
BOOL IsWine;
} OS_INFO;
#endif // _BRIDGE_H

View File

@ -2,8 +2,7 @@
#include <winuser.h> #include <winuser.h>
#include <winresrc.h> #include <winresrc.h>
#define VER_VERSION 1,1,0,0 #include "resource.h"
#define VER_VERSION_STR "1.1\0"
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_VERSION FILEVERSION VER_VERSION
@ -33,6 +32,22 @@ BEGIN
END END
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" IDI_ICON_128 ICON "bridge.ico"
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST bridge.manifest CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST bridge.manifest

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/bash
# This script is used to create a LaunchAgent on MacOS, to support the service functionnality. # This script is used to create a LaunchAgent on MacOS, to support the service functionality.
# Usage: ./launchd.sh (install|remove) # Usage: ./launchd.sh (install|remove)
SYMLINK=/tmp/rpc-bridge/tmpdir SYMLINK=/tmp/rpc-bridge/tmpdir
@ -27,7 +27,7 @@ if [ ! -d "\$TARGET_DIR" ]; then
mkdir -p "\$TARGET_DIR" mkdir -p "\$TARGET_DIR"
fi fi
rm -rf "\$TARGET_DIR" rm -rf "\$TARGET_DIR"
ln -s "\$TMPDIR" "\$TARGET_DIR"" > "$SCRIPT" ln -s "\$TMPDIR" "\$TARGET_DIR"" > "$SCRIPT"
chmod +x "$SCRIPT" chmod +x "$SCRIPT"
# LaunchAgent # LaunchAgent
@ -50,13 +50,12 @@ rm -rf "\$TARGET_DIR"
</plist>" > "$AGENT" </plist>" > "$AGENT"
launchctl load "$AGENT" launchctl load "$AGENT"
echo "LaunchAgent has been installed." echo "LaunchAgent has been installed."
} }
function remove() { function remove() {
rm -rf "$SYMLINK" rm -f "$SYMLINK"
rm -rf "$LOCATION"
rm -f "$SCRIPT" rm -f "$SCRIPT"
rmdir "$LOCATION"
if [ -f "$AGENT" ]; then if [ -f "$AGENT" ]; then
launchctl unload "$AGENT" launchctl unload "$AGENT"
fi fi

View File

@ -64,14 +64,16 @@ This method is recommended because it's easier to manage.
## macOS ## macOS
If using the default Wine prefix (`~/.wine`), you can follow the same steps as in Wine. The steps for MacOS are almost the same, but due to the way `$TMPDIR` works, you will have to install a **LaunchAgent**.
Registering as a service [is not supported](https://github.com/EnderIce2/rpc-bridge/issues/1#issuecomment-2103423242 "Bridge can't get $TMPDIR unless is set with --rpc. See note below"). - Download the latest build from the [releases](https://github.com/EnderIce2/rpc-bridge/releases)
- Open the archive and make the `launchd.sh` script executable by doing: `chmod +x launchd.sh`
- To **install** the LaunchAgent, run `./launchd install` and to **remove** it simply run `./launchd remove`.
The script will add a LaunchAgent to your user, that will symlink the `$TMPDIR` directory to `/tmp/rpc-bridge/tmpdir`.
!!! info "Note" !!! info "Note"
You will need to launch the `bridge.exe` file manually in Wine at least once for it to register and launch automatically the next time.
Since macOS doesn't have `/proc/self/environ` and `$TMPDIR` is neither always set nor the same after reboots, so you will need to [add `--rpc "$TMPDIR"` to the `bridge.exe` arguments](https://github.com/EnderIce2/rpc-bridge/issues/1#issuecomment-2104797235).
If you don't encounter any issues, you can ignore this step.
## Run without installing the service ## Run without installing the service

View File

@ -13,6 +13,9 @@
- `--help` Show help message - `--help` Show help message
- This will show the help message - This will show the help message
- `--version` Show version
- This will show the version of the program
- `--install` Install the service - `--install` Install the service
- This will copy the binary to `C:\windows\bridge.exe` and register it as a service - This will copy the binary to `C:\windows\bridge.exe` and register it as a service

112
gui.c
View File

@ -4,6 +4,9 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#include "resource.h"
/** /**
* The entire code could be better written, but at least it works. * The entire code could be better written, but at least it works.
* *
@ -15,7 +18,8 @@ void print(char const *fmt, ...);
void InstallService(int ServiceStartType, LPCSTR Path); void InstallService(int ServiceStartType, LPCSTR Path);
void RemoveService(); void RemoveService();
void CreateBridge(); void CreateBridge();
extern BOOL IsLinux; extern OS_INFO OSInfo;
extern char *logFilePath;
HWND hwnd = NULL; HWND hwnd = NULL;
HANDLE hBridge = NULL; HANDLE hBridge = NULL;
@ -31,7 +35,7 @@ VOID HandleStartButton(BOOL Silent)
SetWindowText(item, "Do you want to start, install or remove the bridge?"); SetWindowText(item, "Do you want to start, install or remove the bridge?");
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
item = GetDlgItem(hwnd, /* Start Button */ 1); item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "Start"); Button_SetText(item, "&Start");
EnableWindow(item, FALSE); EnableWindow(item, FALSE);
RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN); RedrawWindow(item, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
@ -47,21 +51,7 @@ VOID HandleStartButton(BOOL Silent)
return; return;
} }
if (!IsLinux) SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
{
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;
}
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hSCManager == NULL) if (hSCManager == NULL)
{ {
print("OpenSCManager failed: %s\n", GetErrorMessage()); print("OpenSCManager failed: %s\n", GetErrorMessage());
@ -72,7 +62,7 @@ VOID HandleStartButton(BOOL Silent)
SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START); SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_START);
if (schService == NULL) if (schService == NULL)
{ {
// print("OpenService failed: %s\n", GetErrorMessage()); print("Service doesn't exist: %s\n", GetErrorMessage());
/* Service doesn't exist; running without any service */ /* Service doesn't exist; running without any service */
@ -80,7 +70,7 @@ VOID HandleStartButton(BOOL Silent)
NULL, 0, NULL); NULL, 0, NULL);
HWND item = GetDlgItem(hwnd, /* Start Button */ 1); HWND item = GetDlgItem(hwnd, /* Start Button */ 1);
Button_SetText(item, "Stop"); Button_SetText(item, "&Stop");
item = GetDlgItem(hwnd, 4); item = GetDlgItem(hwnd, 4);
SetWindowText(item, "Bridge is running..."); SetWindowText(item, "Bridge is running...");
IsAlreadyRunning = TRUE; IsAlreadyRunning = TRUE;
@ -115,6 +105,7 @@ VOID HandleStartButton(BOOL Silent)
CloseServiceHandle(hSCManager); CloseServiceHandle(hSCManager);
if (Silent == FALSE) if (Silent == FALSE)
MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK); MessageBox(NULL, "Bridge service started successfully", "Info", MB_OK);
print("Bridge service started successfully\n");
} }
VOID HandleInstallButton() VOID HandleInstallButton()
@ -135,6 +126,44 @@ VOID HandleRemoveButton()
ExitProcess(0); ExitProcess(0);
} }
void ShowLicenseDialog()
{
HMODULE hModule = GetModuleHandle(NULL);
HRSRC hRes = FindResource(hModule, MAKEINTRESOURCE(IDR_LICENSE_TXT), RT_RCDATA);
if (!hRes)
{
MessageBox(NULL, "Resource not found", "Error", MB_OK | MB_ICONERROR);
return;
}
HGLOBAL hResData = LoadResource(NULL, hRes);
if (!hResData)
{
MessageBox(NULL, "Resource failed to load", "Error", MB_OK | MB_ICONERROR);
return;
}
DWORD resSize = SizeofResource(NULL, hRes);
void *pRes = LockResource(hResData);
if (!pRes)
{
MessageBox(NULL, "Resource failed to lock", "Error", MB_OK | MB_ICONERROR);
return;
}
char *licenseText = (char *)malloc(resSize + 1);
if (!licenseText)
{
MessageBox(NULL, "Memory allocation failed", "Error", MB_OK | MB_ICONERROR);
return;
}
memcpy(licenseText, pRes, resSize);
licenseText[resSize] = '\0';
MessageBoxA(hwnd, licenseText, "About", MB_OK);
free(licenseText);
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
switch (msg) switch (msg)
@ -152,6 +181,26 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
case 3: case 3:
HandleRemoveButton(); HandleRemoveButton();
break; break;
case IDM_VIEW_LOG:
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", logFilePath, 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];
sprintf(msg, "rpc-bridge v%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);
MessageBox(NULL, msg, "About", MB_OK);
break;
}
default: default:
break; break;
} }
@ -178,16 +227,9 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle) VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallStyle)
{ {
*btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *btnStartStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *btnRemoveStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP;
*btnInstallStyle = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON; *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 hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS); SC_HANDLE schService = OpenService(hSCManager, "rpc-bridge", SERVICE_START | SERVICE_QUERY_STATUS);
@ -254,21 +296,24 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
0, 15, 400, 25, 0, 15, 400, 25,
hwnd, (HMENU)4, hInstance, NULL); hwnd, (HMENU)4, hInstance, NULL);
HWND hbtn1 = CreateWindow("BUTTON", "Start", HWND hbtn1 = CreateWindow("BUTTON", "&Start",
btnStartStyle, btnStartStyle,
40, 60, 100, 30, 40, 60, 100, 30,
hwnd, (HMENU)1, hInstance, NULL); hwnd, (HMENU)1, hInstance, NULL);
HWND hbtn2 = CreateWindow("BUTTON", "Install", HWND hbtn2 = CreateWindow("BUTTON", "&Install",
btnInstallStyle, btnInstallStyle,
150, 60, 100, 30, 150, 60, 100, 30,
hwnd, (HMENU)2, hInstance, NULL); hwnd, (HMENU)2, hInstance, NULL);
HWND hbtn3 = CreateWindow("BUTTON", "Remove", HWND hbtn3 = CreateWindow("BUTTON", "&Remove",
btnRemoveStyle, btnRemoveStyle,
260, 60, 100, 30, 260, 60, 100, 30,
hwnd, (HMENU)3, hInstance, NULL); hwnd, (HMENU)3, hInstance, NULL);
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAINMENU));
SetMenu(hwnd, hMenu);
HDC hDC = GetDC(hwnd); HDC hDC = GetDC(hwnd);
int nHeight = -MulDiv(11, GetDeviceCaps(hDC, LOGPIXELSY), 72); int nHeight = -MulDiv(11, GetDeviceCaps(hDC, LOGPIXELSY), 72);
@ -288,10 +333,13 @@ int WINAPI __WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
MSG msg; MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) while (GetMessage(&msg, NULL, 0, 0) > 0)
{
if (!IsDialogMessage(hwnd, &msg))
{ {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
}
return msg.wParam; return msg.wParam;
} }

100
kvm.c Normal file
View File

@ -0,0 +1,100 @@
#if defined(__linux__)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <signal.h>
void transferData(int fromSock, int toSock)
{
char buffer[256];
ssize_t bytesRead;
while ((bytesRead = read(fromSock, buffer, sizeof(buffer))) > 0)
{
write(toSock, buffer, bytesRead);
}
}
int connectToSocket(const char *socketPath)
{
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
assert(sock >= 0);
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socketPath, sizeof(addr.sun_path) - 1);
int result = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
assert(result == 0);
return sock;
}
int kvmSock;
int discordSock;
void sigintHandler(int sig)
{
close(kvmSock);
close(discordSock);
exit(0);
}
int main()
{
signal(SIGINT, sigintHandler);
const char *kvmSocketPath = "/tmp/kvm-rpc-bridge";
const char *discordSocketPath = getenv("XDG_RUNTIME_DIR");
if (discordSocketPath == NULL)
{
fprintf(stderr, "XDG_RUNTIME_DIR environment variable not set\n");
return EXIT_FAILURE;
}
char discordSocketFullPath[256];
snprintf(discordSocketFullPath, sizeof(discordSocketFullPath), "%s/discord-ipc-0", discordSocketPath);
printf("Trying Discord socket at %s\n", discordSocketFullPath);
discordSock = connectToSocket(discordSocketFullPath);
printf("Connected to Discord socket successfully\n");
// Connect to KVM socket
printf("Trying KVM socket at %s\n", kvmSocketPath);
kvmSock = connectToSocket(kvmSocketPath);
// Read specific message from KVM and send confirmation
const char kvmExpectedMsg[] = {0x69, 0x15, 0xBB};
const char kvmResponseMsg[] = {0x6E, 0x1D, 0xB4};
char buffer[3];
ssize_t bytesRead = read(kvmSock, buffer, sizeof(buffer));
printf("Read %ld bytes from KVM (%x %x %x)\n", bytesRead, buffer[0], buffer[1], buffer[2]);
assert(bytesRead == sizeof(kvmExpectedMsg));
assert(memcmp(buffer, kvmExpectedMsg, sizeof(kvmExpectedMsg)) == 0);
ssize_t bytesSent = write(kvmSock, kvmResponseMsg, sizeof(kvmResponseMsg));
assert(bytesSent == sizeof(kvmResponseMsg));
printf("Connected to KVM socket successfully\n");
// Transfer data between KVM and Discord sockets
if (fork() == 0)
{
// Child process: transfer data from KVM to Discord
transferData(kvmSock, discordSock);
}
else
{
// Parent process: transfer data from Discord to KVM
transferData(discordSock, kvmSock);
}
close(kvmSock);
close(discordSock);
return 0;
}
#endif // __linux__

91
main.c
View File

@ -3,8 +3,12 @@
#include <assert.h> #include <assert.h>
#include <stdio.h> #include <stdio.h>
#include "bridge.h"
#include "resource.h"
FILE *g_logFile = NULL; FILE *g_logFile = NULL;
BOOL RunningAsService = FALSE; BOOL RunningAsService = FALSE;
char *logFilePath = NULL;
void CreateGUI(); void CreateGUI();
void CreateBridge(); void CreateBridge();
@ -13,7 +17,7 @@ void ServiceMain(int argc, char *argv[]);
void InstallService(int ServiceStartType, LPCSTR Path); void InstallService(int ServiceStartType, LPCSTR Path);
char *native_getenv(const char *name); char *native_getenv(const char *name);
void RemoveService(); void RemoveService();
extern BOOL IsLinux; extern OS_INFO OSInfo;
LPTSTR GetErrorMessage() LPTSTR GetErrorMessage()
{ {
@ -55,11 +59,8 @@ void DetectWine()
} }
if (!GetProcAddress(hNTdll, "wine_get_version")) if (!GetProcAddress(hNTdll, "wine_get_version"))
{ return;
MessageBox(NULL, "This program is only intended to run under Wine.", OSInfo.IsWine = TRUE;
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
ExitProcess(1);
}
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release); static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version"); wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version");
@ -72,14 +73,12 @@ void DetectWine()
{ {
int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?", int result = MessageBox(NULL, "This program is designed for Linux and macOS only!\nDo you want to proceed?",
NULL, MB_YESNO | MB_ICONQUESTION); NULL, MB_YESNO | MB_ICONQUESTION);
if (result == IDYES) if (result == IDNO)
return;
else if (result == IDNO)
ExitProcess(1); ExitProcess(1);
} }
IsLinux = strcmp(__sysname, "Linux") == 0; OSInfo.IsLinux = strcmp(__sysname, "Linux") == 0;
printf("Running on %s\n", __sysname); OSInfo.IsDarwin = strcmp(__sysname, "Darwin") == 0;
} }
void print(char const *fmt, ...) void print(char const *fmt, ...)
@ -114,14 +113,13 @@ void HandleArguments(int argc, char *argv[])
} }
else if (strcmp(argv[1], "--steam") == 0) else if (strcmp(argv[1], "--steam") == 0)
{ {
assert(OSInfo.IsWine == TRUE);
/* All this mess just so when you close the game, /* All this mess just so when you close the game,
it automatically closes the bridge and Steam it automatically closes the bridge and Steam
will not say that the game is still running. */ will not say that the game is still running. */
print("Running as Steam\n"); print("Running as Steam\n");
if (IsLinux == FALSE)
CreateBridge();
if (argc > 2) if (argc > 2)
{ {
if (strcmp(argv[2], "--no-service") == 0) if (strcmp(argv[2], "--no-service") == 0)
@ -272,32 +270,40 @@ void HandleArguments(int argc, char *argv[])
CreateBridge(); CreateBridge();
ExitProcess(0); ExitProcess(0);
} }
else if (strcmp(argv[1], "--version") == 0)
{
printf("%s\n", VER_VERSION_STR);
ExitProcess(0);
}
else if (strcmp(argv[1], "--help") == 0) else if (strcmp(argv[1], "--help") == 0)
{ {
printf("Usage:\n"); printf("Usage:\n"
printf(" %s [args]\n\n", argv[0]); " %s [args]\n"
"\n"
printf("Arguments:\n"); "Arguments:\n"
printf(" --help Show this help\n\n"); " --help Show this help\n"
"\n"
printf(" --install Install service\n"); " --version Show version\n"
printf(" This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n\n"); "\n"
" --install Install service\n"
printf(" --uninstall Uninstall service\n"); " This will copy the binary to C:\\windows\\bridge.exe and register it as a service\n"
printf(" This will remove the service and delete C:\\windows\\bridge.exe\n\n"); "\n"
" --uninstall Uninstall service\n"
printf(" --steam Reserved for Steam\n"); " This will remove the service and delete C:\\windows\\bridge.exe\n"
printf(" This will start the service and exit (used with bridge.sh)\n\n"); "\n"
" --steam Reserved for Steam\n"
printf(" --no-service Do not run as service\n"); " This will start the service and exit (used with bridge.sh)\n"
printf(" (only for --steam)\n\n"); "\n"
" --no-service Do not run as service\n"
printf(" --service Reserved for service\n\n"); " (only for --steam)\n"
"\n"
printf(" --rpc <dir> Set RPC_PATH environment variable\n"); " --service Reserved for service\n"
printf(" This is used to specify the directory where 'discord-ipc-0' is located\n\n"); "\n"
" --rpc <dir> Set RPC_PATH environment variable\n"
printf("Note: If no arguments are provided, the GUI will be shown instead\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); ExitProcess(0);
} }
} }
@ -305,7 +311,14 @@ void HandleArguments(int argc, char *argv[])
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
DetectWine(); DetectWine();
char *logFilePath = "C:\\windows\\logs\\bridge.log"; if (OSInfo.IsWine)
logFilePath = "C:\\windows\\logs\\bridge.log";
else
{
logFilePath = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH);
GetTempPath(MAX_PATH, logFilePath);
strcat_s(logFilePath, MAX_PATH, "bridge.log");
}
g_logFile = fopen(logFilePath, "w"); g_logFile = fopen(logFilePath, "w");
if (g_logFile == NULL) if (g_logFile == NULL)
{ {

9
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, 1, 0, 0
#define VER_VERSION_STR "1.1\0"

View File

@ -8,7 +8,6 @@ SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
void print(char const *fmt, ...); void print(char const *fmt, ...);
void CreateBridge(); void CreateBridge();
LPTSTR GetErrorMessage(); LPTSTR GetErrorMessage();
extern BOOL IsLinux;
void WINAPI ServiceCtrlHandler(DWORD CtrlCode) void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
{ {
@ -88,14 +87,6 @@ void InstallService(int ServiceStartType, LPCSTR Path)
{ {
print("Registering service\n"); 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); SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (schSCManager == NULL) if (schSCManager == NULL)
{ {