From ba5d670d1aeb6e919c1e2a7e12c0049e2e43deb4 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:54:23 +0200 Subject: [PATCH] Convert TCP networking to sync and binary, add DHCP and IP stack init on boot --- Kernel.cs | 59 ++++++- Networking/TcpConnection.cs | 3 +- Networking/TcpRpcBase.cs | 194 ++++++++++++++++++------ Networking/TcpRpcClient.cs | 33 ++-- Networking/TcpRpcServer.cs | 72 +++------ Processes/RemoteDesktopProcess.cs | 122 ++++++++++++--- UI/GUI/Rendering/NetworkRenderSource.cs | 2 +- build-and-run.ps1 | 3 +- 8 files changed, 345 insertions(+), 143 deletions(-) diff --git a/Kernel.cs b/Kernel.cs index a1c986d..7cc52b1 100644 --- a/Kernel.cs +++ b/Kernel.cs @@ -1,5 +1,11 @@ global using Sys = Cosmos.Kernel.System; +using Cosmos.Kernel.HAL.Interfaces.Devices; +using Cosmos.Kernel.System.Network; +using Cosmos.Kernel.System.Network.Config; +using Cosmos.Kernel.System.Network.IPv4.UDP.DHCP; +using Cosmos.Kernel.System.Timer; + using RemSox.Cryptography; using RemSox.Processes; using RemSox.Processing; @@ -11,7 +17,7 @@ namespace RemSox; /// /// Main kernel class - inherits from Cosmos.Kernel.System.Kernel. /// -public class Kernel : Sys.Kernel +public partial class Kernel : Sys.Kernel { protected override void BeforeRun() { @@ -31,6 +37,57 @@ public class Kernel : Sys.Kernel new StopRemoteDesktopCommand() ]); + // Xml serializer test + Console.WriteLine("[Kernel] Testing XML serialization..."); + Thread.Sleep(1000); + System.Drawing.Point point = new(10, 20); + + string xml = $"{point.X}{point.Y}"; + Console.WriteLine("[Kernel] XML Serialization Test: " + xml); + + Console.WriteLine("[TCP Server] Starting..."); + + INetworkDevice? device = NetworkManager.PrimaryDevice; + if (device == null) + { + Console.WriteLine("[ERROR] No network device."); + return; + } + + Console.WriteLine("[TCP Server] Waiting for link..."); + int attempts = 0; + while (!device.LinkUp && attempts < 30) + { + TimerManager.Wait(100); + attempts++; + } + + if (!device.Ready) + { + Console.WriteLine("[ERROR] Device not ready."); + return; + } + + Console.WriteLine("[TCP Server] Initializing network stack..."); + NetworkStack.Initialize(); + + Console.WriteLine("[TCP Server] Running DHCP..."); + DHCPClient dhcp = new DHCPClient(); + if (dhcp.SendDiscoverPacket() == -1) + { + Console.WriteLine("[ERROR] DHCP failed."); + return; + } + + IPConfig? config = NetworkConfigManager.Get(device); + if (config?.IPAddress == null) + { + Console.WriteLine("[ERROR] No IP from DHCP."); + return; + } + + Console.WriteLine("[TCP Server] IP: " + config.IPAddress); + Sys.Graphics.Canvas canvas = Sys.Graphics.FullScreenCanvas.GetFullScreenCanvas(); Sys.Mouse.MouseManager.Initialize(); diff --git a/Networking/TcpConnection.cs b/Networking/TcpConnection.cs index 8a28cb0..4287569 100644 --- a/Networking/TcpConnection.cs +++ b/Networking/TcpConnection.cs @@ -7,12 +7,11 @@ public class TcpConnection(TcpClient client) : IDisposable { public TcpClient Client { get; } = client; public NetworkStream Stream { get; } = client.GetStream(); - public SemaphoreSlim SendLock { get; } = new(1, 1); + public object SendLock { get; } = new(); public void Dispose() { Stream.Dispose(); Client.Dispose(); - SendLock.Dispose(); } } \ No newline at end of file diff --git a/Networking/TcpRpcBase.cs b/Networking/TcpRpcBase.cs index 818578c..cb41baf 100644 --- a/Networking/TcpRpcBase.cs +++ b/Networking/TcpRpcBase.cs @@ -2,9 +2,8 @@ using RemSox.Networking; using System.Buffers.Binary; using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; using System.Net.Sockets; -using System.Text.Json; +using System.Text; public abstract class TcpRpcBase(IPacketCrypto? crypto = null) { @@ -15,57 +14,65 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) // Security Guard: Prevent OOM/DoS via oversized length headers (Default: 32MB) protected const int MaxMessageSize = 32 * 1024 * 1024; - /// - /// Base message registration for handlers tracking the sending connection. - /// - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - public void ListenTo(string type, Func handler) + public void ListenTo(string type, Func handler) + { + handlers[type] = async (conn, msg) => await handler(conn, msg.Payload); + } + + public void ListenTo(string type, Func handler) + { + handlers[type] = async (conn, msg) => await handler(msg.Payload); + } + + public void RespondTo(string type, Func> handler) { handlers[type] = async (conn, msg) => { - T data = JsonSerializer.Deserialize(msg.Payload)!; - await handler(conn, data); + byte[] res = await handler(msg.Payload); + SendRaw(conn, new TcpMessage + { + Type = type, + RequestId = msg.RequestId, + Payload = res + }); }; } - /// - /// Overloaded listener where connection mapping can be ignored (Convenient for Clients). - /// - public void ListenTo(string type, Func handler) + protected void SendRaw(TcpConnection conn, TcpMessage msg) { - ListenTo(type, async (_, data) => await handler(data)); - } - - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - protected async Task SendRaw(TcpConnection conn, TcpMessage msg) - { - byte[] data = JsonSerializer.SerializeToUtf8Bytes(msg); + byte[] rawData = SerializeMessage(msg); if (crypto is not null) { - data = crypto.Encrypt(data); + rawData = crypto.Encrypt(rawData); } - // Allocate a single contiguous frame buffer to prevent inter-thread fragmentation - // and eliminate redundant Socket Write Syscalls. - byte[] packet = new byte[4 + data.Length]; - BinaryPrimitives.WriteInt32LittleEndian(packet, data.Length); - Array.Copy(data, 0, packet, 4, data.Length); + byte[] packet = new byte[4 + rawData.Length]; + BinaryPrimitives.WriteInt32LittleEndian(packet, rawData.Length); + Array.Copy(rawData, 0, packet, 4, rawData.Length); - // Enforce sequence safety across multiple threads pushing data out of a singular stream - await conn.SendLock.WaitAsync(); - try + lock (conn.SendLock) { - await conn.Stream.WriteAsync(packet); - } - finally - { - _ = conn.SendLock.Release(); + conn.Stream.Write(packet, 0, packet.Length); } } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - protected async Task HandleConnection(TcpConnection conn, CancellationToken token = default) + private static void ReadExact(NetworkStream stream, byte[] buffer, int offset, int count) + { + int totalRead = 0; + while (totalRead < count) + { + int read = stream.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + { + Thread.Sleep(10); + continue; + } + totalRead += read; + } + } + + protected void HandleConnection(TcpConnection conn, CancellationToken token = default) { NetworkStream stream = conn.Stream; byte[] lengthBytes = new byte[4]; @@ -74,32 +81,41 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) { while (!token.IsCancellationRequested && conn.Client.Connected) { - // 1. Frame Length Read - await stream.ReadExactlyAsync(lengthBytes, token); + Console.WriteLine("[TcpRpc] Waiting for incoming packet..."); + + ReadExact(stream, lengthBytes, 0, 4); int length = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes); - // DoS Payload Protection Check if (length is <= 0 or > MaxMessageSize) { - throw new InvalidDataException($"Protocol violation: Received packet length of {length} bytes exceeds limits."); + Console.WriteLine($"[TcpRpc] Invalid packet length: {length}"); + Thread.Sleep(10); + continue; } - // 2. Body Payload Read + Console.WriteLine($"[TcpRpc] Incoming packet length: {length} bytes"); + byte[] buffer = new byte[length]; - await stream.ReadExactlyAsync(buffer, token); + ReadExact(stream, buffer, 0, length); + + Console.WriteLine($"[TcpRpc] Incoming packet payload: {BitConverter.ToString(buffer)}"); if (crypto is not null) { buffer = crypto.Decrypt(buffer); } - TcpMessage? msg = JsonSerializer.Deserialize(buffer); + Console.WriteLine($"[TcpRpc] Decrypted packet payload: {BitConverter.ToString(buffer)}"); + + TcpMessage? msg = DeserializeMessage(buffer); if (msg is null) { + Console.WriteLine("[TcpRpc] Failed to deserialize incoming message."); continue; } - // 3. Response Handler Check + Console.WriteLine($"[TcpRpc] Incoming message type: {msg.Type}, requestId: {msg.RequestId}, payload length: {msg.Payload.Length} bytes"); + if (pendingRequests.TryGetValue(msg.RequestId, out TaskCompletionSource? tcs)) { _ = tcs.TrySetResult(msg.Payload); @@ -107,11 +123,8 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) continue; } - // 4. Inbound Router Handler if (handlers.TryGetValue(msg.Type, out Func? handler)) { - // Decouple handler execution to the ThreadPool so slow business logic - // doesn't bottleneck packet processing from the network stream interface. _ = Task.Run(async () => { try @@ -120,7 +133,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) } catch { - // Operational log placement here for user exceptions inside delegates } }, token); } @@ -128,7 +140,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) } catch { - // Explicitly handles natural dropping scenarios cleanly } finally { @@ -137,5 +148,90 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null) } } + private static byte[] SerializeMessage(TcpMessage msg) + { + byte[] typeBytes = Encoding.UTF8.GetBytes(msg.Type); + byte[] requestIdBytes = Encoding.UTF8.GetBytes(msg.RequestId); + + byte[] data = new byte[4 + typeBytes.Length + 4 + requestIdBytes.Length + 4 + msg.Payload.Length]; + int offset = 0; + + WriteInt32(data, ref offset, typeBytes.Length); + typeBytes.CopyTo(data, offset); + offset += typeBytes.Length; + + WriteInt32(data, ref offset, requestIdBytes.Length); + requestIdBytes.CopyTo(data, offset); + offset += requestIdBytes.Length; + + WriteInt32(data, ref offset, msg.Payload.Length); + msg.Payload.CopyTo(data, offset); + + return data; + } + + private static TcpMessage? DeserializeMessage(byte[] data) + { + int offset = 0; + if (offset + 4 > data.Length) + { + return null; + } + + int typeLen = ReadInt32(data, ref offset); + if (offset + typeLen > data.Length) + { + return null; + } + + string type = Encoding.UTF8.GetString(data, offset, typeLen); + offset += typeLen; + + if (offset + 4 > data.Length) + { + return null; + } + + int requestIdLen = ReadInt32(data, ref offset); + if (offset + requestIdLen > data.Length) + { + return null; + } + + string requestId = Encoding.UTF8.GetString(data, offset, requestIdLen); + offset += requestIdLen; + + if (offset + 4 > data.Length) + { + return null; + } + + int payloadLen = ReadInt32(data, ref offset); + if (offset + payloadLen > data.Length) + { + return null; + } + + byte[] payload = new byte[payloadLen]; + Array.Copy(data, offset, payload, 0, payloadLen); + + return new TcpMessage { Type = type, RequestId = requestId, Payload = payload }; + } + + private static void WriteInt32(byte[] data, ref int offset, int value) + { + data[offset++] = (byte)(value & 0xFF); + data[offset++] = (byte)((value >> 8) & 0xFF); + data[offset++] = (byte)((value >> 16) & 0xFF); + data[offset++] = (byte)((value >> 24) & 0xFF); + } + + private static int ReadInt32(byte[] data, ref int offset) + { + int val = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + offset += 4; + return val; + } + protected abstract void OnConnectionClosed(TcpConnection conn); } \ No newline at end of file diff --git a/Networking/TcpRpcClient.cs b/Networking/TcpRpcClient.cs index c2f02de..3126905 100644 --- a/Networking/TcpRpcClient.cs +++ b/Networking/TcpRpcClient.cs @@ -1,6 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.Net; using System.Net.Sockets; -using System.Text.Json; namespace RemSox.Networking; @@ -8,19 +7,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) { private TcpConnection? connection; - public async Task ConnectAsync(string host, int port) + public void Connect(IPAddress ipAddress, int port) { TcpClient client = new(); - await client.ConnectAsync(host, port); + client.Connect(ipAddress, port); connection = new TcpConnection(client); - // Run network monitoring task loop background-detached - _ = HandleConnection(connection); + _ = Task.Run(() => HandleConnection(connection)); } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - public async Task RequestAsync(string type, TReq request, TimeSpan timeout = default) + public async Task RequestAsync(string type, byte[] request, TimeSpan timeout = default) { if (connection is null) { @@ -29,7 +26,7 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) if (timeout == default) { - timeout = TimeSpan.FromSeconds(30); // Default fallback timeout + timeout = TimeSpan.FromSeconds(30); } string requestId = Guid.NewGuid().ToString(); @@ -38,19 +35,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) try { - await SendRaw(connection, new TcpMessage + SendRaw(connection, new TcpMessage { Type = type, RequestId = requestId, - Payload = JsonSerializer.SerializeToUtf8Bytes(request) + Payload = request }); - // Enforce async timeout safety to prevent permanent dictionary leaks on dropped calls using CancellationTokenSource timeoutCts = new(timeout); - await using (timeoutCts.Token.Register(() => tcs.TrySetCanceled())) + using (timeoutCts.Token.Register(() => tcs.TrySetCanceled())) { - byte[] responseBytes = await tcs.Task; - return JsonSerializer.Deserialize(responseBytes)!; + return await tcs.Task; } } finally @@ -59,19 +54,18 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) } } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - public Task SendAsync(string type, T data) + public void Send(string type, byte[] data) { if (connection is null) { throw new InvalidOperationException("Client not connected."); } - return SendRaw(connection, new TcpMessage + SendRaw(connection, new TcpMessage { Type = type, RequestId = Guid.NewGuid().ToString(), - Payload = JsonSerializer.SerializeToUtf8Bytes(data) + Payload = data }); } @@ -82,7 +76,6 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) connection = null; } - // Fail-fast all lingering tasks waiting on an dead connection loop foreach (TaskCompletionSource req in pendingRequests.Values) { _ = req.TrySetException(new SocketException((int)SocketError.ConnectionReset)); diff --git a/Networking/TcpRpcServer.cs b/Networking/TcpRpcServer.cs index 6e25b23..2d9f41a 100644 --- a/Networking/TcpRpcServer.cs +++ b/Networking/TcpRpcServer.cs @@ -1,19 +1,17 @@ using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; -using System.Text.Json; namespace RemSox.Networking; public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) { - // Fix: Using ConcurrentDictionary to prevent collection errors during structural additions/prunings private readonly ConcurrentDictionary connections = new(); + private readonly ConcurrentDictionary activeEndpoints = new(); private TcpListener? listener; private CancellationTokenSource? cts; - public async Task StartAsync(int port, CancellationToken token = default) + public void StartAsync(int port, CancellationToken token = default) { cts = CancellationTokenSource.CreateLinkedTokenSource(token); @@ -24,17 +22,25 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) { while (!cts.IsCancellationRequested) { - TcpClient client = await listener.AcceptTcpClientAsync(cts.Token); + TcpClient client = listener.AcceptTcpClient(); + string ep = client.Client.RemoteEndPoint?.ToString() ?? ""; + if (!activeEndpoints.TryAdd(ep, 0)) + { + client.Close(); + Thread.Sleep(10); + continue; + } + + Console.WriteLine($"Client connected: {ep}"); TcpConnection conn = new(client); _ = connections.TryAdd(conn, 0); - _ = HandleConnection(conn, cts.Token); + _ = Task.Run(() => HandleConnection(conn, cts.Token)); } } catch (OperationCanceledException) { - // Expected shutdown scenario } } @@ -51,62 +57,26 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto) connections.Clear(); } - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - public void RespondTo(string type, Func> handler) + public void SendRawToAll(string type, byte[] payload) { - handlers[type] = async (conn, msg) => - { - TReq req = JsonSerializer.Deserialize(msg.Payload)!; - TRes res = await handler(req); - - await SendRaw(conn, new TcpMessage - { - Type = type, - RequestId = msg.RequestId, - Payload = JsonSerializer.SerializeToUtf8Bytes(res) - }); - }; - } - - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")] - public Task SendToAll(string type, T data) - { - byte[] payloadBytes = JsonSerializer.SerializeToUtf8Bytes(data); - List sendTasks = []; - foreach (TcpConnection conn in connections.Keys) { - sendTasks.Add(SendRaw(conn, new TcpMessage - { - Type = type, - RequestId = Guid.NewGuid().ToString(), - Payload = payloadBytes - })); - } - - return Task.WhenAll(sendTasks); - } - - /// Broadcasts raw binary payload to all connected clients. - public Task SendRawToAll(string type, byte[] payload) - { - List sendTasks = []; - - foreach (TcpConnection conn in connections.Keys) - { - sendTasks.Add(SendRaw(conn, new TcpMessage + SendRaw(conn, new TcpMessage { Type = type, RequestId = Guid.NewGuid().ToString(), Payload = payload - })); + }); } - - return Task.WhenAll(sendTasks); } protected override void OnConnectionClosed(TcpConnection conn) { _ = connections.TryRemove(conn, out _); + + string ep = conn.Client.Client.RemoteEndPoint?.ToString() ?? ""; + _ = activeEndpoints.TryRemove(ep, out _); + + Console.WriteLine($"Client disconnected: {ep}"); } } \ No newline at end of file diff --git a/Processes/RemoteDesktopProcess.cs b/Processes/RemoteDesktopProcess.cs index 559a840..10d8daa 100644 --- a/Processes/RemoteDesktopProcess.cs +++ b/Processes/RemoteDesktopProcess.cs @@ -7,6 +7,8 @@ using RemSox.UI; using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.Windows; +using System.Text; + namespace RemSox.Processes; internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") @@ -17,12 +19,6 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") public int Port { get; private set; } - // Remote input message record types (JSON-serialized over TCP) - private sealed record MouseMoveMsg(int X, int Y); - private sealed record MouseButtonMsg(int X, int Y, string Button); - private sealed record MouseWheelMsg(int X, int Y, int Delta); - private sealed record KeyEventMsg(int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed); - internal override void Start(string[] args) { if (args.Length == 0 || !int.TryParse(args[0], out int port) || port <= 0 || port > 65535) @@ -37,7 +33,7 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") server = new TcpRpcServer(); networkSource = new NetworkRenderSource(server); - server.ListenTo("SyncRequest", async _ => + server.ListenTo("SyncRequest", async _ => { Canvas canvas = FullScreenCanvas.GetFullScreenCanvas(); RenderCommand screenInfo = new() @@ -88,35 +84,37 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") return; } - server.ListenTo("MouseMove", async (msg) => + server.ListenTo("MouseMove", async (payload) => { + var msg = DeserializeMouseMove(payload); WindowManager.EnqueueMouseEvent(MouseEvent.Move(msg.X, msg.Y)); }); - server.ListenTo("MouseDown", async (msg) => + server.ListenTo("MouseDown", async (payload) => { - MouseButton button = ParseButton(msg.Button); - WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, button)); + var msg = DeserializeMouseButton(payload); + WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, ParseButton(msg.Button))); }); - server.ListenTo("MouseUp", async (msg) => + server.ListenTo("MouseUp", async (payload) => { - MouseButton button = ParseButton(msg.Button); - WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, button)); + var msg = DeserializeMouseButton(payload); + WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, ParseButton(msg.Button))); }); - server.ListenTo("MouseWheel", async (msg) => + server.ListenTo("MouseWheel", async (payload) => { + var msg = DeserializeMouseWheel(payload); WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(msg.X, msg.Y, msg.Delta)); }); - server.ListenTo("KeyEvent", async (msg) => + server.ListenTo("KeyEvent", async (payload) => { + var msg = DeserializeKeyEvent(payload); ConsoleKeyEx key = (ConsoleKeyEx)msg.Key; char keyChar = msg.KeyChar.Length > 0 ? msg.KeyChar[0] : '\0'; - bool isPressed = msg.Pressed; - KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, isPressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break); + KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, msg.Pressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break); WindowManager.EnqueueKeyEvent(keyEvent); }); } @@ -131,4 +129,92 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") _ => MouseButton.None }; } + + private static byte[] SerializeMouseMove(int x, int y) + { + byte[] data = new byte[8]; + WriteInt32(data, 0, x); + WriteInt32(data, 4, y); + return data; + } + + private static (int X, int Y) DeserializeMouseMove(byte[] data) + { + return (ReadInt32(data, 0), ReadInt32(data, 4)); + } + + private static byte[] SerializeMouseButton(int x, int y, string button) + { + byte[] buttonBytes = Encoding.UTF8.GetBytes(button); + byte[] data = new byte[8 + 4 + buttonBytes.Length]; + WriteInt32(data, 0, x); + WriteInt32(data, 4, y); + WriteInt32(data, 8, buttonBytes.Length); + buttonBytes.CopyTo(data, 12); + return data; + } + + private static (int X, int Y, string Button) DeserializeMouseButton(byte[] data) + { + int x = ReadInt32(data, 0); + int y = ReadInt32(data, 4); + int len = ReadInt32(data, 8); + string button = Encoding.UTF8.GetString(data, 12, len); + return (x, y, button); + } + + private static byte[] SerializeMouseWheel(int x, int y, int delta) + { + byte[] data = new byte[12]; + WriteInt32(data, 0, x); + WriteInt32(data, 4, y); + WriteInt32(data, 8, delta); + return data; + } + + private static (int X, int Y, int Delta) DeserializeMouseWheel(byte[] data) + { + return (ReadInt32(data, 0), ReadInt32(data, 4), ReadInt32(data, 8)); + } + + private static byte[] SerializeKeyEvent(int key, string keyChar, bool shift, bool alt, bool control, bool pressed) + { + byte[] charBytes = Encoding.UTF8.GetBytes(keyChar); + byte[] data = new byte[4 + 4 + charBytes.Length + 4]; + WriteInt32(data, 0, key); + WriteInt32(data, 4, charBytes.Length); + charBytes.CopyTo(data, 8); + int offset = 8 + charBytes.Length; + data[offset++] = shift ? (byte)1 : (byte)0; + data[offset++] = alt ? (byte)1 : (byte)0; + data[offset++] = control ? (byte)1 : (byte)0; + data[offset] = pressed ? (byte)1 : (byte)0; + return data; + } + + private static (int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed) DeserializeKeyEvent(byte[] data) + { + int key = ReadInt32(data, 0); + int charLen = ReadInt32(data, 4); + string keyChar = Encoding.UTF8.GetString(data, 8, charLen); + int offset = 8 + charLen; + bool shift = data[offset] != 0; + bool alt = data[offset + 1] != 0; + bool control = data[offset + 2] != 0; + bool pressed = data[offset + 3] != 0; + return (key, keyChar, shift, alt, control, pressed); + } + + private static void WriteInt32(byte[] data, int offset, int value) + { + data[offset] = (byte)(value & 0xFF); + data[offset + 1] = (byte)((value >> 8) & 0xFF); + data[offset + 2] = (byte)((value >> 16) & 0xFF); + data[offset + 3] = (byte)((value >> 24) & 0xFF); + } + + private static int ReadInt32(byte[] data, int offset) + { + return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + } } diff --git a/UI/GUI/Rendering/NetworkRenderSource.cs b/UI/GUI/Rendering/NetworkRenderSource.cs index 663bf42..3ebd4da 100644 --- a/UI/GUI/Rendering/NetworkRenderSource.cs +++ b/UI/GUI/Rendering/NetworkRenderSource.cs @@ -11,7 +11,7 @@ public sealed class NetworkRenderSource(TcpRpcServer server) : IRenderSource foreach (RenderCommand cmd in commands) { byte[] data = cmd.ToBytes(); - _ = server.SendRawToAll(MessageType, data); + server.SendRawToAll(MessageType, data); } } diff --git a/build-and-run.ps1 b/build-and-run.ps1 index cb4723a..04bb896 100644 --- a/build-and-run.ps1 +++ b/build-and-run.ps1 @@ -3,4 +3,5 @@ Set-Location $PSScriptRoot cosmos build if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -cosmos run \ No newline at end of file +#cosmos run +bash -c "qemu-system-x86_64 -M q35 -cpu max -m 512M -serial stdio -cdrom ./output-x64/RemSox.iso -vga std -nic user,hostfwd=tcp::9999-:9999" \ No newline at end of file