243 lines
6.5 KiB
Dart
243 lines
6.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:developer' as developer;
|
|
import 'dart:ffi';
|
|
import 'dart:io';
|
|
import 'dart:isolate';
|
|
import 'package:ffi/ffi.dart';
|
|
import 'package:win32/win32.dart';
|
|
import 'ipc_server.dart';
|
|
|
|
class WindowsIpcServer extends IpcServer {
|
|
int? _pipeHandle;
|
|
Timer? _ipcTimer;
|
|
|
|
@override
|
|
Future<void> start() async {
|
|
final pipeName = r'\\.\pipe\discord-ipc'.toNativeUtf16();
|
|
try {
|
|
_pipeHandle = CreateNamedPipe(
|
|
pipeName,
|
|
PIPE_ACCESS_DUPLEX,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
4096, // Output buffer size
|
|
4096, // Input buffer size
|
|
0, // Default timeout
|
|
nullptr, // Security attributes
|
|
);
|
|
|
|
if (_pipeHandle == INVALID_HANDLE_VALUE) {
|
|
final error = GetLastError();
|
|
throw Exception('Failed to create named pipe: error code $error');
|
|
}
|
|
|
|
developer.log(
|
|
'IPC named pipe created at \\\\.\\pipe\\discord-ipc',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
|
|
// Start listening for connections in a separate isolate
|
|
_listenWindowsIpc();
|
|
} finally {
|
|
free(pipeName);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> stop() async {
|
|
for (var socket in sockets) {
|
|
try {
|
|
socket.close();
|
|
} catch (e) {
|
|
developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix);
|
|
}
|
|
}
|
|
sockets.clear();
|
|
|
|
if (_pipeHandle != null) {
|
|
try {
|
|
CloseHandle(_pipeHandle!);
|
|
} catch (e) {
|
|
developer.log('Error closing named pipe: $e', name: kRpcIpcLogPrefix);
|
|
}
|
|
_pipeHandle = null;
|
|
}
|
|
_ipcTimer?.cancel();
|
|
}
|
|
|
|
// Listen for Windows IPC connections in an isolate
|
|
void _listenWindowsIpc() async {
|
|
final receivePort = ReceivePort();
|
|
await Isolate.spawn(_windowsIpcIsolate, receivePort.sendPort);
|
|
|
|
receivePort.listen((message) {
|
|
if (message is int) {
|
|
final socketWrapper = WindowsIpcSocketWrapper(message);
|
|
addSocket(socketWrapper);
|
|
developer.log(
|
|
'New IPC connection on named pipe',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
_handleWindowsIpcData(socketWrapper);
|
|
start(); // Create new pipe for next connection
|
|
}
|
|
});
|
|
}
|
|
|
|
static void _windowsIpcIsolate(SendPort sendPort) {
|
|
while (true) {
|
|
final pipeHandle = CreateNamedPipe(
|
|
r'\\.\pipe\discord-ipc'.toNativeUtf16(),
|
|
PIPE_ACCESS_DUPLEX,
|
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
|
PIPE_UNLIMITED_INSTANCES,
|
|
4096,
|
|
4096,
|
|
0,
|
|
nullptr,
|
|
);
|
|
if (pipeHandle == INVALID_HANDLE_VALUE) {
|
|
developer.log(
|
|
'Failed to create named pipe: ${GetLastError()}',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
break;
|
|
}
|
|
final connected = ConnectNamedPipe(pipeHandle, nullptr);
|
|
if (connected != 0 || GetLastError() == ERROR_PIPE_CONNECTED) {
|
|
sendPort.send(pipeHandle);
|
|
}
|
|
// Avoid tight loop
|
|
sleep(Duration(milliseconds: 100));
|
|
}
|
|
}
|
|
|
|
// Handle Windows IPC data
|
|
void _handleWindowsIpcData(WindowsIpcSocketWrapper socket) async {
|
|
final startTime = DateTime.now();
|
|
final buffer = malloc.allocate<BYTE>(4096);
|
|
final bytesRead = malloc.allocate<DWORD>(4);
|
|
try {
|
|
while (socket.pipeHandle != null) {
|
|
final readStart = DateTime.now();
|
|
final success = ReadFile(
|
|
socket.pipeHandle!,
|
|
buffer.cast(),
|
|
4096,
|
|
bytesRead,
|
|
nullptr,
|
|
);
|
|
final readDuration =
|
|
DateTime.now().difference(readStart).inMicroseconds;
|
|
developer.log(
|
|
'ReadFile took $readDuration microseconds',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
|
|
if (success == FALSE && GetLastError() != ERROR_MORE_DATA) {
|
|
developer.log(
|
|
'IPC read error: ${GetLastError()}',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
socket.close();
|
|
break;
|
|
}
|
|
|
|
final data = buffer.asTypedList(0);
|
|
socket.addData(data);
|
|
final packets = socket.readPackets();
|
|
for (final packet in packets) {
|
|
handlePacket?.call(socket, packet, {});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
developer.log('IPC data error: $e', name: kRpcIpcLogPrefix);
|
|
socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString());
|
|
} finally {
|
|
malloc.free(buffer);
|
|
malloc.free(bytesRead);
|
|
final totalDuration = DateTime.now().difference(startTime).inMicroseconds;
|
|
developer.log(
|
|
'handleWindowsIpcData took $totalDuration microseconds',
|
|
name: kRpcIpcLogPrefix,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class WindowsIpcSocketWrapper extends IpcSocketWrapper {
|
|
final int? pipeHandle;
|
|
|
|
WindowsIpcSocketWrapper(this.pipeHandle);
|
|
|
|
@override
|
|
void send(Map<String, dynamic> msg) {
|
|
developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix);
|
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg);
|
|
final buffer = malloc.allocate<BYTE>(packet.length);
|
|
buffer.asTypedList(packet.length).setAll(0, packet);
|
|
final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes
|
|
try {
|
|
WriteFile(
|
|
pipeHandle!,
|
|
buffer.cast(),
|
|
packet.length,
|
|
bytesWritten,
|
|
nullptr,
|
|
);
|
|
} finally {
|
|
malloc.free(buffer);
|
|
malloc.free(bytesWritten);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void sendPong(dynamic data) {
|
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {});
|
|
final buffer = malloc.allocate<BYTE>(packet.length);
|
|
buffer.asTypedList(packet.length).setAll(0, packet);
|
|
final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes
|
|
try {
|
|
WriteFile(
|
|
pipeHandle!,
|
|
buffer.cast(),
|
|
packet.length,
|
|
bytesWritten,
|
|
nullptr,
|
|
);
|
|
} finally {
|
|
malloc.free(buffer);
|
|
malloc.free(bytesWritten);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
if (pipeHandle != null) {
|
|
CloseHandle(pipeHandle!);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void closeWithCode(int code, [String message = '']) {
|
|
final closeData = {'code': code, 'message': message};
|
|
final packet = IpcServer.encodeIpcPacket(IpcTypes.close, closeData);
|
|
final buffer = malloc.allocate<BYTE>(packet.length);
|
|
buffer.asTypedList(packet.length).setAll(0, packet);
|
|
final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes
|
|
try {
|
|
WriteFile(
|
|
pipeHandle!,
|
|
buffer.cast(),
|
|
packet.length,
|
|
bytesWritten,
|
|
nullptr,
|
|
);
|
|
} finally {
|
|
malloc.free(buffer);
|
|
malloc.free(bytesWritten);
|
|
}
|
|
CloseHandle(pipeHandle!);
|
|
}
|
|
}
|