Implement experimental bridge for kvm virtual machines

This commit is contained in:
EnderIce2 2024-05-30 05:32:56 +03:00
parent 20728818d2
commit 3ac5605a52
Signed by untrusted user who does not match committer: enderice2
GPG Key ID: EACC3AD603BAB4DD
9 changed files with 401 additions and 75 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -12,6 +12,7 @@ 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
gcc kvm.c -o build/rpc-bridge.elf
%.o: %.c
$(info Compiling $<)

298
bridge.c
View File

@ -4,6 +4,8 @@
#include <assert.h>
#include <stdio.h>
#include "bridge.h"
#define __linux_read 3
#define __linux_write 4
#define __linux_open 5
@ -64,7 +66,7 @@ void print(char const *fmt, ...);
LPTSTR GetErrorMessage();
extern BOOL RunningAsService;
BOOL RetryNewConnection;
BOOL IsLinux;
OS_INFO OSInfo = {0};
HANDLE hOut = 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)
{
if (IsLinux)
if (OSInfo.IsLinux)
return linux_syscall(__linux_read, fd, buf, count, 0, 0, 0);
else
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)
{
if (IsLinux)
if (OSInfo.IsLinux)
return linux_syscall(__linux_write, fd, buf, count, 0, 0, 0);
else
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)
{
if (IsLinux)
if (OSInfo.IsLinux)
return linux_syscall(__linux_open, pathname, flags, mode, 0, 0, 0);
else
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)
{
if (IsLinux)
if (OSInfo.IsLinux)
return linux_syscall(__linux_close, fd, 0, 0, 0, 0, 0);
else
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)
{
assert(IsLinux);
assert(OSInfo.IsLinux);
return linux_syscall(__linux_mmap2, addr, length, prot, flags, fd, offset);
}
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);
}
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);
}
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);
else
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)
{
if (IsLinux)
if (OSInfo.IsLinux)
return linux_syscall(__linux_connect, s, name, namelen, 0, 0, 0);
else
return darwin_syscall(__darwin_connect, s, name, namelen, 0, 0, 0);
@ -174,7 +176,7 @@ char *native_getenv(const char *name)
if (ret != 0)
return lpBuffer;
if (!IsLinux)
if (!OSInfo.IsLinux)
{
char *value = getenv(name);
if (value == NULL)
@ -251,9 +253,9 @@ void ConnectToSocket(int fd)
{
print("Connecting to socket\n");
const char *runtime;
if (IsLinux)
if (OSInfo.IsLinux)
runtime = native_getenv("XDG_RUNTIME_DIR");
else
else if (OSInfo.IsDarwin)
{
runtime = native_getenv("TMPDIR");
if (runtime == NULL)
@ -278,6 +280,11 @@ void ConnectToSocket(int fd)
}
}
}
else
{
print("Unsupported OS\n");
ExitProcess(1);
}
print("IPC directory: %s\n", runtime);
@ -302,7 +309,7 @@ void ConnectToSocket(int fd)
print("Connecting to %s\n", pipePath);
if (IsLinux)
if (OSInfo.IsLinux)
{
unsigned long socketArgs[] = {
(unsigned long)fd,
@ -480,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()
{
LPCTSTR lpszPipename = TEXT("\\\\.\\pipe\\discord-ipc-0");
@ -533,43 +696,87 @@ NewConnection:
print("Pipe connected\n");
int fd;
if (IsLinux)
if (!OSInfo.IsWine)
{
unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
(unsigned long)SOCK_STREAM,
0};
fd = sys_socketcall(SYS_SOCKET, socketArgs);
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
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
print("Socket %d created\n", fd);
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);
if (OSInfo.IsLinux)
{
unsigned long socketArgs[] = {
(unsigned long)AF_UNIX,
(unsigned long)SOCK_STREAM,
0};
fd = sys_socketcall(SYS_SOCKET, socketArgs);
}
else
fd = sys_socket(AF_UNIX, SOCK_STREAM, 0);
print("Socket %d created\n", fd);
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);
}
ConnectToSocket(fd);
print("Connected to Discord\n");
bridge_thread bt = {fd, hPipe};
hIn = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferInThread,
(LPVOID)&bt,
0, NULL);
hOut = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)PipeBufferOutThread,
(LPVOID)&bt,
0, NULL);
}
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)
@ -600,7 +807,8 @@ NewConnection:
print("Failed to terminate thread: %s\n",
GetErrorMessage());
sys_close(fd);
if (OSInfo.IsWine)
sys_close(fd);
CloseHandle(hOut);
CloseHandle(hIn);
CloseHandle(hPipe);

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

15
gui.c
View File

@ -4,6 +4,7 @@
#include <assert.h>
#include <stdio.h>
#include "bridge.h"
#include "resource.h"
/**
@ -17,7 +18,8 @@ void print(char const *fmt, ...);
void InstallService(int ServiceStartType, LPCSTR Path);
void RemoveService();
void CreateBridge();
extern BOOL IsLinux;
extern OS_INFO OSInfo;
extern char *logFilePath;
HWND hwnd = NULL;
HANDLE hBridge = NULL;
@ -49,7 +51,7 @@ VOID HandleStartButton(BOOL Silent)
return;
}
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (hSCManager == NULL)
{
print("OpenSCManager failed: %s\n", GetErrorMessage());
@ -180,7 +182,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
HandleRemoveButton();
break;
case IDM_VIEW_LOG:
ShellExecute(NULL, "open", "C:\\windows\\notepad.exe", "C:\\windows\\logs\\bridge.log", NULL, SW_SHOW);
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);
@ -229,13 +231,6 @@ VOID SetButtonStyles(INT *btnStartStyle, INT *btnRemoveStyle, INT *btnInstallSty
*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);

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__

28
main.c
View File

@ -3,10 +3,12 @@
#include <assert.h>
#include <stdio.h>
#include "bridge.h"
#include "resource.h"
FILE *g_logFile = NULL;
BOOL RunningAsService = FALSE;
char *logFilePath = NULL;
void CreateGUI();
void CreateBridge();
@ -15,7 +17,7 @@ void ServiceMain(int argc, char *argv[]);
void InstallService(int ServiceStartType, LPCSTR Path);
char *native_getenv(const char *name);
void RemoveService();
extern BOOL IsLinux;
extern OS_INFO OSInfo;
LPTSTR GetErrorMessage()
{
@ -57,11 +59,8 @@ void DetectWine()
}
if (!GetProcAddress(hNTdll, "wine_get_version"))
{
MessageBox(NULL, "This program is only intended to run under Wine.",
GetErrorMessage(), MB_OK | MB_ICONINFORMATION);
ExitProcess(1);
}
return;
OSInfo.IsWine = TRUE;
static void(CDECL * wine_get_host_version)(const char **sysname, const char **release);
wine_get_host_version = (void *)GetProcAddress(hNTdll, "wine_get_host_version");
@ -78,7 +77,8 @@ void DetectWine()
ExitProcess(1);
}
IsLinux = strcmp(__sysname, "Linux") == 0;
OSInfo.IsLinux = strcmp(__sysname, "Linux") == 0;
OSInfo.IsDarwin = strcmp(__sysname, "Darwin") == 0;
}
void print(char const *fmt, ...)
@ -113,14 +113,13 @@ void HandleArguments(int argc, char *argv[])
}
else if (strcmp(argv[1], "--steam") == 0)
{
assert(OSInfo.IsWine == TRUE);
/* 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)
@ -312,7 +311,14 @@ void HandleArguments(int argc, char *argv[])
int main(int argc, char *argv[])
{
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");
if (g_logFile == NULL)
{

View File

@ -8,7 +8,6 @@ SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
void print(char const *fmt, ...);
void CreateBridge();
LPTSTR GetErrorMessage();
extern BOOL IsLinux;
void WINAPI ServiceCtrlHandler(DWORD CtrlCode)
{
@ -88,14 +87,6 @@ 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)
{