diff --git a/SrcMod/Shell/GlobalUsings.cs b/SrcMod/Shell/GlobalUsings.cs index dc25d52..13ff962 100644 --- a/SrcMod/Shell/GlobalUsings.cs +++ b/SrcMod/Shell/GlobalUsings.cs @@ -8,5 +8,7 @@ global using System.IO; global using System.IO.Compression; global using System.Linq; global using System.Reflection; +global using System.Runtime.InteropServices; +global using System.Text; global using System.Threading; global using static SrcMod.Shell.Tools; diff --git a/SrcMod/Shell/Interop/Kernel32.cs b/SrcMod/Shell/Interop/Kernel32.cs new file mode 100644 index 0000000..751f71c --- /dev/null +++ b/SrcMod/Shell/Interop/Kernel32.cs @@ -0,0 +1,11 @@ +namespace SrcMod.Shell.Interop; + +internal static partial class Kernel32 +{ + [LibraryImport("kernel32.dll", SetLastError = true)] + public static partial uint GetFinalPathNameByHandleA(nint hFile, [MarshalAs(UnmanagedType.LPTStr)] string lpszFilePath, + uint cchFilePath, uint dwFlags); + + [LibraryImport("kernel32.dll")] + public static partial nuint GlobalSize(nint hPtr); +} diff --git a/SrcMod/Shell/Interop/User32.cs b/SrcMod/Shell/Interop/User32.cs new file mode 100644 index 0000000..b11f317 --- /dev/null +++ b/SrcMod/Shell/Interop/User32.cs @@ -0,0 +1,29 @@ +namespace SrcMod.Shell.Interop; + +internal static partial class User32 +{ + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool CloseClipboard(); + + [LibraryImport("user32.dll")] + public static partial uint EnumClipboardFormats(uint uFormat); + + [LibraryImport("user32.dll")] + public static partial nint GetClipboardData(uint uFormat); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool IsClipboardFormatAvailable(uint uFormat); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool OpenClipboard(nint hWndNewOwner); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool EmptyClipboard(); + + [LibraryImport("user32.dll")] + public static partial nint SetClipboardData(uint uFormat, nint hMem); +} diff --git a/SrcMod/Shell/Modules/ClipboardModule.cs b/SrcMod/Shell/Modules/ClipboardModule.cs new file mode 100644 index 0000000..d69b42b --- /dev/null +++ b/SrcMod/Shell/Modules/ClipboardModule.cs @@ -0,0 +1,112 @@ +using SrcMod.Shell.Interop; + +namespace SrcMod.Shell.Modules; + +[Module("clipboard")] +public static class ClipboardModule +{ + [Command("clear")] + public static void ClearClipboard() + { + if (!ValidateUnsafe()) return; + + User32.OpenClipboard(0); + User32.EmptyClipboard(); + User32.CloseClipboard(); + } + + [Command("copy")] + public static void CopyClipboard(string text) + { + const uint format = 1; + + if (!ValidateUnsafe()) return; + + if (!text.EndsWith("\0")) text += "\0"; + byte[] data = Encoding.Default.GetBytes(text); + + nint hGlobal = Marshal.AllocHGlobal(data.Length + 1); + Marshal.Copy(data, 0, hGlobal, data.Length); + + User32.OpenClipboard(0); + User32.EmptyClipboard(); + + User32.SetClipboardData(format, hGlobal); + + User32.CloseClipboard(); + } + + [Command("view")] + public static void ViewClipboard() + { + // TODO (maybe): Make this support other formats? + // I spent way too long trying to make that a reality + // but the whole "clipboard format" thing is a nightmare. + + if (!ValidateUnsafe()) return; + + User32.OpenClipboard(0); + + nint hClipboard; + uint format; + if (User32.IsClipboardFormatAvailable(format = (uint)ClipboardFormat.DspText) || + User32.IsClipboardFormatAvailable(format = (uint)ClipboardFormat.OemText) || + User32.IsClipboardFormatAvailable(format = (uint)ClipboardFormat.Text) || + User32.IsClipboardFormatAvailable(format = (uint)ClipboardFormat.UnicodeText)) + hClipboard = User32.GetClipboardData(format); + else throw new("Clipboard doesn't contain text data."); + + nuint length = Kernel32.GlobalSize(hClipboard); + byte[] data = new byte[length]; + + Marshal.Copy(hClipboard, data, 0, (int)length); + + User32.CloseClipboard(); + + string msg = (ClipboardFormat)format switch + { + ClipboardFormat.DspText or ClipboardFormat.OemText or ClipboardFormat.Text => + Encoding.UTF8.GetString(data), + ClipboardFormat.UnicodeText => Encoding.Unicode.GetString(data), + _ => throw new("Unknown text format.") + }; + + Write(msg); + } + + public enum ClipboardFormat + { + Biff5 = 49988, + Biff8 = 49986, + Biff12 = 50009, + Bitmap = 2, + Csv = 49989, + DataObject = 49161, + Dib = 8, + Dif = 5, + DspText = 129, + EmbedSource = 49163, + EnhancedMetafile = 14, + HandleDrop = 15, + HtmlFormat = 49381, + Hyperlink = 50006, + Link = 49985, + LinkSource = 49165, + LinkSourceDescriptor = 49167, + Locale = 16, + Max = 17, + MetafilePicture = 3, + Native = 49156, + ObjectDescriptor = 49166, + ObjectLink = 49154, + OemText = 7, + OlePrivateData = 49171, + OwnerLink = 49155, + Palette = 9, + RichTextFormat = 49308, + SYLK = 4, + Text = 1, + UnicodeText = 13, + XmlSpreadSheet = 50007 + } +} diff --git a/SrcMod/Shell/Shell.csproj b/SrcMod/Shell/Shell.csproj index 0fdb552..511ed2f 100644 --- a/SrcMod/Shell/Shell.csproj +++ b/SrcMod/Shell/Shell.csproj @@ -12,6 +12,7 @@ That_One_Nerd false Logo.ico + true diff --git a/SrcMod/Shell/Tools.cs b/SrcMod/Shell/Tools.cs index 8610be6..0bbeb15 100644 --- a/SrcMod/Shell/Tools.cs +++ b/SrcMod/Shell/Tools.cs @@ -1,4 +1,6 @@ -namespace SrcMod.Shell; +using System.Text; + +namespace SrcMod.Shell; public static class Tools { @@ -137,4 +139,18 @@ public static class Tools Console.ForegroundColor = prevCol; } + + public static bool ValidateUnsafe() + { + Write("You are about to execute an unsafe command.\nProceed? > ", ConsoleColor.DarkYellow, false); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.CursorVisible = true; + string result = Console.ReadLine()!.Trim().ToLower(); + Console.CursorVisible = false; + Console.ResetColor(); + + return result == "y" || result == "yes" || result == "t" || + result == "true" || result == "p" || result == "proceed"; + } }