├── .github ├── CODEOWNERS └── workflows │ └── build-package.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── PowerShell ├── ConnectRdpSession.cs ├── Devolutions.MsRdpEx.PowerShell.csproj ├── RdpContext.cs ├── RdpMain.cs ├── RdpView.cs ├── StartRdpClient.cs └── StartRdpProcess.cs ├── README.md ├── channels ├── CMakeLists.txt └── DvcServer.cpp ├── cmake ├── MSVCRuntime.cmake └── WindowsRC.cmake ├── com ├── .gitignore ├── MSTSCLib.dll ├── README.md ├── mstscax.h ├── mstscax.idl ├── mstscax.tlh ├── mstscax.tli └── mstscax_i.c ├── detours.ps1 ├── dll ├── ApiHooks.cpp ├── ArrayList.c ├── AxDll.cpp ├── AxHost │ ├── RdpAxHostWnd.cpp │ ├── RdpAxHostWnd.h │ ├── RdpComBase.h │ ├── RdpEventSink.cpp │ ├── RdpEventSink.h │ ├── RdpOleSite.cpp │ ├── RdpOleSite.h │ ├── RdpWinMain.cpp │ └── RdpWinMain.h ├── Bitmap.c ├── CMakeLists.txt ├── ComHelpers.h ├── Detours.cpp ├── DpiHelper.cpp ├── DpiHelper.h ├── Environment.c ├── File.c ├── HashTable.c ├── KeyMaps.cpp ├── Log.c ├── Memory.c ├── MsRdpClient.cpp ├── MsRdpClient.h ├── MsRdpEx.cpp ├── MsRdpEx.def ├── MsRdpEx.h ├── MsRdpEx.rc ├── NameResolver.c ├── NamedPipe.c ├── OutputMirror.c ├── Paths.c ├── Pcap.cpp ├── RdpCoreApi.cpp ├── RdpDvcClient.cpp ├── RdpDvcClient.h ├── RdpFile.c ├── RdpInstance.cpp ├── RdpProcess.cpp ├── RdpSettings.cpp ├── RecordingManifest.c ├── Sspi.cpp ├── Stopwatch.c ├── Stream.cpp ├── String.c ├── TSObjects.cpp ├── TSObjects.h ├── VideoRecorder.c └── WinMsg.c ├── dotnet ├── .gitignore ├── AxInterop.MSTSCLib │ ├── AxInterop.MSTSCLib.csproj │ ├── AxMSTSCLib.cs │ └── RdpAxHost.cs ├── CMakeLists.txt ├── Devolutions.MsRdpEx │ ├── Bindings.cs │ ├── Devolutions.MsRdpEx.csproj │ ├── Devolutions.MsRdpEx.targets │ ├── LoadBalanceInfo.cs │ ├── MarshalHelpers.cs │ ├── RdpCoreApi.cs │ ├── RdpInstance.cs │ └── RdpProcess.cs ├── Directory.Build.props.in ├── Interop.MSTSCLib │ └── Interop.MSTSCLib.dll ├── MsRdpEx_App │ ├── App.config │ ├── DvcClientLib.cs │ ├── MainDlg.Designer.cs │ ├── MainDlg.cs │ ├── MainDlg.resx │ ├── MsRdpEx_App.csproj │ ├── NowProtoPipeTransport.cs │ ├── Program.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── RdpChannel.cs │ ├── RdpManager.cs │ ├── RdpView.cs │ └── RdpView.resx └── common.build.pre.props ├── exe ├── CMakeLists.txt ├── msrdcex │ ├── CMakeLists.txt │ ├── msrdcex.cpp │ ├── msrdcex.ico │ └── msrdcex.rc ├── mstscex │ ├── CMakeLists.txt │ ├── mstscex.cpp │ ├── mstscex.ico │ └── mstscex.rc └── vmconnectex │ ├── CMakeLists.txt │ ├── vmconnectex.cpp │ ├── vmconnectex.ico │ └── vmconnectex.rc ├── images └── MsRdpEx_installed.png ├── include └── MsRdpEx │ ├── ArrayList.h │ ├── Detours.h │ ├── Environment.h │ ├── HashTable.h │ ├── KeyMaps.h │ ├── Memory.h │ ├── MsRdpEx.h │ ├── NameResolver.h │ ├── NamedPipe.h │ ├── OutputMirror.h │ ├── Pcap.h │ ├── RdpCoreApi.h │ ├── RdpFile.h │ ├── RdpInstance.h │ ├── RdpProcess.h │ ├── RdpSettings.h │ ├── RecordingManifest.h │ ├── Sspi.h │ ├── Stopwatch.h │ ├── Stream.h │ └── VideoRecorder.h ├── installer ├── Folders.wxs ├── MsRdpEx.sln ├── MsRdpEx.wixproj ├── MsRdpEx.wxs ├── Package.en-us.wxl ├── Package.wxs └── Variables.wxi └── scripts └── SetAssemblyTargetFramework.ps1 /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # File auto-generated and managed by Devops 2 | /.github/ @devolutions/devops 3 | /.github/dependabot.yml @devolutions/security-managers 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | bin/ 3 | obj/ 4 | .vscode/ 5 | .nupkg 6 | dependencies/ 7 | package/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | 3 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release") 4 | 5 | project(MsRdpEx C CXX) 6 | 7 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 8 | 9 | include(CSharpUtilities) 10 | include(CMakePackageConfigHelpers) 11 | include(WindowsRC) 12 | 13 | file(STRINGS "${CMAKE_SOURCE_DIR}/dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.csproj" CSPROJ_LINES) 14 | foreach(CSPROJ_LINE ${CSPROJ_LINES}) 15 | if(CSPROJ_LINE MATCHES ".*") 16 | string(REGEX REPLACE "(.*)" "\\1" VERSION_STRING ${CSPROJ_LINE}) 17 | string(STRIP ${VERSION_STRING} VERSION_STRING) 18 | break() 19 | endif() 20 | endforeach() 21 | 22 | string(REGEX REPLACE "([0-9]+).[0-9]+.[0-9]+.[0-9]+" "\\1" MSRDPEX_VERSION_MAJOR ${VERSION_STRING}) 23 | string(REGEX REPLACE "[0-9]+.([0-9]+).[0-9]+.[0-9]+" "\\1" MSRDPEX_VERSION_MINOR ${VERSION_STRING}) 24 | string(REGEX REPLACE "[0-9]+.[0-9]+.([0-9]+).[0-9]+" "\\1" MSRDPEX_VERSION_PATCH ${VERSION_STRING}) 25 | string(REGEX REPLACE "[0-9]+.[0-9]+.[0-9]+.([0-9]+)" "\\1" MSRDPEX_VERSION_BUILD ${VERSION_STRING}) 26 | set(MSRDPEX_VERSION "${MSRDPEX_VERSION_MAJOR}.${MSRDPEX_VERSION_MINOR}.${MSRDPEX_VERSION_PATCH}.${MSRDPEX_VERSION_BUILD}") 27 | 28 | message(STATUS "VERSION: ${MSRDPEX_VERSION}") 29 | 30 | string(TIMESTAMP CURRENT_YEAR "%Y") 31 | 32 | set(MSRDPEX_NAME "MsRdpEx") 33 | set(MSRDPEX_VENDOR "Devolutions Inc.") 34 | set(MSRDPEX_COPYRIGHT "Copyright 2021-${CURRENT_YEAR} ${MSRDPEX_VENDOR}") 35 | 36 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 37 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 38 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 39 | 40 | if(NOT DEFINED WITH_NATIVE) 41 | set(WITH_NATIVE TRUE) 42 | endif() 43 | 44 | if(NOT DEFINED WITH_DOTNET) 45 | set(WITH_DOTNET TRUE) 46 | endif() 47 | 48 | if(MSVC) 49 | include(MSVCRuntime) 50 | 51 | if(NOT DEFINED MSVC_RUNTIME) 52 | set(MSVC_RUNTIME "static") 53 | endif() 54 | 55 | configure_msvc_runtime() 56 | endif() 57 | 58 | if(WIN32) 59 | set(C_FLAGS "") 60 | set(C_FLAGS "${C_FLAGS} -D_UNICODE") 61 | set(C_FLAGS "${C_FLAGS} -D_CRT_SECURE_NO_WARNINGS") 62 | set(C_FLAGS "${C_FLAGS} -DWIN32_LEAN_AND_MEAN") 63 | set(C_FLAGS "${C_FLAGS} -D_WINSOCK_DEPRECATED_NO_WARNINGS") 64 | set(C_FLAGS "${C_FLAGS} -DWINVER=0x0602 -D_WIN32_WINNT=0x0602") 65 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") 66 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") 67 | 68 | set(C_FLAGS_RELEASE "/Zi /GF") # produce debug symbols in release builds 69 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${C_FLAGS_RELEASE}") 70 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${C_FLAGS_RELEASE}") 71 | 72 | # http://devcenter.wintellect.com/jrobbins/correctly-creating-native-c-release-build-pdbs 73 | set(LINKER_FLAGS_RELEASE "/DEBUG") 74 | set(LINKER_FLAGS_RELEASE "${LINKER_FLAGS_RELEASE} /OPT:REF /OPT:ICF") 75 | set(LINKER_FLAGS_RELEASE "${LINKER_FLAGS_RELEASE} /MAP /MAPINFO:EXPORTS") 76 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE) 77 | set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE) 78 | set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} ${LINKER_FLAGS_RELEASE}" CACHE STRING "" FORCE) 79 | endif() 80 | 81 | set(DEPENDENCIES_DIR "${CMAKE_SOURCE_DIR}/dependencies") 82 | set(DETOURS_ROOT_DIR "${DEPENDENCIES_DIR}/detours") 83 | 84 | include_directories("${DETOURS_ROOT_DIR}/include") 85 | 86 | add_library(detours STATIC IMPORTED) 87 | 88 | if(CMAKE_GENERATOR_PLATFORM MATCHES "ARM64") 89 | set(MSVC_PLATFORM_TARGET "arm64") 90 | elseif(CMAKE_GENERATOR_PLATFORM MATCHES "Win32") 91 | set(MSVC_PLATFORM_TARGET "x86") 92 | else() 93 | set(MSVC_PLATFORM_TARGET "x64") 94 | endif() 95 | 96 | message(STATUS "MSVC_PLATFORM_TARGET: ${MSVC_PLATFORM_TARGET}") 97 | 98 | set_property(TARGET detours PROPERTY 99 | IMPORTED_LOCATION "${DETOURS_ROOT_DIR}/lib/${MSVC_PLATFORM_TARGET}/Release/detours.lib") 100 | set_property(TARGET detours PROPERTY 101 | IMPORTED_LOCATION_DEBUG "${DETOURS_ROOT_DIR}/lib/${MSVC_PLATFORM_TARGET}/Debug/detours.lib") 102 | 103 | include_directories("${CMAKE_SOURCE_DIR}/include") 104 | include_directories("${CMAKE_SOURCE_DIR}/com") 105 | 106 | if(WITH_NATIVE) 107 | add_subdirectory(dll) 108 | add_subdirectory(exe) 109 | add_subdirectory(channels) 110 | endif() 111 | 112 | if(WITH_DOTNET) 113 | add_subdirectory(dotnet) 114 | endif() 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /PowerShell/ConnectRdpSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Drawing; 4 | using System.Management.Automation; 5 | using System.Management.Automation.Runspaces; 6 | using System.Windows.Forms; 7 | 8 | using MSTSCLib; 9 | 10 | using MsRdpEx; 11 | 12 | namespace MsRdpEx.PowerShell 13 | { 14 | [Cmdlet(VerbsCommunications.Connect,"RdpSession")] 15 | 16 | public class ConnectRdpSessionCommand : PSCmdlet 17 | { 18 | [Parameter(Position = 0, Mandatory = true)] 19 | public string Hostname; 20 | 21 | [Parameter(Position = 1, Mandatory = true)] 22 | public string Username; 23 | 24 | [Parameter(Position = 2, Mandatory = true)] 25 | public string Password; 26 | 27 | protected override void ProcessRecord() 28 | { 29 | RdpContext rdpContext = RdpContext.Instance(); 30 | rdpContext.Connect(Hostname, Username, Password); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PowerShell/Devolutions.MsRdpEx.PowerShell.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | true 6 | Devolutions.MsRdpEx.PowerShell 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PowerShell/RdpContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Drawing; 4 | using System.Threading; 5 | using System.Management.Automation; 6 | using System.Management.Automation.Runspaces; 7 | using System.Windows.Forms; 8 | 9 | using MSTSCLib; 10 | 11 | using MsRdpEx; 12 | 13 | namespace MsRdpEx.PowerShell 14 | { 15 | public class RdpContext 16 | { 17 | static RdpContext instance = null; 18 | static Thread thread = null; 19 | 20 | static RdpMain rdpMain = null; 21 | 22 | public static RdpContext Instance() { 23 | if (instance == null) { 24 | instance = new RdpContext(); 25 | } 26 | return instance; 27 | } 28 | 29 | public RdpContext() 30 | { 31 | RdpContext.StartThread(); 32 | System.Threading.Thread.Sleep(250); 33 | } 34 | 35 | public static Thread StartThread() 36 | { 37 | thread = new Thread(new ThreadStart(MainThread)); 38 | thread.SetApartmentState(ApartmentState.STA); 39 | thread.Start(); 40 | return thread; 41 | } 42 | 43 | static void MainThread() 44 | { 45 | Application.EnableVisualStyles(); 46 | Application.SetCompatibleTextRenderingDefault(false); 47 | rdpMain = new RdpMain(); 48 | Application.Run(rdpMain); 49 | } 50 | 51 | public void Connect(string hostname, string username, string password) 52 | { 53 | rdpMain.Connect(hostname, username, password); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PowerShell/RdpMain.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Data; 6 | using System.Drawing; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | using System.Diagnostics; 12 | using System.Runtime.InteropServices; 13 | 14 | using MSTSCLib; 15 | 16 | using MsRdpEx; 17 | 18 | namespace MsRdpEx.PowerShell 19 | { 20 | public partial class RdpMain : Form 21 | { 22 | public RdpCoreApi coreApi; 23 | public RdpInstance rdpInstance; 24 | public RdpView rdpView; 25 | public List rdpViews = new List(); 26 | 27 | public RdpMain() 28 | { 29 | InitializeComponent(); 30 | 31 | this.coreApi = new RdpCoreApi(); 32 | coreApi.Load(); 33 | } 34 | 35 | public void Connect(string hostname, string username, string password) 36 | { 37 | if (this.InvokeRequired) { 38 | Action safe = delegate { Connect(hostname, username, password); }; 39 | this.Invoke(safe); 40 | return; 41 | } 42 | 43 | RdpContext context = RdpContext.Instance(); 44 | string rdpExDll = coreApi.MsRdpExDllPath; 45 | 46 | string axName = "mstsc"; 47 | string appName = axName; 48 | 49 | RdpView rdpView = new RdpView(axName, rdpExDll); 50 | AxMSTSCLib.AxMsRdpClient9NotSafeForScripting rdp = rdpView.rdpClient; 51 | 52 | this.rdpInstance = new RdpInstance((IMsRdpExInstance)rdp.GetOcx()); 53 | rdpInstance.OutputMirrorEnabled = true; 54 | rdpInstance.VideoRecordingEnabled = true; 55 | 56 | rdp.Server = hostname; 57 | rdp.UserName = username; 58 | rdp.AdvancedSettings9.EnableCredSspSupport = true; 59 | IMsTscNonScriptable secured = (IMsTscNonScriptable)rdp.GetOcx(); 60 | secured.ClearTextPassword = password; 61 | IMsRdpExtendedSettings extendedSettings = (IMsRdpExtendedSettings)rdp.GetOcx(); 62 | object boolValue = false; 63 | extendedSettings.set_Property("EnableHardwareMode", ref boolValue); 64 | Size DesktopSize = new Size(1024, 768); 65 | rdp.DesktopWidth = DesktopSize.Width; 66 | rdp.DesktopHeight = DesktopSize.Height; 67 | rdpView.ClientSize = DesktopSize; 68 | rdpView.Text = String.Format("{0} ({1})", rdp.Server, axName); 69 | 70 | rdp.Connect(); 71 | rdpView.Show(); 72 | 73 | this.rdpViews.Add(rdpView); 74 | } 75 | 76 | protected override void OnLoad(EventArgs e) 77 | { 78 | //Visible = false; 79 | //ShowInTaskbar = false; 80 | //WindowState = FormWindowState.Minimized; 81 | base.OnLoad(e); 82 | } 83 | 84 | private void InitializeComponent() 85 | { 86 | this.SuspendLayout(); 87 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 88 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 89 | this.ClientSize = new System.Drawing.Size(256, 256); 90 | this.Name = "RdpMain"; 91 | this.Text = "Remote Desktop Client"; 92 | this.ResumeLayout(false); 93 | this.Hide(); 94 | } 95 | 96 | protected override void Dispose(bool disposing) 97 | { 98 | base.Dispose(disposing); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /PowerShell/StartRdpClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Drawing; 4 | using System.Management.Automation; 5 | using System.Management.Automation.Runspaces; 6 | using System.Windows.Forms; 7 | 8 | using MSTSCLib; 9 | 10 | using MsRdpEx; 11 | 12 | namespace MsRdpEx.PowerShell 13 | { 14 | [Cmdlet(VerbsLifecycle.Start,"RdpClient")] 15 | public class StartRdpClientCommand : PSCmdlet 16 | { 17 | protected override void ProcessRecord() 18 | { 19 | RdpContext rdpContext = RdpContext.Instance(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PowerShell/StartRdpProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Management.Automation; 3 | using System.Management.Automation.Runspaces; 4 | 5 | using MsRdpEx; 6 | 7 | namespace MsRdpEx.PowerShell 8 | { 9 | [Cmdlet(VerbsLifecycle.Start,"RdpProcess")] 10 | public class StartRdpProcessCommand : PSCmdlet 11 | { 12 | protected override void ProcessRecord() 13 | { 14 | RdpCoreApi coreApi = new RdpCoreApi(); 15 | string rdpExDll = coreApi.MsRdpExDllPath; 16 | 17 | coreApi.Load(); 18 | 19 | string axName = "mstsc"; 20 | string appName = axName; 21 | string[] args = new string[0]; 22 | RdpProcess rdpProcess = new RdpProcess(args, appName, axName); 23 | 24 | WriteObject(rdpProcess); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft RDP Extensions (MsRdpEx) 2 | 3 | ## Installation 4 | 5 | Download and install the latest MsRdpEx MSI package [GitHub releases](https://github.com/Devolutions/MsRdpEx/releases). 6 | 7 | After installation, the launcher executables and API hooking DLL can be found in "%ProgramFiles%\Devolutions\MsRdpEx": 8 | 9 | ![MsRdpEx Installed](./images/MsRdpEx_installed.png) 10 | 11 | The installer automatically associates .RDP files with mstscex, and .RDPW files with msrdcex, so you can get started right away. Simply revert the file type association to use the original Microsoft Remote Desktop Clients without the extensions, or launch mstsc.exe/msrdc.exe manually. 12 | 13 | This repository also contains a C# [nuget package](https://www.nuget.org/packages/Devolutions.MsRdpEx) that can be used to consume the RDP ActiveX interface with or without API hooking, along with launching mstsc.exe or msrdc.exe as external processes using MsRdpEx.dll. 14 | 15 | ## Extended .RDP File Options 16 | 17 | MsRdpEx processes additional .RDP file options that are not normally supported by mstsc.exe: 18 | 19 | | RDP setting | Description | Values | Default value | 20 | |------------------------------------|------------------------|------------------------|:----------------------:| 21 | | KDCProxyURL:s:value | Kerberos KDC Proxy HTTPS URL | KDC Proxy HTTPS *URL*, not using error-prone KDCProxyName format, and unrestricted in length, like https://:443/KdcProxy | - | 22 | | UserSpecifiedServerName:s:value | Server name used for TLS and Kerberos server validation | explicit server name (usually the machine FQDN) | same as DNS hostname used for RDP server | 23 | | EnableMouseJiggler:i:value | Enable RDP mouse jiggler | 0/1 | 0 | 24 | | MouseJigglerInterval:i:value | RDP mouse jiggler interval in seconds | Interval in seconds | 60 | 25 | | MouseJigglerMethod:i:value | RDP mouse jiggler method | 0/1 | 0 | 26 | | AllowBackgroundInput:i:value | Allow background input events when window is not in focus | 0/1 | 0 | 27 | | EnableRelativeMouse:i:value | Enable relative mouse mode | 0/1 | 0 | 28 | | DisableCredentialsDelegation:i:value | Disable CredSSP credential delegation | 0/1 | 0 | 29 | | RedirectedAuthentication:i:value | Enable Remote Credential Guard | 0/1 | 0 | 30 | | RestrictedLogon:i:value | Enable Restricted Admin Mode | 0/1 | 0 | 31 | | DisableUDPTransport:i:value | Disable RDP UDP transport (TCP only) | 0/1 | 0 | 32 | | ConnectToChildSession:i:value | Connect to child session | 0/1 | 0 | 33 | | EnableHardwareMode:i:value | Disable DirectX client presenter (force GDI client presenter) | 0/1 | 1 | 34 | | ClearTextPassword:s:value | Target RDP server password - use for testing only | Insecure password | - | 35 | | GatewayPassword:s:value | RD Gateway server password - use for testing only | Insecure password | - | 36 | 37 | ## Extended RDP client logs 38 | 39 | MsRdpEx also supports extended logging controlled by environment variables: 40 | 41 | ```powershell 42 | $Env:MSRDPEX_LOG_ENABLED="1" 43 | $Env:MSRDPEX_LOG_LEVEL="DEBUG" 44 | .\mstscex.exe 45 | ``` 46 | 47 | If you don't pass a .RDP file, the mstsc.exe GUI will launch normally, but you won't be able to leverage any of the extended MsRdpEx .RDP file options. The default log file path location is in "%LocalAppData%\MsRdpEx\MsRdpEx.log". You can override log settings using the MSRDPEX_LOG_LEVEL and MSRDPEX_LOG_FILE_PATH environment variables: 48 | 49 | ```powershell 50 | $Env:MSRDPEX_LOG_ENABLED="1" 51 | $Env:MSRDPEX_LOG_LEVEL="TRACE" 52 | $Env:MSRDPEX_LOG_FILE_PATH="C:\Windows\Temp\MsRdpEx.log" 53 | .\mstscex.exe 54 | ``` 55 | 56 | The trace log level is extremely verbose, so it should only be used when necessary. The MsRdpEx logging is very helpful in understanding the Microsoft RDP client internals. 57 | 58 | ## Building from source 59 | 60 | Using a [Visual Studio developer shell](https://www.powershellgallery.com/packages/VsDevShell), build the [Detours](https://github.com/Microsoft/Detours) library: 61 | 62 | ```powershell 63 | Enter-VsDevShell x64 64 | .\detours.ps1 65 | ``` 66 | 67 | Generate the Visual Studio project files for your target platform: 68 | 69 | ```powershell 70 | mkdir build-x64 && cd build-x64 71 | cmake -G "Visual Studio 17 2022" -A x64 .. 72 | ``` 73 | 74 | Open the Visual Studio solution or build it from the command-line: 75 | 76 | ```powershell 77 | cmake --build . --config Release 78 | ``` 79 | 80 | You should now have mstscex.exe and MsRdpEx.dll. 81 | -------------------------------------------------------------------------------- /channels/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_executable(DvcServer 3 | DvcServer.cpp) 4 | 5 | target_link_libraries(DvcServer wtsapi32.lib) 6 | -------------------------------------------------------------------------------- /channels/DvcServer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | DWORD OpenVirtualChannel(const char* channelName, HANDLE* phFile) 8 | { 9 | HANDLE hWTSHandle = NULL; 10 | HANDLE hWTSFileHandle; 11 | PVOID vcFileHandlePtr = NULL; 12 | DWORD len; 13 | DWORD rc = ERROR_SUCCESS; 14 | 15 | hWTSHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, (LPSTR)channelName, WTS_CHANNEL_OPTION_DYNAMIC); 16 | 17 | if (!hWTSHandle) 18 | { 19 | rc = GetLastError(); 20 | printf("WTSVirtualChannelOpenEx API Call Failed: GetLastError() = %d\n", GetLastError()); 21 | goto exitpt; 22 | } 23 | 24 | BOOL bSuccess = WTSVirtualChannelQuery(hWTSHandle, WTSVirtualFileHandle, &vcFileHandlePtr, &len); 25 | 26 | if (!bSuccess) 27 | { 28 | rc = GetLastError(); 29 | goto exitpt; 30 | } 31 | 32 | if (len != sizeof(HANDLE)) 33 | { 34 | rc = ERROR_INVALID_PARAMETER; 35 | goto exitpt; 36 | } 37 | 38 | hWTSFileHandle = *(HANDLE*)vcFileHandlePtr; 39 | 40 | bSuccess = DuplicateHandle(GetCurrentProcess(), 41 | hWTSFileHandle, GetCurrentProcess(), phFile, 0, FALSE, DUPLICATE_SAME_ACCESS); 42 | 43 | if (!bSuccess) 44 | { 45 | rc = GetLastError(); 46 | goto exitpt; 47 | } 48 | 49 | rc = ERROR_SUCCESS; 50 | exitpt: 51 | if (vcFileHandlePtr) 52 | { 53 | WTSFreeMemory(vcFileHandlePtr); 54 | } 55 | if (hWTSHandle) 56 | { 57 | WTSVirtualChannelClose(hWTSHandle); 58 | } 59 | return rc; 60 | } 61 | 62 | DWORD WriteVirtualChannelMessage(HANDLE hFile, ULONG cbSize, BYTE* pBuffer) 63 | { 64 | BYTE WriteBuffer[1024]; 65 | DWORD dwWritten; 66 | BOOL bSuccess; 67 | HANDLE hEvent; 68 | 69 | hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 70 | 71 | OVERLAPPED overlapped = { 0 }; 72 | overlapped.hEvent = hEvent; 73 | 74 | bSuccess = WriteFile(hFile, pBuffer, cbSize, &dwWritten, &overlapped); 75 | 76 | if (!bSuccess) 77 | { 78 | if (GetLastError() == ERROR_IO_PENDING) 79 | { 80 | DWORD dwStatus = WaitForSingleObject(overlapped.hEvent, 10000); 81 | bSuccess = GetOverlappedResult(hFile, &overlapped, &dwWritten, FALSE); 82 | } 83 | } 84 | 85 | if (!bSuccess) 86 | { 87 | DWORD error = GetLastError(); 88 | return error; 89 | } 90 | 91 | return 0; 92 | } 93 | 94 | DWORD HandleVirtualChannel(HANDLE hFile) 95 | { 96 | BYTE ReadBuffer[CHANNEL_PDU_LENGTH]; 97 | DWORD dwRead; 98 | BYTE b = 0; 99 | CHANNEL_PDU_HEADER* pHdr = (CHANNEL_PDU_HEADER*)ReadBuffer; 100 | BOOL bSuccess; 101 | HANDLE hEvent; 102 | 103 | const char* cmd = "whoami"; 104 | ULONG cbSize = strlen(cmd) + 1; 105 | BYTE* pBuffer = (BYTE*)cmd; 106 | WriteVirtualChannelMessage(hFile, cbSize, pBuffer); 107 | 108 | hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 109 | 110 | do 111 | { 112 | OVERLAPPED overlapped = { 0 }; 113 | DWORD TotalRead = 0; 114 | 115 | do { 116 | overlapped.hEvent = hEvent; 117 | bSuccess = ReadFile(hFile, ReadBuffer, sizeof(ReadBuffer), &dwRead, &overlapped); 118 | 119 | if (!bSuccess) 120 | { 121 | if (GetLastError() == ERROR_IO_PENDING) 122 | { 123 | DWORD dwStatus = WaitForSingleObject(overlapped.hEvent, INFINITE); 124 | bSuccess = GetOverlappedResult(hFile, &overlapped, &dwRead, FALSE); 125 | } 126 | } 127 | 128 | if (!bSuccess) 129 | { 130 | DWORD error = GetLastError(); 131 | return error; 132 | } 133 | 134 | printf("read %d bytes\n", dwRead); 135 | 136 | ULONG packetSize = dwRead - sizeof(*pHdr); 137 | TotalRead += packetSize; 138 | PBYTE pData = (PBYTE)(pHdr + 1); 139 | 140 | printf(">> %s\n", (const char*)pData); 141 | 142 | } while (0 == (pHdr->flags & CHANNEL_FLAG_LAST)); 143 | 144 | } while (true); 145 | 146 | return 0; 147 | } 148 | 149 | INT _cdecl wmain(INT argc, __in_ecount(argc) WCHAR** argv) 150 | { 151 | DWORD rc; 152 | HANDLE hFile; 153 | const char* channelName = "DvcSample"; 154 | 155 | printf("Opening %s dynamic virtual channel\n", channelName); 156 | rc = OpenVirtualChannel(channelName, &hFile); 157 | 158 | if (ERROR_SUCCESS != rc) 159 | { 160 | printf("Failed to open %s dynamic virtual channel\n", channelName); 161 | return 0; 162 | } 163 | else 164 | { 165 | printf("%s dynamic virtual channel is opened\n", channelName); 166 | } 167 | 168 | HandleVirtualChannel(hFile); 169 | 170 | CloseHandle(hFile); 171 | 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /cmake/MSVCRuntime.cmake: -------------------------------------------------------------------------------- 1 | 2 | macro(configure_msvc_runtime) 3 | if(MSVC) 4 | # Default to statically-linked runtime. 5 | if("${MSVC_RUNTIME}" STREQUAL "") 6 | set(MSVC_RUNTIME "dynamic") 7 | endif() 8 | 9 | # Set compiler options. 10 | set(variables 11 | CMAKE_C_FLAGS 12 | CMAKE_C_FLAGS_DEBUG 13 | CMAKE_C_FLAGS_MINSIZEREL 14 | CMAKE_C_FLAGS_RELEASE 15 | CMAKE_C_FLAGS_RELWITHDEBINFO 16 | CMAKE_CXX_FLAGS 17 | CMAKE_CXX_FLAGS_DEBUG 18 | CMAKE_CXX_FLAGS_MINSIZEREL 19 | CMAKE_CXX_FLAGS_RELEASE 20 | CMAKE_CXX_FLAGS_RELWITHDEBINFO) 21 | 22 | if(${MSVC_RUNTIME} STREQUAL "static") 23 | message(STATUS "MSVC: using statically-linked runtime (/MT and /MTd).") 24 | foreach(variable ${variables}) 25 | if(${variable} MATCHES "/MD") 26 | string(REGEX REPLACE "/MD" "/MT" ${variable} "${${variable}}") 27 | endif() 28 | endforeach() 29 | else() 30 | message(STATUS "MSVC: using dynamically-linked runtime (/MD and /MDd).") 31 | foreach(variable ${variables}) 32 | if(${variable} MATCHES "/MT") 33 | string(REGEX REPLACE "/MT" "/MD" ${variable} "${${variable}}") 34 | endif() 35 | endforeach() 36 | endif() 37 | 38 | foreach(variable ${variables}) 39 | if(${variable} MATCHES "/Ob0") 40 | string(REGEX REPLACE "/Ob0" "/Ob2" ${variable} "${${variable}}") 41 | endif() 42 | endforeach() 43 | 44 | foreach(variable ${variables}) 45 | if(${variable} MATCHES "/W3") 46 | string(REGEX REPLACE "/W3" "/W2" ${variable} "${${variable}}") 47 | endif() 48 | endforeach() 49 | 50 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS}") 51 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 52 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 53 | set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL}") 54 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 55 | 56 | foreach(variable ${variables}) 57 | set(${variable} "${${variable}}" CACHE STRING "MSVC_${variable}" FORCE) 58 | endforeach() 59 | endif() 60 | endmacro(configure_msvc_runtime) 61 | -------------------------------------------------------------------------------- /cmake/WindowsRC.cmake: -------------------------------------------------------------------------------- 1 | 2 | function(windows_rc_generate_version_info) 3 | 4 | set(RC_ARGS_OPTIONS "") 5 | set(RC_ARGS_SINGLE "NAME;TYPE;VERSION;VENDOR;FILENAME;COPYRIGHT;OUTPUT") 6 | set(RC_ARGS_MULTI "") 7 | 8 | cmake_parse_arguments(RC "${RC_ARGS_OPTIONS}" "${RC_ARGS_SINGLE}" "${RC_ARGS_MULTI}" ${ARGN}) 9 | 10 | if(NOT DEFINED RC_TYPE OR "${RC_TYPE}" STREQUAL "") 11 | set(RC_TYPE "APP") 12 | endif() 13 | 14 | if(NOT DEFINED RC_VERSION OR "${RC_VERSION}" STREQUAL "") 15 | set(RC_VERSION "1.0.0") 16 | endif() 17 | 18 | # File Version 19 | 20 | if(NOT DEFINED RC_FILE_VERSION OR "${RC_FILE_VERSION}" STREQUAL "") 21 | set(RC_FILE_VERSION ${RC_VERSION}) 22 | endif() 23 | 24 | string(REPLACE "." ";" RC_FILE_VERSION_NUMBERS ${RC_FILE_VERSION}) 25 | list(LENGTH RC_FILE_VERSION_NUMBERS RC_FILE_VERSION_LENGTH) 26 | 27 | if(RC_FILE_VERSION_LENGTH LESS 4) 28 | list(APPEND RC_FILE_VERSION_NUMBERS "0") 29 | endif() 30 | 31 | string(REPLACE ";" "," RC_FILE_VERSION_NUMBER "${RC_FILE_VERSION_NUMBERS}") 32 | string(REPLACE ";" "." RC_FILE_VERSION_STRING "${RC_FILE_VERSION_NUMBERS}") 33 | 34 | # Product Version 35 | 36 | if(NOT DEFINED RC_PRODUCT_VERSION OR "${RC_PRODUCT_VERSION}" STREQUAL "") 37 | set(RC_PRODUCT_VERSION ${RC_VERSION}) 38 | endif() 39 | 40 | string(REPLACE "." ";" RC_PRODUCT_VERSION_NUMBERS ${RC_PRODUCT_VERSION}) 41 | list(LENGTH RC_PRODUCT_VERSION_NUMBERS RC_PRODUCT_VERSION_LENGTH) 42 | 43 | if(RC_PRODUCT_VERSION_LENGTH LESS 4) 44 | list(APPEND RC_PRODUCT_VERSION_NUMBERS "0") 45 | endif() 46 | 47 | string(REPLACE ";" "," RC_PRODUCT_VERSION_NUMBER "${RC_PRODUCT_VERSION_NUMBERS}") 48 | string(REPLACE ";" "." RC_PRODUCT_VERSION_STRING "${RC_PRODUCT_VERSION_NUMBERS}") 49 | 50 | set(RC_FILEOS "0x40004L") 51 | 52 | if(RC_TYPE STREQUAL "APP") 53 | set(RC_FILETYPE "0x1L") # VFT_APP 54 | elseif(RC_TYPE STREQUAL "DLL") 55 | set(RC_FILETYPE "0x2L") # VFT_DLL 56 | elseif(RC_TYPE STREQUAL "DRV") 57 | set(RC_FILETYPE "0x3L") # VFT_DRV 58 | else() 59 | set(RC_FILETYPE "0x0L") # VFT_UNKNOWN 60 | endif() 61 | 62 | set(RC_FILESUBTYPE "0x0L") 63 | 64 | set(RC_COMPANY_NAME "${RC_VENDOR}") 65 | set(RC_FILE_DESCRIPTION "${RC_NAME}") 66 | set(RC_PRODUCT_NAME "${RC_NAME}") 67 | set(RC_INTERNAL_NAME ${RC_FILENAME}) 68 | set(RC_ORIGINAL_FILENAME ${RC_FILENAME}) 69 | set(RC_LEGAL_COPYRIGHT ${RC_COPYRIGHT}) 70 | 71 | # VERSIONINFO resource 72 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058 73 | 74 | set(LINES "") 75 | list(APPEND LINES "") 76 | list(APPEND LINES "#include ") 77 | list(APPEND LINES "") 78 | list(APPEND LINES "VS_VERSION_INFO VERSIONINFO") 79 | list(APPEND LINES " FILEVERSION ${RC_FILE_VERSION_NUMBER}") 80 | list(APPEND LINES " PRODUCTVERSION ${RC_PRODUCT_VERSION_NUMBER}") 81 | list(APPEND LINES " FILEFLAGSMASK 0x3fL") 82 | list(APPEND LINES "#ifdef _DEBUG") 83 | list(APPEND LINES " FILEFLAGS 0x1L") 84 | list(APPEND LINES "#else") 85 | list(APPEND LINES " FILEFLAGS 0x0L") 86 | list(APPEND LINES "#endif") 87 | list(APPEND LINES " FILEOS ${RC_FILEOS}") 88 | list(APPEND LINES " FILETYPE ${RC_FILETYPE}") 89 | list(APPEND LINES " FILESUBTYPE ${RC_FILESUBTYPE}") 90 | list(APPEND LINES "BEGIN") 91 | list(APPEND LINES " BLOCK \"StringFileInfo\"") 92 | list(APPEND LINES " BEGIN") 93 | list(APPEND LINES " BLOCK \"040904b0\"") 94 | list(APPEND LINES " BEGIN") 95 | list(APPEND LINES " VALUE \"CompanyName\", \"${RC_COMPANY_NAME}\"") 96 | list(APPEND LINES " VALUE \"FileDescription\", \"${RC_FILE_DESCRIPTION}\"") 97 | list(APPEND LINES " VALUE \"FileVersion\", \"${RC_FILE_VERSION_STRING}\"") 98 | list(APPEND LINES " VALUE \"InternalName\", \"${RC_INTERNAL_NAME}\"") 99 | list(APPEND LINES " VALUE \"LegalCopyright\", \"${RC_LEGAL_COPYRIGHT}\"") 100 | list(APPEND LINES " VALUE \"OriginalFilename\", \"${RC_ORIGINAL_FILENAME}\"") 101 | list(APPEND LINES " VALUE \"ProductName\", \"${RC_PRODUCT_NAME}\"") 102 | list(APPEND LINES " VALUE \"ProductVersion\", \"${RC_PRODUCT_VERSION_STRING}\"") 103 | list(APPEND LINES " END") 104 | list(APPEND LINES " END") 105 | list(APPEND LINES " BLOCK \"VarFileInfo\"") 106 | list(APPEND LINES " BEGIN") 107 | list(APPEND LINES " VALUE \"Translation\", 0x409, 1200") 108 | list(APPEND LINES " END") 109 | list(APPEND LINES "END") 110 | list(APPEND LINES "") 111 | 112 | set(RC_FILE "") 113 | foreach(LINE ${LINES}) 114 | set(RC_FILE "${RC_FILE}${LINE}\n") 115 | endforeach() 116 | 117 | file(WRITE "${RC_OUTPUT}" ${RC_FILE}) 118 | endfunction() 119 | -------------------------------------------------------------------------------- /com/.gitignore: -------------------------------------------------------------------------------- 1 | mstscax.dll 2 | mstscax.tlb 3 | rdclientax.dll 4 | AxMSTSCLib.dll 5 | AxMSTSCLib.pdb 6 | AxMSTSCLib.cs -------------------------------------------------------------------------------- /com/MSTSCLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/com/MSTSCLib.dll -------------------------------------------------------------------------------- /com/README.md: -------------------------------------------------------------------------------- 1 | 2 | Open a Visual Studio Command Prompt with PowerShell. 3 | 4 | Copy mstscax.dll (or rdclientax.dll) in the current directory: 5 | 6 | ```powershell 7 | cp "$Env:SystemRoot\System32\mstscax.dll" . 8 | ``` 9 | 10 | Use the MSVC compiler built-in type library importer: 11 | 12 | ```powershell 13 | echo '#import "mstscax.dll" named_guids' > import.cpp 14 | cl.exe /c /nologo .\import.cpp && rm .\import.* 15 | (Get-Content mstscax.tlh | Select-Object -Skip 7) | Set-Content mstscax.tlh 16 | (Get-Content mstscax.tli | Select-Object -Skip 7) | Set-Content mstscax.tli 17 | ``` 18 | 19 | Generate C# import library: 20 | 21 | ```powershell 22 | tlbimp.exe /machine:Agnostic /out:Interop.MSTSCLib.dll /namespace:MSTSCLib .\mstscax.dll 23 | aximp.exe /source /rcw:Interop.MSTSCLib.dll /out:AxInterop.MSTSCLib.dll .\mstscax.dll 24 | (Get-Content AxInterop.MSTSCLib.cs | Select-Object -Skip 10) | Set-Content AxInterop.MSTSCLib.cs 25 | ``` 26 | 27 | Generate interface definition file (IDL) from type library: 28 | 29 | * Launch OleView.exe from an elevated command prompt 30 | * File -> View TypeLib, then select mstscax.dll 31 | * File -> Save As..., use file name mstscax.idl 32 | 33 | The resulting .IDL file needs to be manually edited to fix type declaration ordering before midl.exe can compile it again into a .tlb file. 34 | 35 | ```powershell 36 | midl.exe .\mstscax.idl /notlb /header mstscax.h /iid mstscax_i.c 37 | ``` 38 | 39 | In 32-bit mode, the generated headers define UINT_PTR and LONG_PTR types that can cause conflicts, use this code snippet to comment them out: 40 | 41 | ```powershell 42 | 'mstscax.tlh', 'mstscax.h' | ForEach-Object { 43 | $file = $_ 44 | (Get-Content $file) | ForEach-Object { 45 | if ($_ -match '^\s*typedef\s+.*?_PTR;') { 46 | '// ' + $_ 47 | } else { 48 | $_ 49 | } 50 | } | Set-Content $file 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /com/mstscax.idl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/com/mstscax.idl -------------------------------------------------------------------------------- /detours.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $GitRepo = 'https://github.com/microsoft/Detours', 3 | [string] $GitCommit = '4b8c659' # July 24, 2023 4 | ) 5 | 6 | $RepoRoot = $PSScriptRoot 7 | 8 | $SourcesDir = "$RepoRoot/dependencies/sources" 9 | New-Item -ItemType Directory -Path $SourcesDir -ErrorAction SilentlyContinue | Out-Null 10 | 11 | Push-Location 12 | Set-Location $SourcesDir 13 | 14 | if (-Not (Test-Path -Path "detours")) { 15 | & 'git' 'clone' $GitRepo 'detours' 16 | } 17 | 18 | Set-Location "detours/src" 19 | 20 | & 'git' 'checkout' $GitCommit 21 | 22 | if (-Not (Test-Path Env:VSCMD_ARG_TGT_ARCH)) { 23 | throw "VSCMD_ARG_TGT_ARCH is not set, use a Visual Studio developer shell" 24 | } 25 | 26 | Write-Host "Building Detours for $Env:VSCMD_ARG_TGT_ARCH" 27 | 28 | $TargetArchs = @($Env:VSCMD_ARG_TGT_ARCH) 29 | $TargetConfs = @('Release', 'Debug') 30 | 31 | foreach ($TargetArch in $TargetArchs) { 32 | $Env:VSCMD_ARG_TGT_ARCH="$TargetArch" 33 | $Env:DETOURS_TARGET_PROCESSOR="$($TargetArch.ToUpper())" 34 | & nmake clean 35 | foreach ($TargetConf in $TargetConfs) { 36 | $Env:DETOURS_CONFIG="$TargetConf" 37 | & nmake 38 | } 39 | } 40 | 41 | Set-Location ".." 42 | 43 | $PkgDir = "$RepoRoot/dependencies/detours" 44 | New-Item -ItemType Directory -Path $PkgDir -ErrorAction SilentlyContinue | Out-Null 45 | New-Item -ItemType Directory -Path "$PkgDir/lib" -ErrorAction SilentlyContinue | Out-Null 46 | 47 | Remove-Item -Path "$PkgDir/include" -Recurse -ErrorAction SilentlyContinue | Out-Null 48 | Copy-Item "include" -Destination "$PkgDir/include" -Exclude @("syelog.h") -Recurse 49 | 50 | foreach ($TargetArch in $TargetArchs) { 51 | Remove-Item -Path "$PkgDir/lib/$TargetArch" -Recurse -ErrorAction SilentlyContinue | Out-Null 52 | New-Item -ItemType Directory -Path "$PkgDir/lib/$TargetArch" -ErrorAction SilentlyContinue | Out-Null 53 | foreach ($TargetConf in $TargetConfs) { 54 | $LibDir = "lib." + $TargetArch.ToUpper() + $TargetConf 55 | Write-Host "Copying $LibDir to "$PkgDir/lib/$TargetArch/$TargetConf"" 56 | New-Item -ItemType Directory -Path "$PkgDir/lib/$TargetArch/$TargetConf" | Out-Null 57 | Copy-Item "$LibDir/*" -Destination "$PkgDir/lib/$TargetArch/$TargetConf" -Recurse 58 | } 59 | } 60 | 61 | Pop-Location 62 | -------------------------------------------------------------------------------- /dll/AxDll.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "MsRdpEx.h" 3 | 4 | #include "MsRdpClient.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | extern bool g_AxHookEnabled; 11 | 12 | HRESULT CDECL MsRdpEx_AxDll_DllGetClassObject(fnDllGetClassObject pfnDllGetClassObject, REFCLSID rclsid, REFIID riid, LPVOID* ppv) 13 | { 14 | HRESULT hr = pfnDllGetClassObject(rclsid, riid, ppv); 15 | 16 | if (!MsRdpEx_GetAxHookEnabled()) 17 | return hr; 18 | 19 | if (riid == IID_IClassFactory) 20 | { 21 | if (hr == S_OK) 22 | { 23 | *ppv = MsRdpEx_CClassFactory_New(rclsid, (IClassFactory*) *ppv); 24 | } 25 | } 26 | 27 | return hr; 28 | } 29 | 30 | bool CDECL MsRdpEx_mstscax_Load(MsRdpEx_mstscax* dll, const char* filename) 31 | { 32 | bool success = false; 33 | 34 | if (dll->initialized) 35 | return true; 36 | 37 | ZeroMemory(dll, sizeof(MsRdpEx_mstscax)); 38 | 39 | dll->hModule = MsRdpEx_LoadLibrary(filename); 40 | 41 | MsRdpEx_LogPrint(DEBUG, "mstscax_load(%s): %p", filename, dll->hModule); 42 | 43 | if (!dll->hModule) 44 | goto exit; 45 | 46 | dll->DllCanUnloadNow = (fnDllCanUnloadNow)GetProcAddress(dll->hModule, "DllCanUnloadNow"); 47 | dll->DllGetClassObject = (fnDllGetClassObject)GetProcAddress(dll->hModule, "DllGetClassObject"); 48 | dll->DllRegisterServer = (fnDllRegisterServer)GetProcAddress(dll->hModule, "DllRegisterServer"); 49 | dll->DllUnregisterServer = (fnDllUnregisterServer)GetProcAddress(dll->hModule, "DllUnregisterServer"); 50 | dll->DllGetTscCtlVer = (fnDllGetTscCtlVer)GetProcAddress(dll->hModule, "DllGetTscCtlVer"); 51 | dll->DllSetAuthProperties = (fnDllSetAuthProperties)GetProcAddress(dll->hModule, "DllSetAuthProperties"); 52 | dll->DllGetClaimsToken = (fnDllGetClaimsToken9)GetProcAddress(dll->hModule, "DllGetClaimsToken"); 53 | dll->DllSetClaimsToken = (fnDllSetClaimsToken)GetProcAddress(dll->hModule, "DllSetClaimsToken"); 54 | dll->DllLogoffClaimsToken = (fnDllLogoffClaimsToken1)GetProcAddress(dll->hModule, "DllLogoffClaimsToken"); 55 | dll->DllCancelAuthentication = (fnDllCancelAuthentication)GetProcAddress(dll->hModule, "DllCancelAuthentication"); 56 | dll->DllDeleteSavedCreds = (fnDllDeleteSavedCreds)GetProcAddress(dll->hModule, "DllDeleteSavedCreds"); 57 | 58 | dll->tscCtlVer = dll->DllGetTscCtlVer(); 59 | 60 | success = true; 61 | exit: 62 | dll->initialized = true; 63 | return success; 64 | } 65 | 66 | bool CDECL MsRdpEx_mstscax_Init(MsRdpEx_mstscax* dll) 67 | { 68 | bool success = false; 69 | char* axPath = NULL; 70 | 71 | if (dll->initialized) 72 | return true; 73 | 74 | axPath = MsRdpEx_GetEnv("MSRDPEX_MSTSCAX_DLL"); 75 | 76 | if (!axPath) 77 | axPath = _strdup(MsRdpEx_GetPath(MSRDPEX_MSTSCAX_DLL_PATH)); 78 | 79 | if (!axPath) 80 | goto exit; 81 | 82 | success = MsRdpEx_mstscax_Load(dll, axPath); 83 | exit: 84 | free(axPath); 85 | return success; 86 | } 87 | 88 | void CDECL MsRdpEx_mstscax_Uninit(MsRdpEx_mstscax* dll) 89 | { 90 | if (dll->hModule) { 91 | FreeLibrary(dll->hModule); 92 | dll->hModule = NULL; 93 | } 94 | 95 | ZeroMemory(dll, sizeof(MsRdpEx_mstscax)); 96 | } 97 | 98 | bool CDECL MsRdpEx_rdclientax_Load(MsRdpEx_rdclientax* dll, const char* filename) 99 | { 100 | bool success = false; 101 | 102 | if (dll->initialized) 103 | return true; 104 | 105 | ZeroMemory(dll, sizeof(MsRdpEx_rdclientax)); 106 | 107 | dll->hModule = MsRdpEx_LoadLibrary(filename); 108 | 109 | MsRdpEx_LogPrint(DEBUG, "rdclientax_load(%s): %p", filename, dll->hModule); 110 | 111 | if (!dll->hModule) 112 | goto exit; 113 | 114 | dll->DllCanUnloadNow = (fnDllCanUnloadNow)GetProcAddress(dll->hModule, "DllCanUnloadNow"); 115 | dll->DllGetClassObject = (fnDllGetClassObject)GetProcAddress(dll->hModule, "DllGetClassObject"); 116 | dll->DllRegisterServer = (fnDllRegisterServer)GetProcAddress(dll->hModule, "DllRegisterServer"); 117 | dll->DllUnregisterServer = (fnDllUnregisterServer)GetProcAddress(dll->hModule, "DllUnregisterServer"); 118 | dll->DllGetTscCtlVer = (fnDllGetTscCtlVer)GetProcAddress(dll->hModule, "DllGetTscCtlVer"); 119 | dll->DllGetNewActivityId = (fnDllGetNewActivityId)GetProcAddress(dll->hModule, "DllGetNewActivityId"); 120 | dll->DllSetAuthProperties = (fnDllSetAuthProperties)GetProcAddress(dll->hModule, "DllSetAuthProperties"); 121 | dll->DllGetClaimsToken = (fnDllGetClaimsToken19)GetProcAddress(dll->hModule, "DllGetClaimsToken"); 122 | dll->DllSetClaimsToken = (fnDllSetClaimsToken)GetProcAddress(dll->hModule, "DllSetClaimsToken"); 123 | dll->DllLogoffClaimsToken = (fnDllLogoffClaimsToken3)GetProcAddress(dll->hModule, "DllLogoffClaimsToken"); 124 | dll->DllCancelAuthentication = (fnDllCancelAuthentication)GetProcAddress(dll->hModule, "DllCancelAuthentication"); 125 | dll->DllDeleteSavedCreds = (fnDllDeleteSavedCreds)GetProcAddress(dll->hModule, "DllDeleteSavedCreds"); 126 | dll->DllPreCleanUp = (fnDllPreCleanUp)GetProcAddress(dll->hModule, "DllPreCleanUp"); 127 | 128 | dll->tscCtlVer = dll->DllGetTscCtlVer(); 129 | 130 | success = true; 131 | exit: 132 | dll->initialized = true; 133 | return success; 134 | } 135 | 136 | bool CDECL MsRdpEx_rdclientax_Init(MsRdpEx_rdclientax* dll) 137 | { 138 | bool success = false; 139 | char* axPath = NULL; 140 | 141 | if (dll->initialized) 142 | return true; 143 | 144 | axPath = MsRdpEx_GetEnv("MSRDPEX_RDCLIENTAX_DLL"); 145 | 146 | if (!axPath) 147 | axPath = _strdup(MsRdpEx_GetPath(MSRDPEX_RDCLIENTAX_DLL_PATH)); 148 | 149 | if (!axPath) 150 | goto exit; 151 | 152 | success = MsRdpEx_rdclientax_Load(dll, axPath); 153 | exit: 154 | free(axPath); 155 | return success; 156 | } 157 | 158 | void CDECL MsRdpEx_rdclientax_Uninit(MsRdpEx_rdclientax* dll) 159 | { 160 | if (dll->hModule) { 161 | FreeLibrary(dll->hModule); 162 | dll->hModule = NULL; 163 | } 164 | 165 | ZeroMemory(dll, sizeof(MsRdpEx_rdclientax)); 166 | } 167 | -------------------------------------------------------------------------------- /dll/AxHost/RdpAxHostWnd.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_AX_HOST_H 2 | #define MSRDPEX_AX_HOST_H 3 | 4 | #include 5 | 6 | int MsRdpEx_AxHost_WinMain( 7 | HINSTANCE hInstance, 8 | HINSTANCE hPrevInstance, 9 | LPWSTR lpCmdLine, 10 | int nCmdShow); 11 | 12 | #endif /* MSRDPEX_AX_HOST_H */ -------------------------------------------------------------------------------- /dll/AxHost/RdpComBase.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_COM_BASE_H 2 | #define MSRDPEX_COM_BASE_H 3 | 4 | #include "../ComHelpers.h" 5 | 6 | #include "../com/mstscax.tlh" 7 | using namespace MSTSCLib; 8 | 9 | #endif /* MSRDPEX_COM_BASE_H */ 10 | -------------------------------------------------------------------------------- /dll/AxHost/RdpEventSink.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "RdpEventSink.h" 3 | 4 | // IUnknown methods 5 | STDMETHODIMP CRdpEventSink::QueryInterface(REFIID riid, void** ppv) 6 | { 7 | HRESULT hr = S_OK; 8 | 9 | if (!ppv) 10 | return E_INVALIDARG; 11 | 12 | *ppv = NULL; 13 | 14 | if (riid == IID_IUnknown) { 15 | *ppv = this; 16 | } 17 | else if (riid == IID_IDispatch) { 18 | *ppv = this; 19 | } 20 | else if (riid == DIID_IMsTscAxEvents) { 21 | *ppv = this; 22 | } 23 | 24 | if (nullptr != *ppv) { 25 | ((IUnknown*)*ppv)->AddRef(); 26 | } 27 | else { 28 | hr = E_NOINTERFACE; 29 | } 30 | 31 | return hr; 32 | } 33 | 34 | STDMETHODIMP_(ULONG) CRdpEventSink::AddRef(void) 35 | { 36 | return InterlockedIncrement(&m_refCount); 37 | } 38 | 39 | STDMETHODIMP_(ULONG) CRdpEventSink::Release(void) 40 | { 41 | ULONG refCount = InterlockedDecrement(&m_refCount); 42 | 43 | if (refCount != 0) { 44 | return refCount; 45 | } 46 | 47 | delete this; 48 | return 0; 49 | } 50 | 51 | // IDispatch methods 52 | STDMETHODIMP CRdpEventSink::GetTypeInfoCount(UINT* pctinfo) 53 | { 54 | *pctinfo = 0; 55 | return S_OK; 56 | } 57 | 58 | STDMETHODIMP CRdpEventSink::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) 59 | { 60 | return E_NOTIMPL; 61 | } 62 | 63 | STDMETHODIMP CRdpEventSink::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, 64 | LCID lcid, DISPID* rgDispId) 65 | { 66 | return E_NOTIMPL; 67 | } 68 | 69 | STDMETHODIMP CRdpEventSink::Invoke(DISPID dispIdMember, 70 | REFIID riid, LCID lcid, WORD wFlags, 71 | DISPPARAMS* pDispParams, VARIANT* pVarResult, 72 | EXCEPINFO* pExcepInfo, UINT* puArgErr) 73 | { 74 | HRESULT hr = E_NOTIMPL; 75 | 76 | switch (dispIdMember) 77 | { 78 | case IMsTscAxEvents_OnConnectingId: 79 | hr = OnConnecting(); 80 | break; 81 | 82 | case IMsTscAxEvents_OnConnectedId: 83 | hr = OnConnected(); 84 | break; 85 | 86 | case IMsTscAxEvents_OnLoginCompleteId: 87 | hr = OnLoginComplete(); 88 | break; 89 | 90 | case IMsTscAxEvents_OnDisconnectedId: 91 | hr = OnDisconnected(pDispParams->rgvarg->lVal); 92 | break; 93 | 94 | case IMsTscAxEvents_OnEnterFullScreenModeId: 95 | hr = OnEnterFullScreenMode(); 96 | break; 97 | 98 | case IMsTscAxEvents_OnLeaveFullScreenModeId: 99 | hr = OnLeaveFullScreenMode(); 100 | break; 101 | 102 | case IMsTscAxEvents_OnRemoteDesktopSizeChangeId: 103 | hr = OnRemoteDesktopSizeChange(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal); 104 | break; 105 | 106 | case IMsTscAxEvents_OnRequestContainerMinimizeId: 107 | hr = OnRequestContainerMinimize(); 108 | break; 109 | 110 | case IMsTscAxEvents_OnConfirmCloseId: 111 | hr = OnConfirmClose(pDispParams->rgvarg[0].pboolVal); 112 | break; 113 | } 114 | 115 | return hr; 116 | } 117 | 118 | // IMsTscAxEvents methods 119 | STDMETHODIMP CRdpEventSink::OnConnecting() 120 | { 121 | return S_OK; 122 | } 123 | 124 | STDMETHODIMP CRdpEventSink::OnConnected() 125 | { 126 | return S_OK; 127 | } 128 | 129 | STDMETHODIMP CRdpEventSink::OnLoginComplete() 130 | { 131 | return S_OK; 132 | } 133 | 134 | STDMETHODIMP CRdpEventSink::OnDisconnected(long discReason) 135 | { 136 | return S_OK; 137 | } 138 | 139 | STDMETHODIMP CRdpEventSink::OnEnterFullScreenMode() 140 | { 141 | return S_OK; 142 | } 143 | 144 | STDMETHODIMP CRdpEventSink::OnLeaveFullScreenMode() 145 | { 146 | return S_OK; 147 | } 148 | 149 | STDMETHODIMP CRdpEventSink::OnRemoteDesktopSizeChange(long width, long height) 150 | { 151 | return S_OK; 152 | } 153 | 154 | STDMETHODIMP CRdpEventSink::OnRequestContainerMinimize() 155 | { 156 | return S_OK; 157 | } 158 | 159 | STDMETHODIMP CRdpEventSink::OnConfirmClose(VARIANT_BOOL* pfAllowClose) 160 | { 161 | *pfAllowClose = VARIANT_TRUE; 162 | return S_OK; 163 | } 164 | 165 | CRdpEventSink::CRdpEventSink(HWND hWndParent) 166 | { 167 | m_refCount = 0; 168 | m_hWndParent = hWndParent; 169 | } 170 | 171 | CRdpEventSink::~CRdpEventSink() 172 | { 173 | 174 | } 175 | -------------------------------------------------------------------------------- /dll/AxHost/RdpEventSink.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_EVENT_SINK_H 2 | #define MSRDPEX_EVENT_SINK_H 3 | 4 | #include 5 | 6 | #include "RdpComBase.h" 7 | 8 | #define IMsTscAxEvents_OnConnectingId 0x00000001 9 | #define IMsTscAxEvents_OnConnectedId 0x00000002 10 | #define IMsTscAxEvents_OnLoginCompleteId 0x00000003 11 | #define IMsTscAxEvents_OnDisconnectedId 0x00000004 12 | #define IMsTscAxEvents_OnEnterFullScreenModeId 0x00000005 13 | #define IMsTscAxEvents_OnLeaveFullScreenModeId 0x00000006 14 | #define IMsTscAxEvents_OnChannelReceivedDataId 0x00000007 15 | #define IMsTscAxEvents_OnRequestGoFullScreenId 0x00000008 16 | #define IMsTscAxEvents_OnRequestLeaveFullScreenId 0x00000009 17 | #define IMsTscAxEvents_OnFatalErrorId 0x0000000a 18 | #define IMsTscAxEvents_OnWarningId 0x0000000b 19 | #define IMsTscAxEvents_OnRemoteDesktopSizeChangeId 0x0000000c 20 | #define IMsTscAxEvents_OnIdleTimeoutNotificationId 0x0000000d 21 | #define IMsTscAxEvents_OnRequestContainerMinimizeId 0x0000000e 22 | #define IMsTscAxEvents_OnConfirmCloseId 0x0000000f 23 | #define IMsTscAxEvents_OnReceivedTSPublicKeyId 0x00000010 24 | #define IMsTscAxEvents_OnAutoReconnectingId 0x00000011 25 | #define IMsTscAxEvents_OnAuthenticationWarningDisplayedId 0x00000012 26 | #define IMsTscAxEvents_OnAuthenticationWarningDismissedId 0x00000013 27 | #define IMsTscAxEvents_OnRemoteProgramResultId 0x00000014 28 | #define IMsTscAxEvents_OnRemoteProgramDisplayedId 0x00000015 29 | #define IMsTscAxEvents_OnRemoteWindowDisplayedId 0x00000016 30 | #define IMsTscAxEvents_OnLogonErrorId 0x00000017 31 | #define IMsTscAxEvents_OnFocusReleasedId 0x00000018 32 | #define IMsTscAxEvents_OnUserNameAcquiredId 0x00000019 33 | #define IMsTscAxEvents_OnMouseInputModeChangedId 0x0000001a 34 | #define IMsTscAxEvents_OnServiceMessageReceivedId 0x0000001b 35 | #define IMsTscAxEvents_OnConnectionBarPullDownId 0x0000001c 36 | #define IMsTscAxEvents_OnNetworkStatusChangedId 0x0000001d 37 | #define IMsTscAxEvents_OnDevicesButtonPressedId 0x0000001e 38 | #define IMsTscAxEvents_OnAutoReconnectedId 0x0000001f 39 | #define IMsTscAxEvents_OnAutoReconnecting2Id 0x00000020 40 | 41 | class CRdpEventSink : public IMsTscAxEvents 42 | { 43 | public: 44 | // Constructor and Destructor 45 | CRdpEventSink(HWND hWndParent); 46 | virtual ~CRdpEventSink(); 47 | 48 | // IUnknown methods 49 | STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; 50 | STDMETHOD_(ULONG, AddRef)(void) override; 51 | STDMETHOD_(ULONG, Release)(void) override; 52 | 53 | // IDispatch methods 54 | STDMETHOD(GetTypeInfoCount)(UINT* pctinfo) override; 55 | STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override; 56 | STDMETHOD(GetIDsOfNames)(REFIID riid, 57 | LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) override; 58 | STDMETHOD(Invoke)(DISPID dispIdMember, 59 | REFIID riid, LCID lcid, WORD wFlags, 60 | DISPPARAMS* pDispParams, VARIANT* pVarResult, 61 | EXCEPINFO* pExcepInfo, UINT* puArgErr) override; 62 | 63 | // IMsTscAxEvents methods 64 | STDMETHOD(OnConnecting)(void); 65 | STDMETHOD(OnConnected)(void); 66 | STDMETHOD(OnLoginComplete)(void); 67 | STDMETHOD(OnDisconnected)(long discReason); 68 | STDMETHOD(OnEnterFullScreenMode)(void); 69 | STDMETHOD(OnLeaveFullScreenMode)(void); 70 | STDMETHOD(OnRemoteDesktopSizeChange)(long width, long height); 71 | STDMETHOD(OnRequestContainerMinimize)(void); 72 | STDMETHOD(OnConfirmClose)(VARIANT_BOOL* pfAllowClose); 73 | 74 | private: 75 | ULONG m_refCount; 76 | HWND m_hWndParent; 77 | }; 78 | 79 | #endif /* MSRDPEX_EVENT_SINK_H */ -------------------------------------------------------------------------------- /dll/AxHost/RdpOleSite.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "RdpOleSite.h" 3 | 4 | CRdpOleClientSite::CRdpOleClientSite(IUnknown* pUnkOuter) 5 | { 6 | m_refCount = 0; 7 | m_pUnkOuter = pUnkOuter; 8 | m_pUnkOuter->AddRef(); 9 | } 10 | 11 | CRdpOleClientSite::~CRdpOleClientSite() 12 | { 13 | SafeRelease(m_pUnkOuter); 14 | } 15 | 16 | // IUnknown methods 17 | STDMETHODIMP CRdpOleClientSite::QueryInterface(REFIID riid, void** ppv) 18 | { 19 | HRESULT hr = S_OK; 20 | 21 | if (!ppv) 22 | return E_INVALIDARG; 23 | 24 | *ppv = NULL; 25 | 26 | if (riid == IID_IUnknown) { 27 | *ppv = static_cast(this); 28 | } 29 | else if (riid == IID_IOleClientSite) { 30 | *ppv = static_cast(this); 31 | } 32 | else if (m_pUnkOuter) { 33 | return m_pUnkOuter->QueryInterface(riid, ppv); 34 | } 35 | else { 36 | hr = E_NOINTERFACE; 37 | } 38 | 39 | if (*ppv) { 40 | AddRef(); 41 | } 42 | 43 | return hr; 44 | } 45 | 46 | STDMETHODIMP_(ULONG) CRdpOleClientSite::AddRef() 47 | { 48 | return InterlockedIncrement(&m_refCount); 49 | } 50 | 51 | STDMETHODIMP_(ULONG) CRdpOleClientSite::Release() 52 | { 53 | ULONG refCount = InterlockedDecrement(&m_refCount); 54 | if (refCount == 0) { 55 | delete this; 56 | } 57 | return refCount; 58 | } 59 | 60 | // IOleClientSite methods 61 | STDMETHODIMP CRdpOleClientSite::SaveObject() 62 | { 63 | return S_OK; 64 | } 65 | 66 | STDMETHODIMP CRdpOleClientSite::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk) 67 | { 68 | *ppmk = NULL; 69 | return E_NOTIMPL; 70 | } 71 | 72 | STDMETHODIMP CRdpOleClientSite::GetContainer(IOleContainer** ppContainer) 73 | { 74 | *ppContainer = NULL; 75 | return E_NOTIMPL; 76 | } 77 | 78 | STDMETHODIMP CRdpOleClientSite::ShowObject() 79 | { 80 | return S_OK; 81 | } 82 | 83 | STDMETHODIMP CRdpOleClientSite::OnShowWindow(BOOL fShow) 84 | { 85 | return S_OK; 86 | } 87 | 88 | STDMETHODIMP CRdpOleClientSite::RequestNewObjectLayout() 89 | { 90 | return E_NOTIMPL; 91 | } 92 | 93 | CRdpOleInPlaceSiteEx::CRdpOleInPlaceSiteEx(IUnknown* pUnkOuter) 94 | : m_refCount(0), m_hWnd(0) 95 | { 96 | m_pUnkOuter = pUnkOuter; 97 | m_pUnkOuter->AddRef(); 98 | } 99 | 100 | CRdpOleInPlaceSiteEx::~CRdpOleInPlaceSiteEx() 101 | { 102 | SafeRelease(m_pUnkOuter); 103 | } 104 | 105 | // IUnknown methods 106 | STDMETHODIMP CRdpOleInPlaceSiteEx::QueryInterface(REFIID riid, void** ppv) 107 | { 108 | HRESULT hr = S_OK; 109 | 110 | if (!ppv) return E_INVALIDARG; 111 | *ppv = NULL; 112 | 113 | if (riid == IID_IUnknown) { 114 | *ppv = static_cast(this); 115 | } 116 | else if (riid == IID_IOleWindow || riid == IID_IOleInPlaceSite || riid == IID_IOleInPlaceSiteEx) { 117 | *ppv = static_cast(this); 118 | } 119 | else if (m_pUnkOuter) { 120 | return m_pUnkOuter->QueryInterface(riid, ppv); 121 | } 122 | else { 123 | hr = E_NOINTERFACE; 124 | } 125 | 126 | if (*ppv) { 127 | AddRef(); 128 | } 129 | 130 | return hr; 131 | } 132 | 133 | STDMETHODIMP_(ULONG) CRdpOleInPlaceSiteEx::AddRef() 134 | { 135 | return InterlockedIncrement(&m_refCount); 136 | } 137 | 138 | STDMETHODIMP_(ULONG) CRdpOleInPlaceSiteEx::Release() 139 | { 140 | ULONG refCount = InterlockedDecrement(&m_refCount); 141 | if (refCount == 0) { 142 | delete this; 143 | } 144 | return refCount; 145 | } 146 | 147 | // IOleWindow methods 148 | STDMETHODIMP CRdpOleInPlaceSiteEx::GetWindow(HWND* phwnd) 149 | { 150 | *phwnd = m_hWnd; 151 | return S_OK; 152 | } 153 | 154 | STDMETHODIMP CRdpOleInPlaceSiteEx::ContextSensitiveHelp(BOOL fEnterMode) 155 | { 156 | return S_OK; 157 | } 158 | 159 | // IOleInPlaceSite methods 160 | STDMETHODIMP CRdpOleInPlaceSiteEx::CanInPlaceActivate() 161 | { 162 | return S_OK; 163 | } 164 | 165 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnInPlaceActivate() 166 | { 167 | return S_OK; 168 | } 169 | 170 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnUIActivate() 171 | { 172 | return S_OK; 173 | } 174 | 175 | STDMETHODIMP CRdpOleInPlaceSiteEx::GetWindowContext(IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, 176 | LPRECT lprcPosRect, LPRECT lprcClipRect, 177 | LPOLEINPLACEFRAMEINFO lpFrameInfo) 178 | { 179 | RECT rect; 180 | 181 | *ppFrame = NULL; 182 | *ppDoc = NULL; 183 | lpFrameInfo = NULL; 184 | 185 | if (GetClientRect(m_hWnd, &rect)) 186 | { 187 | int width = rect.right - rect.left; 188 | int height = rect.bottom - rect.top; 189 | SetRect(lprcClipRect, 0, 0, width, height); 190 | SetRect(lprcPosRect, 0, 0, width, height); 191 | } 192 | 193 | return S_OK; 194 | } 195 | 196 | STDMETHODIMP CRdpOleInPlaceSiteEx::Scroll(SIZE scrollExtant) 197 | { 198 | return S_OK; 199 | } 200 | 201 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnUIDeactivate(BOOL fUndoable) 202 | { 203 | return S_OK; 204 | } 205 | 206 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnInPlaceDeactivate() 207 | { 208 | return S_OK; 209 | } 210 | 211 | STDMETHODIMP CRdpOleInPlaceSiteEx::DiscardUndoState() 212 | { 213 | return S_OK; 214 | } 215 | 216 | STDMETHODIMP CRdpOleInPlaceSiteEx::DeactivateAndUndo() 217 | { 218 | return S_OK; 219 | } 220 | 221 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnPosRectChange(LPCRECT lprcPosRect) 222 | { 223 | return S_OK; 224 | } 225 | 226 | // IOleInPlaceSiteEx methods 227 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnInPlaceActivateEx(BOOL* pfNoRedraw, DWORD dwFlags) 228 | { 229 | *pfNoRedraw = TRUE; 230 | return S_OK; 231 | } 232 | 233 | STDMETHODIMP CRdpOleInPlaceSiteEx::OnInPlaceDeactivateEx(BOOL fNoRedraw) 234 | { 235 | return S_OK; 236 | } 237 | 238 | STDMETHODIMP CRdpOleInPlaceSiteEx::RequestUIActivate() 239 | { 240 | return S_OK; 241 | } 242 | 243 | // additional methods 244 | STDMETHODIMP CRdpOleInPlaceSiteEx::SetWindow(HWND hWnd) 245 | { 246 | m_hWnd = hWnd; 247 | return S_OK; 248 | } 249 | -------------------------------------------------------------------------------- /dll/AxHost/RdpOleSite.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_OLE_SITE_H 2 | #define MSRDPEX_OLE_SITE_H 3 | 4 | #include 5 | 6 | #include "RdpComBase.h" 7 | 8 | class CRdpOleClientSite : public IOleClientSite { 9 | public: 10 | // Constructor and Destructor 11 | CRdpOleClientSite(IUnknown* pUnkOuter); 12 | virtual ~CRdpOleClientSite(); 13 | 14 | // IUnknown methods 15 | STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; 16 | STDMETHOD_(ULONG, AddRef)() override; 17 | STDMETHOD_(ULONG, Release)() override; 18 | 19 | // IOleClientSite methods 20 | STDMETHOD(SaveObject)() override; 21 | STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker** ppmk) override; 22 | STDMETHOD(GetContainer)(IOleContainer** ppContainer) override; 23 | STDMETHOD(ShowObject)() override; 24 | STDMETHOD(OnShowWindow)(BOOL fShow) override; 25 | STDMETHOD(RequestNewObjectLayout)() override; 26 | 27 | private: 28 | ULONG m_refCount; 29 | IUnknown* m_pUnkOuter; 30 | }; 31 | 32 | class CRdpOleInPlaceSiteEx : public IOleInPlaceSiteEx { 33 | public: 34 | // Constructor and Destructor 35 | CRdpOleInPlaceSiteEx(IUnknown* pUnkOuter); 36 | virtual ~CRdpOleInPlaceSiteEx(); 37 | 38 | // IUnknown methods 39 | STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override; 40 | STDMETHOD_(ULONG, AddRef)() override; 41 | STDMETHOD_(ULONG, Release)() override; 42 | 43 | // IOleWindow methods 44 | STDMETHOD(GetWindow)(HWND* phwnd) override; 45 | STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode) override; 46 | 47 | // IOleInPlaceSite methods 48 | STDMETHOD(CanInPlaceActivate)() override; 49 | STDMETHOD(OnInPlaceActivate)() override; 50 | STDMETHOD(OnUIActivate)() override; 51 | STDMETHOD(GetWindowContext)(IOleInPlaceFrame** ppFrame, 52 | IOleInPlaceUIWindow** ppDoc, 53 | LPRECT lprcPosRect, LPRECT lprcClipRect, 54 | LPOLEINPLACEFRAMEINFO lpFrameInfo) override; 55 | STDMETHOD(Scroll)(SIZE scrollExtant) override; 56 | STDMETHOD(OnUIDeactivate)(BOOL fUndoable) override; 57 | STDMETHOD(OnInPlaceDeactivate)() override; 58 | STDMETHOD(DiscardUndoState)() override; 59 | STDMETHOD(DeactivateAndUndo)() override; 60 | STDMETHOD(OnPosRectChange)(LPCRECT lprcPosRect) override; 61 | 62 | // IOleInPlaceSiteEx methods 63 | STDMETHOD(OnInPlaceActivateEx)(BOOL* pfNoRedraw, DWORD dwFlags) override; 64 | STDMETHOD(OnInPlaceDeactivateEx)(BOOL fNoRedraw) override; 65 | STDMETHOD(RequestUIActivate)() override; 66 | 67 | // Additional methods specific to your implementation 68 | STDMETHOD(SetWindow)(HWND hWnd); 69 | 70 | private: 71 | ULONG m_refCount; 72 | HWND m_hWnd; 73 | IUnknown* m_pUnkOuter; 74 | }; 75 | 76 | #endif /* MSRDPEX_OLE_SITE_H */ -------------------------------------------------------------------------------- /dll/AxHost/RdpWinMain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "RdpAxHostWnd.h" 5 | #include "RdpWinMain.h" 6 | 7 | static LPWSTR ProcessAxHostCommandLine(LPWSTR lpCmdLine, bool* axHost) 8 | { 9 | *axHost = false; 10 | LPCWSTR cmdArg = L"/axhost"; 11 | WCHAR* cmdLine = _wcsdup(lpCmdLine); 12 | WCHAR* found = wcsstr(cmdLine, cmdArg); 13 | 14 | if (found != NULL) { 15 | *axHost = true; 16 | memmove(found, found + wcslen(cmdArg), 17 | (wcslen(found + wcslen(cmdArg)) + 1) * sizeof(WCHAR)); 18 | } 19 | 20 | return cmdLine; 21 | } 22 | 23 | int MsRdpEx_WinMain( 24 | _In_ HINSTANCE hInstance, 25 | _In_opt_ HINSTANCE hPrevInstance, 26 | _In_ LPWSTR lpCmdLine, 27 | _In_ int nShowCmd, 28 | const char* axName) 29 | { 30 | HRESULT hr; 31 | int exitCode = 0; 32 | bool axHost = false; 33 | 34 | LPWSTR cmdLine = ProcessAxHostCommandLine(lpCmdLine, &axHost); 35 | 36 | if (axHost) 37 | { 38 | MsRdpEx_SetEnv("MSRDPEX_AXNAME", axName); 39 | exitCode = MsRdpEx_AxHost_WinMain(hInstance, hPrevInstance, cmdLine, nShowCmd); 40 | } 41 | else 42 | { 43 | hr = MsRdpEx_LaunchProcess(-1, NULL, NULL, axName); 44 | 45 | if (FAILED(hr)) { 46 | exitCode = -1; 47 | } 48 | } 49 | 50 | free(cmdLine); 51 | 52 | return exitCode; 53 | } 54 | -------------------------------------------------------------------------------- /dll/AxHost/RdpWinMain.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_WIN_MAIN_H 2 | #define MSRDPEX_WIN_MAIN_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | int MsRdpEx_WinMain( 11 | HINSTANCE hInstance, 12 | HINSTANCE hPrevInstance, 13 | LPWSTR lpCmdLine, 14 | int nCmdShow, 15 | const char* axName); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif /* MSRDPEX_WIN_MAIN_H */ 22 | -------------------------------------------------------------------------------- /dll/Bitmap.c: -------------------------------------------------------------------------------- 1 | 2 | #include "MsRdpEx.h" 3 | 4 | #include 5 | 6 | #pragma pack(push, 1) 7 | 8 | struct _MSRDPEX_BITMAP_FILE_HEADER 9 | { 10 | uint8_t bfType[2]; 11 | uint32_t bfSize; 12 | uint16_t bfReserved1; 13 | uint16_t bfReserved2; 14 | uint32_t bfOffBits; 15 | }; 16 | typedef struct _MSRDPEX_BITMAP_FILE_HEADER MSRDPEX_BITMAP_FILE_HEADER; 17 | 18 | struct _MSRDPEX_BITMAP_INFO_HEADER 19 | { 20 | uint32_t biSize; 21 | int32_t biWidth; 22 | int32_t biHeight; 23 | uint16_t biPlanes; 24 | uint16_t biBitCount; 25 | uint32_t biCompression; 26 | uint32_t biSizeImage; 27 | int32_t biXPelsPerMeter; 28 | int32_t biYPelsPerMeter; 29 | uint32_t biClrUsed; 30 | uint32_t biClrImportant; 31 | }; 32 | typedef struct _MSRDPEX_BITMAP_INFO_HEADER MSRDPEX_BITMAP_INFO_HEADER; 33 | 34 | struct _MSRDPEX_BITMAP_CORE_HEADER 35 | { 36 | uint32_t bcSize; 37 | uint16_t bcWidth; 38 | uint16_t bcHeight; 39 | uint16_t bcPlanes; 40 | uint16_t bcBitCount; 41 | }; 42 | typedef struct _MSRDPEX_BITMAP_CORE_HEADER MSRDPEX_BITMAP_CORE_HEADER; 43 | 44 | #pragma pack(pop) 45 | 46 | bool MsRdpEx_WriteBitmapFile(const char* filename, uint8_t* data, int width, int height, int bpp) 47 | { 48 | FILE* fp; 49 | bool success = true; 50 | MSRDPEX_BITMAP_FILE_HEADER bf; 51 | MSRDPEX_BITMAP_INFO_HEADER bi; 52 | 53 | ZeroMemory(&bf, sizeof(MSRDPEX_BITMAP_FILE_HEADER)); 54 | ZeroMemory(&bi, sizeof(MSRDPEX_BITMAP_INFO_HEADER)); 55 | 56 | fp = fopen(filename, "w+b"); 57 | 58 | if (!fp) 59 | return false; 60 | 61 | bf.bfType[0] = 'B'; 62 | bf.bfType[1] = 'M'; 63 | bf.bfReserved1 = 0; 64 | bf.bfReserved2 = 0; 65 | bf.bfOffBits = sizeof(MSRDPEX_BITMAP_FILE_HEADER) + sizeof(MSRDPEX_BITMAP_INFO_HEADER); 66 | bi.biSizeImage = width * height * (bpp / 8); 67 | bf.bfSize = bf.bfOffBits + bi.biSizeImage; 68 | 69 | bi.biWidth = width; 70 | bi.biHeight = -1 * height; 71 | bi.biPlanes = 1; 72 | bi.biBitCount = bpp; 73 | bi.biCompression = 0; 74 | bi.biXPelsPerMeter = 0; 75 | bi.biYPelsPerMeter = 0; 76 | bi.biClrUsed = 0; 77 | bi.biClrImportant = 0; 78 | bi.biSize = sizeof(MSRDPEX_BITMAP_INFO_HEADER); 79 | 80 | if (fwrite(&bf, sizeof(MSRDPEX_BITMAP_FILE_HEADER), 1, fp) != 1 || 81 | fwrite(&bi, sizeof(MSRDPEX_BITMAP_INFO_HEADER), 1, fp) != 1 || 82 | fwrite(data, bi.biSizeImage, 1, fp) != 1) 83 | { 84 | success = false; 85 | } 86 | 87 | fclose(fp); 88 | 89 | return success; 90 | } 91 | 92 | HBITMAP MsRdpEx_CreateDIBSection(HDC hDC, int width, int height, int bpp, uint8_t** ppPixelData) 93 | { 94 | BITMAPINFO bitmapInfo; 95 | ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO)); 96 | bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFO); 97 | bitmapInfo.bmiHeader.biWidth = width; 98 | bitmapInfo.bmiHeader.biHeight = -1 * height; 99 | bitmapInfo.bmiHeader.biPlanes = 1; 100 | bitmapInfo.bmiHeader.biBitCount = bpp; 101 | bitmapInfo.bmiHeader.biCompression = BI_RGB; 102 | return CreateDIBSection(hDC, &bitmapInfo, DIB_RGB_COLORS, (void**) ppPixelData, NULL, 0); 103 | } 104 | -------------------------------------------------------------------------------- /dll/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(MSRDPEX_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/include/MsRdpEx") 3 | 4 | set(MSRDPEX_RESOURCES 5 | MsRdpEx.rc) 6 | 7 | windows_rc_generate_version_info( 8 | NAME "MsRdpEx" TYPE "DLL" 9 | VERSION "${MSRDPEX_VERSION}" 10 | FILENAME "MsRdpEx.dll" 11 | VENDOR "${MSRDPEX_VENDOR}" 12 | COPYRIGHT "${MSRDPEX_COPYRIGHT}" 13 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.rc) 14 | 15 | source_group("Resources" FILES ${MSRDPEX_RESOURCES}) 16 | 17 | set(MSRDPEX_HEADERS 18 | "${MSRDPEX_INCLUDE_DIR}/Sspi.h" 19 | "${MSRDPEX_INCLUDE_DIR}/Stopwatch.h" 20 | "${MSRDPEX_INCLUDE_DIR}/Stream.h" 21 | "${MSRDPEX_INCLUDE_DIR}/Detours.h" 22 | "${MSRDPEX_INCLUDE_DIR}/Memory.h" 23 | "${MSRDPEX_INCLUDE_DIR}/Pcap.h" 24 | "${MSRDPEX_INCLUDE_DIR}/ArrayList.h" 25 | "${MSRDPEX_INCLUDE_DIR}/HashTable.h" 26 | "${MSRDPEX_INCLUDE_DIR}/KeyMaps.h" 27 | "${MSRDPEX_INCLUDE_DIR}/Environment.h" 28 | "${MSRDPEX_INCLUDE_DIR}/NameResolver.h" 29 | "${MSRDPEX_INCLUDE_DIR}/RdpFile.h" 30 | "${MSRDPEX_INCLUDE_DIR}/RdpCoreApi.h" 31 | "${MSRDPEX_INCLUDE_DIR}/RdpProcess.h" 32 | "${MSRDPEX_INCLUDE_DIR}/RdpInstance.h" 33 | "${MSRDPEX_INCLUDE_DIR}/RdpSettings.h" 34 | "${MSRDPEX_INCLUDE_DIR}/OutputMirror.h" 35 | "${MSRDPEX_INCLUDE_DIR}/VideoRecorder.h" 36 | "${MSRDPEX_INCLUDE_DIR}/RecordingManifest.h" 37 | "${MSRDPEX_INCLUDE_DIR}/NamedPipe.h" 38 | "${MSRDPEX_INCLUDE_DIR}/MsRdpEx.h") 39 | 40 | set(MSRDPEX_SOURCES 41 | ArrayList.c 42 | HashTable.c 43 | KeyMaps.cpp 44 | Environment.c 45 | NameResolver.c 46 | Stopwatch.c 47 | DpiHelper.cpp 48 | DpiHelper.h 49 | Sspi.cpp 50 | Stream.cpp 51 | Detours.cpp 52 | Memory.c 53 | File.c 54 | Log.c 55 | Paths.c 56 | String.c 57 | MsRdpClient.cpp 58 | MsRdpClient.h 59 | ApiHooks.cpp 60 | OutputMirror.c 61 | VideoRecorder.c 62 | RecordingManifest.c 63 | NamedPipe.c 64 | Pcap.cpp 65 | Bitmap.c 66 | WinMsg.c 67 | RdpFile.c 68 | RdpCoreApi.cpp 69 | RdpProcess.cpp 70 | RdpInstance.cpp 71 | RdpSettings.cpp 72 | RdpDvcClient.cpp 73 | RdpDvcClient.h 74 | TSObjects.cpp 75 | TSObjects.h 76 | MsRdpEx.cpp 77 | MsRdpEx.h 78 | AxDll.cpp) 79 | 80 | set(MSRDPEX_AXHOST_SOURCE_DIR "${CMAKE_SOURCE_DIR}/dll/AxHost") 81 | include_directories(${AXHOST_SOURCE_DIR}) 82 | 83 | set(MSRDPEX_AXHOST_SOURCES 84 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpAxHostWnd.cpp" 85 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpAxHostWnd.h" 86 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpEventSink.cpp" 87 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpEventSink.h" 88 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpOleSite.cpp" 89 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpOleSite.h" 90 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpComBase.h" 91 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpWinMain.cpp" 92 | "${MSRDPEX_AXHOST_SOURCE_DIR}/RdpWinMain.h") 93 | 94 | add_library(MsRdpEx_Dll SHARED 95 | ${MSRDPEX_SOURCES} 96 | ${MSRDPEX_HEADERS} 97 | ${MSRDPEX_RESOURCES} 98 | ${MSRDPEX_AXHOST_SOURCES} 99 | MsRdpEx.def) 100 | 101 | set(MSRDPEX_LIBS 102 | detours 103 | version.lib 104 | userenv.lib 105 | user32.lib 106 | rpcrt4.lib 107 | ws2_32.lib 108 | secur32.lib 109 | credui.lib 110 | advapi32.lib 111 | comctl32.lib) 112 | 113 | target_compile_options(MsRdpEx_Dll PRIVATE /W4) 114 | 115 | target_link_libraries(MsRdpEx_Dll ${MSRDPEX_LIBS}) 116 | 117 | set_target_properties(MsRdpEx_Dll PROPERTIES OUTPUT_NAME "MsRdpEx") 118 | -------------------------------------------------------------------------------- /dll/ComHelpers.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_COM_HELPERS_H 2 | #define MSRDPEX_COM_HELPERS_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef SafeRelease 11 | #define SafeRelease(_x) { if ((_x) != nullptr) { (_x)->Release(); (_x) = nullptr; } } 12 | #endif 13 | 14 | #ifndef ToVariantBool 15 | #define ToVariantBool(_b) ((_b) ? VARIANT_TRUE : VARIANT_FALSE) 16 | #endif 17 | 18 | #define VariantInitBool(pv, b) \ 19 | do { \ 20 | VariantInit((pv)); \ 21 | (pv)->vt = VT_BOOL; \ 22 | (pv)->boolVal = ((b) ? VARIANT_TRUE : VARIANT_FALSE); \ 23 | } while (0) 24 | 25 | #endif /* MSRDPEX_COM_HELPERS_H */ 26 | -------------------------------------------------------------------------------- /dll/Detours.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | -------------------------------------------------------------------------------- /dll/DpiHelper.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_DPI_HELPER_H 2 | #define MSRDPEX_DPI_HELPER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #ifndef SCALING_ENUMS_DECLARED 9 | 10 | typedef enum 11 | { 12 | DEVICE_PRIMARY = 0, 13 | DEVICE_IMMERSIVE = 1, 14 | } DISPLAY_DEVICE_TYPE; 15 | 16 | typedef enum 17 | { 18 | SCF_VALUE_NONE = 0x00, 19 | SCF_SCALE = 0x01, 20 | SCF_PHYSICAL = 0x02, 21 | } SCALE_CHANGE_FLAGS; 22 | 23 | DEFINE_ENUM_FLAG_OPERATORS(SCALE_CHANGE_FLAGS); 24 | 25 | #define SCALING_ENUMS_DECLARED 26 | #endif 27 | 28 | #ifndef DPI_ENUMS_DECLARED 29 | 30 | typedef enum PROCESS_DPI_AWARENESS 31 | { 32 | PROCESS_DPI_UNAWARE = 0, 33 | PROCESS_SYSTEM_DPI_AWARE = 1, 34 | PROCESS_PER_MONITOR_DPI_AWARE = 2 35 | } PROCESS_DPI_AWARENESS; 36 | 37 | typedef enum MONITOR_DPI_TYPE 38 | { 39 | MDT_EFFECTIVE_DPI = 0, 40 | MDT_ANGULAR_DPI = 1, 41 | MDT_RAW_DPI = 2, 42 | MDT_DEFAULT = MDT_EFFECTIVE_DPI 43 | } MONITOR_DPI_TYPE; 44 | 45 | #define DPI_ENUMS_DECLARED 46 | #endif 47 | 48 | #ifndef SHELL_UI_COMPONENT_ENUMS_DECLARED 49 | 50 | typedef enum 51 | { 52 | SHELL_UI_COMPONENT_TASKBARS = 0, 53 | SHELL_UI_COMPONENT_NOTIFICATIONAREA = 1, 54 | SHELL_UI_COMPONENT_DESKBAND = 2, 55 | } SHELL_UI_COMPONENT; 56 | 57 | #define SHELL_UI_COMPONENT_ENUMS_DECLARED 58 | #endif 59 | 60 | #define WM_DPICHANGED 0x02E0 61 | #define WM_GETDPISCALEDSIZE 0x02E4 62 | 63 | typedef DEVICE_SCALE_FACTOR(WINAPI * fnGetScaleFactorForDevice)(DISPLAY_DEVICE_TYPE deviceType); 64 | typedef HRESULT(WINAPI * fnRegisterScaleChangeNotifications)(DISPLAY_DEVICE_TYPE displayDevice, HWND hwndNotify, 65 | uint32_t uMsgNotify, DWORD* pdwCookie); 66 | typedef HRESULT(WINAPI * fnRevokeScaleChangeNotifications)(DISPLAY_DEVICE_TYPE displayDevice, DWORD dwCookie); 67 | typedef HRESULT(WINAPI * fnGetScaleFactorForMonitor)(HMONITOR hMonitor, DEVICE_SCALE_FACTOR* pScale); 68 | typedef HRESULT(WINAPI * fnRegisterScaleChangeEvent)(HANDLE hEvent, DWORD_PTR* pdwCookie); 69 | typedef HRESULT(WINAPI * fnUnregisterScaleChangeEvent)(DWORD_PTR dwCookie); 70 | typedef HRESULT(WINAPI * fnSetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value); 71 | typedef HRESULT(WINAPI * fnGetProcessDpiAwareness)(HANDLE hProcess, PROCESS_DPI_AWARENESS* value); 72 | typedef HRESULT(WINAPI * fnGetDpiForMonitor)(HMONITOR hMonitor, MONITOR_DPI_TYPE dpiType, uint32_t* dpiX, 73 | uint32_t* dpiY); 74 | typedef uint32_t(WINAPI * fnGetDpiForShellUIComponent)(SHELL_UI_COMPONENT component); 75 | typedef BOOL(WINAPI * fnAdjustWindowRectExForDpi)(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, 76 | UINT dpi); 77 | typedef BOOL(WINAPI * fnEnableNonClientDpiScaling)(HWND hwnd); 78 | 79 | struct dpi_helper 80 | { 81 | HMODULE hShCore; 82 | HMODULE hUser32; 83 | fnGetScaleFactorForDevice GetScaleFactorForDevice; 84 | fnRegisterScaleChangeNotifications RegisterScaleChangeNotifications; 85 | fnRevokeScaleChangeNotifications RevokeScaleChangeNotifications; 86 | fnGetScaleFactorForMonitor GetScaleFactorForMonitor; 87 | fnRegisterScaleChangeEvent RegisterScaleChangeEvent; 88 | fnUnregisterScaleChangeEvent UnregisterScaleChangeEvent; 89 | fnSetProcessDpiAwareness SetProcessDpiAwareness; 90 | fnGetProcessDpiAwareness GetProcessDpiAwareness; 91 | fnGetDpiForMonitor GetDpiForMonitor; 92 | fnGetDpiForShellUIComponent GetDpiForShellUIComponent; 93 | fnAdjustWindowRectExForDpi AdjustWindowRectExForDpi; 94 | fnEnableNonClientDpiScaling EnableNonClientDpiScaling; 95 | }; 96 | typedef struct dpi_helper DpiHelper; 97 | 98 | #ifdef __cplusplus 99 | extern "C" { 100 | #endif 101 | 102 | DpiHelper* DpiHelper_Get(); 103 | void DpiHelper_Release(); 104 | 105 | float DpiHelper_GetScaleFactor(int dpi); 106 | int DpiHelper_ScaleValue(int value, int dpi); 107 | 108 | DEVICE_SCALE_FACTOR MsRdpEx_GetScaleFactorForDevice(DISPLAY_DEVICE_TYPE deviceType); 109 | HRESULT MsRdpEx_RegisterScaleChangeNotifications(DISPLAY_DEVICE_TYPE displayDevice, HWND hwndNotify, uint32_t uMsgNotify, 110 | DWORD* pdwCookie); 111 | HRESULT MsRdpEx_RevokeScaleChangeNotifications(DISPLAY_DEVICE_TYPE displayDevice, DWORD dwCookie); 112 | HRESULT MsRdpEx_GetScaleFactorForMonitor(HMONITOR hMonitor, DEVICE_SCALE_FACTOR* pScale); 113 | HRESULT MsRdpEx_RegisterScaleChangeEvent(HANDLE hEvent, DWORD_PTR* pdwCookie); 114 | HRESULT MsRdpEx_UnregisterScaleChangeEvent(DWORD_PTR dwCookie); 115 | HRESULT MsRdpEx_SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value); 116 | HRESULT MsRdpEx_GetProcessDpiAwareness(HANDLE hProcess, PROCESS_DPI_AWARENESS* value); 117 | HRESULT MsRdpEx_GetDpiForMonitor(HMONITOR hMonitor, MONITOR_DPI_TYPE dpiType, uint32_t* dpiX, uint32_t* dpiY); 118 | uint32_t MsRdpEx_GetDpiForShellUIComponent(SHELL_UI_COMPONENT component); 119 | BOOL MsRdpEx_AdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, UINT dpi); 120 | BOOL MsRdpEx_EnableNonClientDpiScaling(HWND hwnd); 121 | 122 | #ifdef __cplusplus 123 | } 124 | #endif 125 | 126 | #endif /* MSRDPEX_DPI_HELPER_H */ 127 | -------------------------------------------------------------------------------- /dll/Environment.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | bool MsRdpEx_SetEnv(const char* name, const char* value) 7 | { 8 | bool result = false; 9 | WCHAR* nameW = NULL; 10 | WCHAR* valueW = NULL; 11 | 12 | if (!name) 13 | { 14 | goto exit; 15 | } 16 | 17 | if (MsRdpEx_ConvertToUnicode(CP_UTF8, 0, name, -1, &nameW, 0) < 1) { 18 | goto exit; 19 | } 20 | 21 | if (value) { // optional 22 | if (MsRdpEx_ConvertToUnicode(CP_UTF8, 0, value, -1, &valueW, 0) < 1) { 23 | goto exit; 24 | } 25 | } 26 | 27 | result = SetEnvironmentVariableW(nameW, valueW) ? true : false; 28 | 29 | exit: 30 | free(nameW); 31 | free(valueW); 32 | return result; 33 | } 34 | 35 | char* MsRdpEx_GetEnv(const char* name) 36 | { 37 | DWORD nSizeW; 38 | WCHAR* nameW = NULL; 39 | WCHAR* valueW = NULL; 40 | char* value = NULL; 41 | 42 | if (!name) 43 | { 44 | goto exit; 45 | } 46 | 47 | if (MsRdpEx_ConvertToUnicode(CP_UTF8, 0, name, -1, &nameW, 0) < 1) { 48 | goto exit; 49 | } 50 | 51 | nSizeW = GetEnvironmentVariableW(nameW, NULL, 0); 52 | 53 | if (!nSizeW) 54 | return NULL; 55 | 56 | valueW = (WCHAR*) malloc((nSizeW + 1) * 2); 57 | 58 | if (!valueW) { 59 | goto exit; 60 | } 61 | 62 | nSizeW = GetEnvironmentVariableW(nameW, valueW, nSizeW); 63 | 64 | valueW[nSizeW] = '\0'; 65 | 66 | if (MsRdpEx_ConvertFromUnicode(CP_UTF8, 0, valueW, -1, &value, 0, NULL, NULL) < 0) { 67 | goto exit; 68 | } 69 | 70 | exit: 71 | free(nameW); 72 | free(valueW); 73 | return value; 74 | } 75 | 76 | bool MsRdpEx_EnvExists(const char* name) 77 | { 78 | if (!name) 79 | return false; 80 | 81 | return GetEnvironmentVariableA(name, NULL, 0) ? true : false; 82 | } 83 | 84 | bool MsRdpEx_GetEnvBool(const char* name, bool defaultValue) 85 | { 86 | char* env; 87 | bool value = defaultValue; 88 | 89 | env = MsRdpEx_GetEnv(name); 90 | 91 | if (!env) 92 | return value; 93 | 94 | if ((strcmp(env, "1") == 0) || (_stricmp(env, "TRUE") == 0)) 95 | value = true; 96 | else if ((strcmp(env, "0") == 0) || (_stricmp(env, "FALSE") == 0)) 97 | value = false; 98 | 99 | free(env); 100 | 101 | return value; 102 | } 103 | 104 | int MsRdpEx_GetEnvInt(const char* name, int defaultValue) 105 | { 106 | char* env; 107 | int value = defaultValue; 108 | 109 | env = MsRdpEx_GetEnv(name); 110 | 111 | if (!env) 112 | return value; 113 | 114 | value = atoi(env); 115 | 116 | free(env); 117 | 118 | return value; 119 | } 120 | 121 | char** MsRdpEx_GetEnvironmentVariables(int* envc) 122 | { 123 | int count; 124 | int index; 125 | size_t length; 126 | char* env = NULL; 127 | char** envs = NULL; 128 | LPCWSTR pEnvW = NULL; 129 | LPVOID lpEnvBlock = NULL; 130 | 131 | if (!CreateEnvironmentBlock(&lpEnvBlock, NULL, TRUE)) { 132 | return NULL; 133 | } 134 | 135 | count = 0; 136 | pEnvW = (WCHAR*) lpEnvBlock; 137 | 138 | while (true) 139 | { 140 | length = wcslen(pEnvW); 141 | 142 | if (length < 1) 143 | break; 144 | 145 | pEnvW = &pEnvW[length + 1]; 146 | count++; 147 | } 148 | 149 | envs = (char**) calloc(count + 1, sizeof(char*)); 150 | 151 | if (!envs) 152 | goto fail; 153 | 154 | pEnvW = (WCHAR*) lpEnvBlock; 155 | 156 | for (index = 0; index < count; index++) 157 | { 158 | length = wcslen(pEnvW); 159 | 160 | env = NULL; 161 | 162 | if (MsRdpEx_ConvertFromUnicode(CP_UTF8, 0, pEnvW, -1, &env, 0, NULL, NULL) < 0) { 163 | goto fail; 164 | } 165 | 166 | envs[index] = env; 167 | 168 | pEnvW = &pEnvW[length + 1]; 169 | } 170 | 171 | *envc = count; 172 | 173 | DestroyEnvironmentBlock(lpEnvBlock); 174 | return envs; 175 | fail: 176 | DestroyEnvironmentBlock(lpEnvBlock); 177 | free(envs); 178 | return NULL; 179 | } 180 | 181 | void MsRdpEx_FreeEnvironmentVariables(int envc, char** envs) 182 | { 183 | int index; 184 | 185 | if (!envs) 186 | return; 187 | 188 | for (index = 0; index < envc; index++) { 189 | free(envs[index]); 190 | } 191 | 192 | free(envs); 193 | } 194 | 195 | char* MsRdpEx_ReadTextFromNamedPipe(const char* pipeName) 196 | { 197 | size_t size = 0; 198 | size_t length = 0; 199 | DWORD readBytes = 0; 200 | char* text = NULL; 201 | char* buffer = NULL; 202 | char* tempBuffer = NULL; 203 | HANDLE pipeHandle = NULL; 204 | char filename[MSRDPEX_MAX_PATH]; 205 | 206 | if (!pipeName) 207 | return NULL; 208 | 209 | sprintf_s(filename, sizeof(filename) - 1, "\\\\.\\pipe\\%s", pipeName); 210 | 211 | pipeHandle = CreateFileA(filename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); 212 | 213 | if (pipeHandle == INVALID_HANDLE_VALUE) { 214 | goto exit; 215 | } 216 | 217 | size = 1024; 218 | buffer = (char*) malloc(size); 219 | 220 | if (!buffer) 221 | goto exit; 222 | 223 | length = 0; 224 | while (1) { 225 | if (!ReadFile(pipeHandle, buffer + length, size - length, &readBytes, NULL) || (readBytes == 0)) { 226 | // If no bytes were read or an error occurred, break out of the loop. 227 | break; 228 | } 229 | 230 | length += readBytes; 231 | if ((size - length) <= 1) { 232 | tempBuffer = (char*) realloc(buffer, size * 2); 233 | if (!tempBuffer) { 234 | goto exit; 235 | } 236 | buffer = tempBuffer; 237 | size = size * 2; 238 | } 239 | } 240 | buffer[length] = '\0'; 241 | text = _strdup(buffer); 242 | 243 | exit: 244 | if (buffer) { 245 | SecureZeroMemory(buffer, size); 246 | free(buffer); 247 | } 248 | if (pipeHandle && (pipeHandle != INVALID_HANDLE_VALUE)) { 249 | CloseHandle(pipeHandle); 250 | } 251 | return text; 252 | } 253 | -------------------------------------------------------------------------------- /dll/KeyMaps.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct { 4 | const char* keyName; 5 | uint32_t vkCode; 6 | } KeyCodeMapping; 7 | 8 | #define VK_UNKNOWN 0 9 | 10 | #define VK_KEY_0 0x30 /* '0' key */ 11 | #define VK_KEY_1 0x31 /* '1' key */ 12 | #define VK_KEY_2 0x32 /* '2' key */ 13 | #define VK_KEY_3 0x33 /* '3' key */ 14 | #define VK_KEY_4 0x34 /* '4' key */ 15 | #define VK_KEY_5 0x35 /* '5' key */ 16 | #define VK_KEY_6 0x36 /* '6' key */ 17 | #define VK_KEY_7 0x37 /* '7' key */ 18 | #define VK_KEY_8 0x38 /* '8' key */ 19 | #define VK_KEY_9 0x39 /* '9' key */ 20 | 21 | #define VK_KEY_A 0x41 /* 'A' key */ 22 | #define VK_KEY_B 0x42 /* 'B' key */ 23 | #define VK_KEY_C 0x43 /* 'C' key */ 24 | #define VK_KEY_D 0x44 /* 'D' key */ 25 | #define VK_KEY_E 0x45 /* 'E' key */ 26 | #define VK_KEY_F 0x46 /* 'F' key */ 27 | #define VK_KEY_G 0x47 /* 'G' key */ 28 | #define VK_KEY_H 0x48 /* 'H' key */ 29 | #define VK_KEY_I 0x49 /* 'I' key */ 30 | #define VK_KEY_J 0x4A /* 'J' key */ 31 | #define VK_KEY_K 0x4B /* 'K' key */ 32 | #define VK_KEY_L 0x4C /* 'L' key */ 33 | #define VK_KEY_M 0x4D /* 'M' key */ 34 | #define VK_KEY_N 0x4E /* 'N' key */ 35 | #define VK_KEY_O 0x4F /* 'O' key */ 36 | #define VK_KEY_P 0x50 /* 'P' key */ 37 | #define VK_KEY_Q 0x51 /* 'Q' key */ 38 | #define VK_KEY_R 0x52 /* 'R' key */ 39 | #define VK_KEY_S 0x53 /* 'S' key */ 40 | #define VK_KEY_T 0x54 /* 'T' key */ 41 | #define VK_KEY_U 0x55 /* 'U' key */ 42 | #define VK_KEY_V 0x56 /* 'V' key */ 43 | #define VK_KEY_W 0x57 /* 'W' key */ 44 | #define VK_KEY_X 0x58 /* 'X' key */ 45 | #define VK_KEY_Y 0x59 /* 'Y' key */ 46 | #define VK_KEY_Z 0x5A /* 'Z' key */ 47 | 48 | #define VK_ABNT_C1 0xC1 /* Brazilian (ABNT) Keyboard */ 49 | #define VK_ABNT_C2 0xC2 /* Brazilian (ABNT) Keyboard */ 50 | 51 | // https://github.com/microsoft/vscode/blob/main/src/vs/base/common/keyCodes.ts 52 | 53 | static const KeyCodeMapping keyCodeTable[] = 54 | { 55 | { "None", VK_UNKNOWN }, 56 | { "Hyper", VK_UNKNOWN }, 57 | { "Super", VK_UNKNOWN }, 58 | { "Fn", VK_UNKNOWN }, 59 | { "FnLock", VK_UNKNOWN }, 60 | { "Suspend", VK_UNKNOWN }, 61 | { "Resume", VK_UNKNOWN }, 62 | { "Turbo", VK_UNKNOWN }, 63 | { "Sleep", VK_SLEEP }, 64 | { "WakeUp", VK_UNKNOWN }, 65 | 66 | { "Ctrl", VK_CONTROL }, 67 | { "Shift", VK_SHIFT }, 68 | { "Alt", VK_MENU }, 69 | 70 | { "A", VK_KEY_A }, 71 | { "B", VK_KEY_B }, 72 | { "C", VK_KEY_C }, 73 | { "D", VK_KEY_D }, 74 | { "E", VK_KEY_E }, 75 | { "F", VK_KEY_F }, 76 | { "G", VK_KEY_G }, 77 | { "H", VK_KEY_H }, 78 | { "I", VK_KEY_I }, 79 | { "J", VK_KEY_J }, 80 | { "K", VK_KEY_K }, 81 | { "L", VK_KEY_L }, 82 | { "M", VK_KEY_M }, 83 | { "N", VK_KEY_N }, 84 | { "O", VK_KEY_O }, 85 | { "P", VK_KEY_P }, 86 | { "Q", VK_KEY_Q }, 87 | { "R", VK_KEY_R }, 88 | { "S", VK_KEY_S }, 89 | { "T", VK_KEY_T }, 90 | { "U", VK_KEY_U }, 91 | { "V", VK_KEY_V }, 92 | { "W", VK_KEY_W }, 93 | { "X", VK_KEY_X }, 94 | { "Y", VK_KEY_Y }, 95 | { "Z", VK_KEY_Z }, 96 | 97 | { "0", VK_KEY_0 }, 98 | { "1", VK_KEY_1 }, 99 | { "2", VK_KEY_2 }, 100 | { "3", VK_KEY_3 }, 101 | { "4", VK_KEY_4 }, 102 | { "5", VK_KEY_5 }, 103 | { "6", VK_KEY_6 }, 104 | { "7", VK_KEY_7 }, 105 | { "8", VK_KEY_8 }, 106 | { "9", VK_KEY_9 }, 107 | 108 | { "Enter", VK_RETURN }, 109 | { "Escape", VK_ESCAPE }, 110 | { "Backspace", VK_BACK }, 111 | { "Tab", VK_TAB }, 112 | { "Space", VK_SPACE }, 113 | { "-", VK_OEM_MINUS }, 114 | { "=", VK_OEM_PLUS }, 115 | { "[", VK_OEM_4 }, 116 | { "]", VK_OEM_6 }, 117 | { "\\", VK_OEM_5 }, 118 | { ";", VK_OEM_1 }, 119 | { "\'", VK_OEM_7 }, 120 | { "`", VK_OEM_3 }, 121 | { ",", VK_OEM_COMMA }, 122 | { ".", VK_OEM_PERIOD }, 123 | { "/", VK_OEM_2 }, 124 | { "CapsLock", VK_CAPITAL }, 125 | 126 | { "F1", VK_F1 }, 127 | { "F2", VK_F2 }, 128 | { "F3", VK_F3 }, 129 | { "F4", VK_F4 }, 130 | { "F5", VK_F5 }, 131 | { "F6", VK_F6 }, 132 | { "F7", VK_F7 }, 133 | { "F8", VK_F8 }, 134 | { "F9", VK_F9 }, 135 | { "F10", VK_F10 }, 136 | { "F11", VK_F11 }, 137 | { "F12", VK_F12 }, 138 | 139 | { "PrintScreen", VK_UNKNOWN }, 140 | { "ScrollLock", VK_SCROLL }, 141 | { "PauseBreak", VK_PAUSE }, 142 | { "Insert", VK_INSERT }, 143 | { "Home", VK_HOME }, 144 | { "PageUp", VK_PRIOR }, 145 | { "Delete", VK_DELETE }, 146 | { "End", VK_END }, 147 | { "PageDown", VK_NEXT }, 148 | { "RightArrow", VK_RIGHT }, 149 | { "LeftArrow", VK_LEFT }, 150 | { "DownArrow", VK_DOWN }, 151 | { "UpArrow", VK_UP }, 152 | 153 | { "NumLock", VK_NUMLOCK }, 154 | { "NumPad_Divide", VK_DIVIDE }, 155 | { "NumPad_Subtract", VK_SUBTRACT }, 156 | { "NumPad_Add", VK_ADD }, 157 | { "NumPad_Enter", VK_UNKNOWN }, 158 | 159 | { "Numpad0", VK_NUMPAD0 }, 160 | { "Numpad1", VK_NUMPAD1 }, 161 | { "Numpad2", VK_NUMPAD2 }, 162 | { "Numpad3", VK_NUMPAD3 }, 163 | { "Numpad4", VK_NUMPAD4 }, 164 | { "Numpad5", VK_NUMPAD5 }, 165 | { "Numpad6", VK_NUMPAD6 }, 166 | { "Numpad7", VK_NUMPAD7 }, 167 | { "Numpad8", VK_NUMPAD8 }, 168 | { "Numpad9", VK_NUMPAD9 }, 169 | 170 | { "NumPad_Decimal", VK_DECIMAL }, 171 | { "Numpad_Equal", VK_UNKNOWN }, 172 | { "Numpad_Separator", VK_SEPARATOR }, 173 | { "Numpad_Clear", VK_CLEAR }, 174 | }; 175 | 176 | uint32_t MsRdpEx_KeyNameToVKCode(const char* keyName) 177 | { 178 | int numMappings = sizeof(keyCodeTable) / sizeof(keyCodeTable[0]); 179 | 180 | for (int i = 0; i < numMappings; i++) { 181 | if (_stricmp(keyCodeTable[i].keyName, keyName) == 0) { 182 | return keyCodeTable[i].vkCode; 183 | } 184 | } 185 | 186 | return VK_UNKNOWN; 187 | } 188 | -------------------------------------------------------------------------------- /dll/Log.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | static bool g_LogInitialized = false; 7 | 8 | static FILE* g_LogFile = NULL; 9 | static bool g_LogEnabled = false; 10 | static char g_LogFilePath[MSRDPEX_MAX_PATH] = { 0 }; 11 | 12 | static uint32_t g_LogLevel = MSRDPEX_LOG_DEBUG; 13 | 14 | #define MSRDPEX_LOG_MAX_LINE 8192 15 | 16 | LPCSTR LOG_LEVELS[7] = { "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "OFF" }; 17 | 18 | bool MsRdpEx_IsLogLevelActive(uint32_t logLevel) 19 | { 20 | if (!g_LogEnabled) 21 | return false; 22 | 23 | if (g_LogLevel == MSRDPEX_LOG_OFF) 24 | return false; 25 | 26 | return logLevel >= g_LogLevel; 27 | } 28 | 29 | bool MsRdpEx_LogVA(uint32_t level, const char* format, va_list args) 30 | { 31 | if (!g_LogFile) 32 | return true; 33 | 34 | SYSTEMTIME st; 35 | GetLocalTime(&st); 36 | 37 | DWORD pid = GetCurrentProcessId(); 38 | DWORD tid = GetCurrentThreadId(); 39 | 40 | char message[MSRDPEX_LOG_MAX_LINE]; 41 | vsnprintf_s(message, MSRDPEX_LOG_MAX_LINE - 1, _TRUNCATE, format, args); 42 | 43 | fprintf(g_LogFile, "[%s] %04d-%02d-%02d %02d:%02d:%02d.%03d PID:%lu TID:%lu - %s\n", 44 | LOG_LEVELS[level], 45 | st.wYear, st.wMonth, st.wDay, 46 | st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, 47 | pid, tid, 48 | message); 49 | fflush(g_LogFile); // WARNING: performance drag 50 | 51 | return true; 52 | } 53 | 54 | bool MsRdpEx_Log(uint32_t level, const char* format, ...) 55 | { 56 | bool status; 57 | va_list args; 58 | va_start(args, format); 59 | status = MsRdpEx_LogVA(level, format, args); 60 | va_end(args); 61 | return status; 62 | } 63 | 64 | void MsRdpEx_LogHexDump(const uint8_t* data, size_t size) 65 | { 66 | int i, ln, hn; 67 | const uint8_t* p = data; 68 | size_t width = 16; 69 | size_t offset = 0; 70 | size_t chunk = 0; 71 | char line[512] = { 0 }; 72 | char* bin2hex = "0123456789ABCDEF"; 73 | 74 | while (offset < size) { 75 | chunk = size - offset; 76 | 77 | if (chunk >= width) 78 | chunk = width; 79 | 80 | for (i = 0; i < chunk; i++) 81 | { 82 | ln = p[i] & 0xF; 83 | hn = (p[i] >> 4) & 0xF; 84 | 85 | line[i * 2] = bin2hex[hn]; 86 | line[(i * 2) + 1] = bin2hex[ln]; 87 | } 88 | 89 | line[chunk * 2] = ' '; 90 | 91 | for (i = chunk; i < width; i++) { 92 | line[i * 2] = ' '; 93 | line[(i * 2) + 1] = ' '; 94 | } 95 | 96 | char* side = &line[(width * 2) + 1]; 97 | 98 | for (i = 0; i < chunk; i++) 99 | { 100 | char c = ((p[i] >= 0x20) && (p[i] < 0x7F)) ? p[i] : '.'; 101 | side[i] = c; 102 | } 103 | side[i] = '\n'; 104 | side[i+1] = '\0'; 105 | 106 | if (g_LogFile) { 107 | fwrite(line, 1, strlen(line), g_LogFile); 108 | } 109 | 110 | offset += chunk; 111 | p += chunk; 112 | } 113 | } 114 | 115 | void MsRdpEx_LogEnvInit() 116 | { 117 | char* envvar; 118 | 119 | if (g_LogInitialized) 120 | return; 121 | 122 | bool logEnabled = MsRdpEx_EnvExists("MSRDPEX_LOG_LEVEL"); 123 | 124 | if (logEnabled) { 125 | // only set if true to avoid overriding current value 126 | MsRdpEx_SetLogEnabled(true); 127 | } 128 | 129 | envvar = MsRdpEx_GetEnv("MSRDPEX_LOG_LEVEL"); 130 | 131 | if (envvar) { 132 | 133 | if (MsRdpEx_StringIEquals(envvar, "TRACE")) 134 | { 135 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_TRACE); 136 | } 137 | else if (MsRdpEx_StringIEquals(envvar, "DEBUG")) 138 | { 139 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_DEBUG); 140 | } 141 | else if (MsRdpEx_StringIEquals(envvar, "INFO")) 142 | { 143 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_INFO); 144 | } 145 | else if (MsRdpEx_StringIEquals(envvar, "WARN")) 146 | { 147 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_WARN); 148 | } 149 | else if (MsRdpEx_StringIEquals(envvar, "ERROR")) 150 | { 151 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_ERROR); 152 | } 153 | else if (MsRdpEx_StringIEquals(envvar, "FATAL")) 154 | { 155 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_FATAL); 156 | } 157 | else if (MsRdpEx_StringIEquals(envvar, "OFF")) 158 | { 159 | MsRdpEx_SetLogLevel(MSRDPEX_LOG_OFF); 160 | } 161 | else 162 | { 163 | int ival = atoi(envvar); 164 | 165 | if ((ival >= 0) && (ival <= 6)) 166 | { 167 | MsRdpEx_SetLogLevel((uint32_t)ival); 168 | } 169 | } 170 | } 171 | 172 | free(envvar); 173 | 174 | envvar = MsRdpEx_GetEnv("MSRDPEX_LOG_FILE_PATH"); 175 | 176 | if (envvar) { 177 | MsRdpEx_SetLogFilePath(envvar); 178 | } 179 | 180 | free(envvar); 181 | 182 | g_LogInitialized = true; 183 | } 184 | 185 | void MsRdpEx_LogOpen() 186 | { 187 | MsRdpEx_LogEnvInit(); 188 | 189 | if (!g_LogEnabled) 190 | return; 191 | 192 | if (g_LogFilePath[0] == '\0') { 193 | const char* appDataPath = MsRdpEx_GetPath(MSRDPEX_APP_DATA_PATH); 194 | sprintf_s(g_LogFilePath, MSRDPEX_MAX_PATH, "%s\\MsRdpEx.log", appDataPath); 195 | } 196 | 197 | g_LogFile = MsRdpEx_FileOpen(g_LogFilePath, "wb"); 198 | } 199 | 200 | void MsRdpEx_LogClose() 201 | { 202 | if (g_LogFile) { 203 | fclose(g_LogFile); 204 | g_LogFile = NULL; 205 | } 206 | } 207 | 208 | void MsRdpEx_SetLogEnabled(bool logEnabled) 209 | { 210 | g_LogEnabled = logEnabled; 211 | } 212 | 213 | void MsRdpEx_SetLogLevel(uint32_t logLevel) 214 | { 215 | g_LogLevel = logLevel; 216 | } 217 | 218 | void MsRdpEx_SetLogFilePath(const char* logFilePath) 219 | { 220 | strcpy_s(g_LogFilePath, MSRDPEX_MAX_PATH, logFilePath); 221 | } 222 | -------------------------------------------------------------------------------- /dll/Memory.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | bool MsRdpEx_IsBadReadPtr(void* ptr, size_t size) 5 | { 6 | bool ok; 7 | DWORD mask; 8 | BYTE* p = (BYTE*) ptr; 9 | BYTE* maxPtr = p + size; 10 | BYTE* regionEnd = NULL; 11 | MEMORY_BASIC_INFORMATION mbi; 12 | 13 | if (size < 0) 14 | return false; 15 | 16 | if (!p) 17 | return true; 18 | 19 | mask = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; 20 | 21 | do 22 | { 23 | if (p == ptr || p == regionEnd) 24 | { 25 | if (VirtualQuery((LPCVOID)p, &mbi, sizeof(mbi)) == 0) 26 | { 27 | return true; 28 | } 29 | else 30 | { 31 | regionEnd = ((BYTE*) mbi.BaseAddress + mbi.RegionSize); 32 | } 33 | } 34 | 35 | ok = (mbi.Protect & mask) != 0; 36 | 37 | if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) 38 | { 39 | ok = false; 40 | } 41 | 42 | if (!ok) 43 | { 44 | return true; 45 | } 46 | 47 | if (maxPtr <= regionEnd) /* the whole address range is inside the current memory region */ 48 | { 49 | return false; 50 | } 51 | else if (maxPtr > regionEnd) /* this region is a part of (or overlaps with) the address range we are checking */ 52 | { 53 | p = regionEnd; /* lets move to the next memory region */ 54 | } 55 | } while (p < maxPtr); 56 | 57 | return false; 58 | } 59 | 60 | bool MsRdpEx_CanReadUnsafePtr(void* ptr, size_t size) 61 | { 62 | return MsRdpEx_IsBadReadPtr(ptr, size) ? false : true; 63 | } 64 | 65 | bool MsRdpEx_StringEqualsUnsafePtr(const char* ptr, const char* str) 66 | { 67 | size_t length = strlen(str); 68 | 69 | if (length < 1) 70 | return false; 71 | 72 | if (!MsRdpEx_CanReadUnsafePtr((void*) ptr, length + 1)) 73 | return false; 74 | 75 | return (strcmp(ptr, str) == 0) ? true : false; 76 | } 77 | 78 | bool MsRdpEx_StringIEqualsUnsafePtr(const char* ptr, const char* str) 79 | { 80 | size_t length = strlen(str); 81 | 82 | if (length < 1) 83 | return false; 84 | 85 | if (!MsRdpEx_CanReadUnsafePtr((void*)ptr, length + 1)) 86 | return false; 87 | 88 | return (_stricmp(ptr, str) == 0) ? true : false; 89 | } 90 | -------------------------------------------------------------------------------- /dll/MsRdpClient.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_CLIENT_H 2 | #define MSRDPEX_CLIENT_H 3 | 4 | #include "MsRdpEx.h" 5 | 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | void* MsRdpEx_CClassFactory_New(REFCLSID rclsid, IClassFactory* pDelegate); 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | 18 | #endif /* MSRDPEX_CLIENT_H */ 19 | -------------------------------------------------------------------------------- /dll/MsRdpEx.def: -------------------------------------------------------------------------------- 1 | LIBRARY "MsRdpEx" 2 | EXPORTS 3 | DllCanUnloadNow PRIVATE 4 | DllGetClassObject PRIVATE 5 | DllRegisterServer PRIVATE 6 | DllUnregisterServer PRIVATE 7 | DllGetTscCtlVer 8 | DllGetNewActivityId 9 | DllSetAuthProperties 10 | DllGetClaimsToken 11 | DllSetClaimsToken 12 | DllLogoffClaimsToken 13 | DllCancelAuthentication 14 | DllDeleteSavedCreds 15 | DllPreCleanUp 16 | MsRdpEx_GetClaimsToken 17 | MsRdpEx_LogoffClaimsToken 18 | MsRdpEx_CancelAuthentication 19 | MsRdpEx_DeleteSavedCreds 20 | MsRdpEx_PreCleanUp 21 | MsRdpEx_InitPaths 22 | MsRdpEx_GetPath 23 | MsRdpEx_CreateInstance 24 | MsRdpEx_LaunchProcess 25 | MsRdpExProcess_CreateInstance 26 | MsRdpEx_GetArgumentVector 27 | MsRdpEx_FreeArgumentVector 28 | MsRdpEx_WinMain -------------------------------------------------------------------------------- /dll/MsRdpEx.rc: -------------------------------------------------------------------------------- 1 | 2 | #include "version.rc" 3 | -------------------------------------------------------------------------------- /dll/NameResolver.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | struct _MsRdpEx_NameResolver 7 | { 8 | MsRdpEx_HashTable* renaming; 9 | }; 10 | 11 | void MsRdpEx_NameResolver_Free(MsRdpEx_NameResolver* ctx); 12 | 13 | static int g_RefCount = 0; 14 | static MsRdpEx_NameResolver* g_NameResolver = NULL; 15 | 16 | bool MsRdpEx_NameResolver_GetMapping(const char* oldName, char** pNewName) 17 | { 18 | MsRdpEx_NameResolver* ctx = g_NameResolver; 19 | 20 | if (!ctx) 21 | return false; 22 | 23 | char* newName = MsRdpEx_HashTable_GetItemValue(ctx->renaming, (void*) oldName); 24 | 25 | if (newName) { 26 | *pNewName = _strdup(newName); 27 | 28 | if (*pNewName != NULL) { 29 | return true; 30 | } 31 | } 32 | 33 | return false; 34 | } 35 | 36 | bool MsRdpEx_NameResolver_RemapName(const char* oldName, const char* newName) 37 | { 38 | MsRdpEx_NameResolver* ctx = g_NameResolver; 39 | 40 | if (!ctx) 41 | return false; 42 | 43 | MsRdpEx_LogPrint(DEBUG, "RemapName: %s -> %s", oldName, newName); 44 | 45 | MsRdpEx_HashTable_Add(ctx->renaming, (void*) oldName, (void*) newName); 46 | 47 | return true; 48 | } 49 | 50 | bool MsRdpEx_NameResolver_UnmapName(const char* name) 51 | { 52 | MsRdpEx_NameResolver* ctx = g_NameResolver; 53 | 54 | if (!ctx) 55 | return false; 56 | 57 | return MsRdpEx_HashTable_Remove(ctx->renaming, (void*) name); 58 | } 59 | 60 | MsRdpEx_NameResolver* MsRdpEx_NameResolver_New() 61 | { 62 | MsRdpEx_NameResolver* ctx; 63 | 64 | ctx = (MsRdpEx_NameResolver*) calloc(1, sizeof(MsRdpEx_NameResolver)); 65 | 66 | if (!ctx) 67 | return NULL; 68 | 69 | ctx->renaming = MsRdpEx_HashTable_New(true, MSRDPEX_HASHTABLE_FLAGS_NONE); 70 | 71 | if (!ctx->renaming) 72 | goto error; 73 | 74 | MsRdpEx_HashTable_SetHashFunction(ctx->renaming, MsRdpEx_HashTable_StringHash); 75 | MsRdpEx_HashTable_SetKeyCompareFunction(ctx->renaming, MsRdpEx_StringIEquals); 76 | MsRdpEx_HashTable_SetKeyCloneFunction(ctx->renaming, MsRdpEx_HashTable_StringClone); 77 | MsRdpEx_HashTable_SetKeyFreeFunction(ctx->renaming, MsRdpEx_HashTable_StringFree); 78 | MsRdpEx_HashTable_SetValueCompareFunction(ctx->renaming, MsRdpEx_StringIEquals); 79 | MsRdpEx_HashTable_SetValueCloneFunction(ctx->renaming, MsRdpEx_HashTable_StringClone); 80 | MsRdpEx_HashTable_SetValueFreeFunction(ctx->renaming, MsRdpEx_HashTable_StringFree); 81 | 82 | return ctx; 83 | error: 84 | MsRdpEx_NameResolver_Free(ctx); 85 | return NULL; 86 | } 87 | 88 | void MsRdpEx_NameResolver_Free(MsRdpEx_NameResolver* ctx) 89 | { 90 | if (!ctx) 91 | return; 92 | 93 | if (ctx->renaming) { 94 | MsRdpEx_HashTable_Free(ctx->renaming); 95 | ctx->renaming = NULL; 96 | } 97 | } 98 | 99 | MsRdpEx_NameResolver* MsRdpEx_NameResolver_Get() 100 | { 101 | if (!g_NameResolver) 102 | g_NameResolver = MsRdpEx_NameResolver_New(true); 103 | 104 | g_RefCount++; 105 | 106 | return g_NameResolver; 107 | } 108 | 109 | void MsRdpEx_NameResolver_Release() 110 | { 111 | g_RefCount--; 112 | 113 | if (g_RefCount < 0) 114 | g_RefCount = 0; 115 | 116 | if (g_NameResolver && (g_RefCount < 1)) 117 | { 118 | MsRdpEx_NameResolver_Free(g_NameResolver); 119 | g_NameResolver = NULL; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /dll/NamedPipe.c: -------------------------------------------------------------------------------- 1 | 2 | #include "MsRdpEx.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #define XMF_NAMED_PIPE_BUFFER_SIZE 8192 10 | 11 | int MsRdpEx_NamedPipe_Read(HANDLE np_handle, uint8_t* data, size_t size) 12 | { 13 | DWORD cb_read = 0; 14 | 15 | if (!ReadFile(np_handle, data, size, &cb_read, NULL)) { 16 | return -1; 17 | } 18 | 19 | return (int) cb_read; 20 | } 21 | 22 | int MsRdpEx_NamedPipe_Write(HANDLE np_handle, const uint8_t* data, size_t size) 23 | { 24 | DWORD cb_write = 0; 25 | 26 | if (!WriteFile(np_handle, (void*)data, (DWORD)size, &cb_write, NULL)) { 27 | return -1; 28 | } 29 | 30 | return (int) cb_write; 31 | } 32 | 33 | HANDLE MsRdpEx_NamedPipe_Open(const char* np_name) 34 | { 35 | HANDLE np_handle; 36 | char filename[MSRDPEX_MAX_PATH]; 37 | 38 | if (!np_name) 39 | return NULL; 40 | 41 | sprintf_s(filename, sizeof(filename) - 1, "\\\\.\\pipe\\%s", np_name); 42 | 43 | np_handle = CreateFileA(filename, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); 44 | 45 | if (np_handle == INVALID_HANDLE_VALUE) { 46 | return NULL; 47 | } 48 | 49 | return np_handle; 50 | } 51 | 52 | HANDLE MsRdpEx_NamedPipe_Create(const char* np_name, int max_clients) 53 | { 54 | HANDLE np_handle; 55 | char filename[MSRDPEX_MAX_PATH]; 56 | 57 | if (!np_name) 58 | return NULL; 59 | 60 | sprintf_s(filename, sizeof(filename) - 1, "\\\\.\\pipe\\%s", np_name); 61 | 62 | np_handle = CreateNamedPipeA(filename, 63 | PIPE_ACCESS_DUPLEX, 64 | PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 65 | max_clients, 66 | XMF_NAMED_PIPE_BUFFER_SIZE, 67 | XMF_NAMED_PIPE_BUFFER_SIZE, 68 | 0, NULL); 69 | 70 | if (np_handle == INVALID_HANDLE_VALUE) { 71 | return NULL; 72 | } 73 | 74 | return np_handle; 75 | } 76 | 77 | HANDLE MsRdpEx_NamedPipe_Accept(HANDLE np_handle) 78 | { 79 | if (!ConnectNamedPipe(np_handle, NULL)) { 80 | return NULL; 81 | } 82 | return np_handle; 83 | } 84 | 85 | 86 | void MsRdpEx_NamedPipe_Close(HANDLE np_handle) 87 | { 88 | CloseHandle(np_handle); 89 | } 90 | -------------------------------------------------------------------------------- /dll/RdpCoreApi.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "MsRdpEx.h" 8 | 9 | extern "C" const GUID IID_IMsRdpExCoreApi; 10 | extern "C" const GUID IID_IMsRdpExInstance; 11 | extern "C" const GUID IID_IMsRdpExProcess; 12 | 13 | class CMsRdpExCoreApi : public IMsRdpExCoreApi 14 | { 15 | public: 16 | CMsRdpExCoreApi() 17 | { 18 | m_refCount = 0; 19 | MsRdpEx_PathCchDetect(m_MsRdpExDllPath, MSRDPEX_MAX_PATH, MSRDPEX_CURRENT_LIBRARY_PATH); 20 | } 21 | 22 | ~CMsRdpExCoreApi() 23 | { 24 | 25 | } 26 | 27 | // IUnknown interface 28 | public: 29 | HRESULT STDMETHODCALLTYPE QueryInterface( 30 | REFIID riid, 31 | LPVOID* ppvObject 32 | ) 33 | { 34 | HRESULT hr = E_NOINTERFACE; 35 | ULONG refCount = m_refCount; 36 | char iid[MSRDPEX_GUID_STRING_SIZE]; 37 | MsRdpEx_GuidBinToStr((GUID*)&riid, iid, 0); 38 | 39 | if (riid == IID_IUnknown) 40 | { 41 | *ppvObject = (LPVOID)((IUnknown*)this); 42 | refCount = InterlockedIncrement(&m_refCount); 43 | hr = S_OK; 44 | } 45 | else if (riid == IID_IMsRdpExCoreApi) 46 | { 47 | *ppvObject = (LPVOID)((IUnknown*)this); 48 | refCount = InterlockedIncrement(&m_refCount); 49 | hr = S_OK; 50 | } 51 | 52 | MsRdpEx_LogPrint(DEBUG, "CMsRdpExCoreApi::QueryInterface(%s) = 0x%08X, %d", iid, hr, refCount); 53 | 54 | return hr; 55 | } 56 | 57 | ULONG STDMETHODCALLTYPE AddRef() 58 | { 59 | ULONG refCount = InterlockedIncrement(&m_refCount); 60 | MsRdpEx_LogPrint(DEBUG, "CMsRdpExCoreApi::AddRef() = %d", refCount); 61 | return refCount; 62 | } 63 | 64 | ULONG STDMETHODCALLTYPE Release() 65 | { 66 | ULONG refCount = InterlockedDecrement(&m_refCount); 67 | 68 | MsRdpEx_LogPrint(DEBUG, "CMsRdpExCoreApi::Release() = %d", refCount); 69 | 70 | if (refCount == 0) 71 | { 72 | delete this; 73 | return 0; 74 | } 75 | 76 | return refCount; 77 | } 78 | 79 | // IMsRdpExCoreApi 80 | public: 81 | HRESULT __stdcall Load() 82 | { 83 | MsRdpEx_LogPrint(DEBUG, "CMsRdpExCoreApi::Load"); 84 | return S_OK; 85 | } 86 | 87 | HRESULT __stdcall Unload() 88 | { 89 | MsRdpEx_LogPrint(DEBUG, "CMsRdpExCoreApi::Unload"); 90 | return S_OK; 91 | } 92 | 93 | const char* __stdcall GetMsRdpExDllPath() 94 | { 95 | return (const char*) m_MsRdpExDllPath; 96 | } 97 | 98 | void __stdcall SetLogEnabled(bool logEnabled) 99 | { 100 | MsRdpEx_SetLogEnabled(logEnabled); 101 | } 102 | 103 | void __stdcall SetLogLevel(uint32_t logLevel) 104 | { 105 | MsRdpEx_SetLogLevel(logLevel); 106 | } 107 | 108 | void __stdcall SetLogFilePath(const char* logFilePath) 109 | { 110 | MsRdpEx_SetLogFilePath(logFilePath); 111 | } 112 | 113 | void __stdcall SetPcapEnabled(bool pcapEnabled) 114 | { 115 | MsRdpEx_SetPcapEnabled(pcapEnabled); 116 | } 117 | 118 | void __stdcall SetPcapFilePath(const char* pcapFilePath) 119 | { 120 | MsRdpEx_SetPcapFilePath(pcapFilePath); 121 | } 122 | 123 | void __stdcall SetAxHookEnabled(bool axHookEnabled) 124 | { 125 | MsRdpEx_SetAxHookEnabled(axHookEnabled); 126 | } 127 | 128 | bool __stdcall QueryInstanceByWindowHandle(HWND hWnd, LPVOID* ppvObject) 129 | { 130 | HRESULT hr; 131 | IMsRdpExInstance* instance = NULL; 132 | 133 | instance = (IMsRdpExInstance*) MsRdpEx_InstanceManager_FindByOutputPresenterHwnd(hWnd); 134 | 135 | if (!instance) 136 | return false; 137 | 138 | hr = instance->QueryInterface(IID_IMsRdpExInstance, ppvObject); 139 | 140 | return (hr == S_OK) ? true : false; 141 | } 142 | 143 | bool __stdcall OpenInstanceForWindowHandle(HWND hWnd, LPVOID* ppvObject) 144 | { 145 | HRESULT hr; 146 | IMsRdpExInstance* instance = NULL; 147 | 148 | instance = (IMsRdpExInstance*) MsRdpEx_InstanceManager_FindByOutputPresenterHwnd(hWnd); 149 | 150 | if (!instance) 151 | { 152 | instance = (IMsRdpExInstance*) CMsRdpExInstance_New(NULL); 153 | instance->AttachOutputWindow(hWnd, NULL); 154 | MsRdpEx_InstanceManager_Add((CMsRdpExInstance*) instance); 155 | } 156 | 157 | hr = instance->QueryInterface(IID_IMsRdpExInstance, ppvObject); 158 | 159 | return (hr == S_OK) ? true : false; 160 | } 161 | 162 | private: 163 | ULONG m_refCount; 164 | char m_MsRdpExDllPath[MSRDPEX_MAX_PATH] = { 0 }; 165 | }; 166 | 167 | HRESULT CDECL MsRdpExCoreApi_CreateInstance(LPVOID* ppvObject) 168 | { 169 | CMsRdpExCoreApi* pObj = new CMsRdpExCoreApi(); 170 | *ppvObject = (LPVOID) pObj; 171 | return S_OK; 172 | } 173 | 174 | HRESULT CDECL MsRdpEx_CreateInstance(REFCLSID riid, LPVOID* ppvObject) 175 | { 176 | IUnknown* pUnknown = NULL; 177 | HRESULT hr = E_NOINTERFACE; 178 | 179 | char iid[MSRDPEX_GUID_STRING_SIZE]; 180 | MsRdpEx_GuidBinToStr((GUID*)&riid, iid, 0); 181 | 182 | MsRdpEx_LogPrint(DEBUG, "MsRdpEx_CreateInstance(%s)", iid); 183 | 184 | if (riid == IID_IMsRdpExCoreApi) { 185 | hr = MsRdpExCoreApi_CreateInstance((LPVOID*) &pUnknown); 186 | } 187 | else if (riid == IID_IMsRdpExProcess) { 188 | hr = MsRdpExProcess_CreateInstance((LPVOID*) &pUnknown); 189 | } 190 | 191 | if (hr == S_OK) { 192 | hr = pUnknown->QueryInterface(riid, ppvObject); 193 | } 194 | 195 | return hr; 196 | } 197 | -------------------------------------------------------------------------------- /dll/RdpDvcClient.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_DVC_CLIENT_H 2 | #define MSRDPEX_DVC_CLIENT_H 3 | 4 | #include "MsRdpEx.h" 5 | 6 | #include 7 | 8 | class CRdpDvcClient : 9 | public IWTSVirtualChannelCallback 10 | { 11 | public: 12 | // IUnknown methods 13 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override; 14 | ULONG STDMETHODCALLTYPE AddRef() override; 15 | ULONG STDMETHODCALLTYPE Release() override; 16 | 17 | // IWTSVirtualChannelCallback methods 18 | HRESULT STDMETHODCALLTYPE OnDataReceived(ULONG cbSize, BYTE* pBuffer) override; 19 | HRESULT STDMETHODCALLTYPE OnClose(void) override; 20 | 21 | // Additional methods 22 | void SetChannel(IWTSVirtualChannel* pChannel); 23 | void SetListener(IWTSListener* pListener); 24 | 25 | CRdpDvcClient(void); 26 | virtual ~CRdpDvcClient(); 27 | 28 | private: 29 | ULONG m_refCount = 0; 30 | IWTSVirtualChannel* m_pChannel = NULL; 31 | IWTSListener* m_pListener = NULL; 32 | }; 33 | 34 | class CRdpDvcListener : 35 | public IWTSListenerCallback 36 | { 37 | public: 38 | // IUnknown methods 39 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override; 40 | ULONG STDMETHODCALLTYPE AddRef() override; 41 | ULONG STDMETHODCALLTYPE Release() override; 42 | 43 | // IWTSListenerCallback methods 44 | HRESULT STDMETHODCALLTYPE OnNewChannelConnection(IWTSVirtualChannel* pChannel, 45 | BSTR data, BOOL* pbAccept, IWTSVirtualChannelCallback** ppCallback) override; 46 | 47 | // Additional methods 48 | CRdpDvcListener(void); 49 | virtual ~CRdpDvcListener(); 50 | private: 51 | ULONG m_refCount = 0; 52 | }; 53 | 54 | class CRdpDvcPlugin : 55 | public IWTSPlugin 56 | { 57 | public: 58 | // IUnknown methods 59 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override; 60 | ULONG STDMETHODCALLTYPE AddRef() override; 61 | ULONG STDMETHODCALLTYPE Release() override; 62 | 63 | // IWTSPlugin methods 64 | HRESULT STDMETHODCALLTYPE Initialize(IWTSVirtualChannelManager* pChannelMgr) override; 65 | HRESULT STDMETHODCALLTYPE Connected() override; 66 | HRESULT STDMETHODCALLTYPE Disconnected(DWORD dwDisconnectCode) override; 67 | HRESULT STDMETHODCALLTYPE Terminated() override; 68 | 69 | // Additional methods 70 | CRdpDvcPlugin(void); 71 | virtual ~CRdpDvcPlugin(); 72 | 73 | private: 74 | ULONG m_refCount = 0; 75 | IWTSVirtualChannel* m_pChannel = NULL; 76 | }; 77 | 78 | HRESULT STDAPICALLTYPE DllGetClassObject_DvcPlugin(REFCLSID rclsid, REFIID riid, LPVOID* ppv, void* instance); 79 | 80 | #endif /* MSRDPEX_DVC_CLIENT_H */ 81 | -------------------------------------------------------------------------------- /dll/RdpSettings.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/dll/RdpSettings.cpp -------------------------------------------------------------------------------- /dll/Stopwatch.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | static bool g_ProfilingInitialized = false; 6 | static uint32_t g_ProfilingLevel = MSRDPEX_PROF_OFF; 7 | 8 | static bool MsRdpEx_ProfilingLevelActive(uint32_t profilingLevel) 9 | { 10 | if (g_ProfilingLevel == MSRDPEX_PROF_OFF) 11 | return false; 12 | 13 | return profilingLevel >= g_ProfilingLevel; 14 | } 15 | 16 | static void MsRdpEx_SetProfilingLevel(uint32_t profilingLevel) 17 | { 18 | g_ProfilingLevel = profilingLevel; 19 | } 20 | 21 | void MsRdpEx_ProfilingInit() 22 | { 23 | char* envvar = NULL; 24 | 25 | if (g_ProfilingInitialized) 26 | return; 27 | 28 | if (!MsRdpEx_EnvExists("MSRDPEX_PROF_LEVEL")) 29 | { 30 | return; 31 | } 32 | 33 | envvar = MsRdpEx_GetEnv("MSRDPEX_PROF_LEVEL"); 34 | 35 | if (envvar) 36 | { 37 | int ival = atoi(envvar); 38 | 39 | if ((ival >= 0) && (ival <= 3)) 40 | { 41 | MsRdpEx_SetProfilingLevel((uint32_t)ival); 42 | } 43 | } 44 | 45 | free(envvar); 46 | 47 | g_ProfilingInitialized = true; 48 | } 49 | 50 | void MsRdpEx_Stopwatch_Init(MsRdpEx_Stopwatch* stopwatch, uint32_t profilingLevel, bool start) 51 | { 52 | MsRdpEx_Stopwatch_InitEx(stopwatch, profilingLevel, start, true); 53 | } 54 | 55 | void MsRdpEx_Stopwatch_InitEx(MsRdpEx_Stopwatch* stopwatch, uint32_t profilingLevel, bool start, bool highPrecision) 56 | { 57 | stopwatch->enabled = MsRdpEx_ProfilingLevelActive(profilingLevel); 58 | stopwatch->highPrecision = highPrecision; 59 | 60 | if (start) 61 | { 62 | MsRdpEx_Stopwatch_Start(stopwatch); 63 | } 64 | } 65 | 66 | void MsRdpEx_Stopwatch_Start(MsRdpEx_Stopwatch* stopwatch) 67 | { 68 | if (stopwatch->enabled) 69 | { 70 | if (stopwatch->highPrecision) 71 | { 72 | QueryPerformanceCounter(&stopwatch->time); 73 | } 74 | else 75 | { 76 | stopwatch->tickCount = GetTickCount64(); 77 | } 78 | } 79 | } 80 | 81 | double MsRdpEx_Stopwatch_GetTime(MsRdpEx_Stopwatch* stopwatch) 82 | { 83 | if (stopwatch->enabled) 84 | { 85 | if (stopwatch->highPrecision) 86 | { 87 | LARGE_INTEGER frequency; 88 | LARGE_INTEGER stop; 89 | LARGE_INTEGER elapsed; 90 | 91 | QueryPerformanceFrequency(&frequency); 92 | QueryPerformanceCounter(&stop); 93 | 94 | elapsed.QuadPart = stop.QuadPart - stopwatch->time.QuadPart; 95 | elapsed.QuadPart *= 1000; 96 | elapsed.QuadPart /= frequency.QuadPart; 97 | 98 | return (double)elapsed.QuadPart; 99 | } 100 | else 101 | { 102 | ULONGLONG elapsed = GetTickCount64() - stopwatch->tickCount; 103 | return (double) elapsed; 104 | } 105 | } 106 | 107 | return -1; 108 | } 109 | 110 | void MsRdpEx_Stopwatch_Print(MsRdpEx_Stopwatch* stopwatch, uint32_t logLevel, const char* message) 111 | { 112 | if (!stopwatch->enabled) 113 | { 114 | return; 115 | } 116 | 117 | if (!MsRdpEx_IsLogLevelActive(logLevel)) 118 | { 119 | return; 120 | } 121 | 122 | MsRdpEx_Log(logLevel, "%s [%.3fms]", message, MsRdpEx_Stopwatch_GetTime(stopwatch)); 123 | } 124 | -------------------------------------------------------------------------------- /dll/Stream.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | bool MsRdpEx_Stream_Find(MsRdpEx_Stream* s, const uint8_t* pattern, size_t pattern_length, size_t* offset) 5 | { 6 | MsRdpEx_Stream* _s = NULL; 7 | bool result = false; 8 | 9 | if (MsRdpEx_Stream_GetRemainingLength(s) < pattern_length) 10 | return false; 11 | 12 | _s = MsRdpEx_Stream_New(s->pointer, MsRdpEx_Stream_GetRemainingLength(s), false); 13 | 14 | while (MsRdpEx_Stream_CheckSafeRead(_s, pattern_length)) 15 | { 16 | if (memcmp(_s->pointer, pattern, pattern_length) == 0) 17 | { 18 | result = true; 19 | *offset = _s->pointer - s->pointer; 20 | goto exit; 21 | } 22 | 23 | MsRdpEx_StreamSeek(_s, 1); 24 | } 25 | 26 | exit: 27 | MsRdpEx_Stream_Free(_s); 28 | 29 | return result; 30 | } 31 | 32 | bool MsRdpEx_Stream_EnsureCapacity(MsRdpEx_Stream* s, size_t size) 33 | { 34 | if (!s->capacity) 35 | return false; 36 | 37 | if (s->capacity < size) 38 | { 39 | uint8_t* new_buf; 40 | size_t position; 41 | size_t old_capacity; 42 | size_t new_capacity; 43 | 44 | old_capacity = s->capacity; 45 | new_capacity = old_capacity; 46 | 47 | do 48 | { 49 | new_capacity *= 2; 50 | } 51 | while (new_capacity < size); 52 | 53 | position = MsRdpEx_Stream_GetPosition(s); 54 | 55 | if (s->owner) 56 | { 57 | new_buf = (uint8_t*) realloc(s->buffer, new_capacity); 58 | 59 | if (!new_buf) 60 | return false; 61 | } 62 | else 63 | { 64 | new_buf = (uint8_t*) malloc(new_capacity); 65 | 66 | if (!new_buf) 67 | return false; 68 | 69 | CopyMemory(new_buf, s->buffer, old_capacity); 70 | 71 | s->owner = true; 72 | } 73 | 74 | s->buffer = new_buf; 75 | s->capacity = new_capacity; 76 | s->length = new_capacity; 77 | ZeroMemory(&s->buffer[old_capacity], s->capacity - old_capacity); 78 | 79 | MsRdpEx_Stream_SetPosition(s, position); 80 | } 81 | 82 | return true; 83 | } 84 | 85 | bool MsRdpEx_Stream_EnsureRemainingCapacity(MsRdpEx_Stream* s, size_t size) 86 | { 87 | if ((MsRdpEx_Stream_GetPosition(s) + size) > MsRdpEx_Stream_Capacity(s)) 88 | return MsRdpEx_Stream_EnsureCapacity(s, MsRdpEx_Stream_Capacity(s) + size); 89 | 90 | return true; 91 | } 92 | 93 | bool MsRdpEx_Stream_CheckSafeRead(MsRdpEx_Stream* s, size_t size) 94 | { 95 | size_t offset; 96 | 97 | if (s->buffer > s->pointer) 98 | return false; 99 | 100 | offset = s->pointer - s->buffer; 101 | 102 | if (s->length < offset) 103 | return false; 104 | 105 | int remaining_length = s->length - (s->pointer - s->buffer); 106 | 107 | if (remaining_length < size) 108 | return false; 109 | 110 | return true; 111 | } 112 | 113 | bool MsRdpEx_Stream_CheckSafeWrite(MsRdpEx_Stream* s, size_t size) 114 | { 115 | if ((MsRdpEx_Stream_GetPosition(s) + size) > MsRdpEx_Stream_Capacity(s)) 116 | { 117 | return MsRdpEx_Stream_EnsureCapacity(s, MsRdpEx_Stream_Capacity(s) + size); 118 | } 119 | 120 | return true; 121 | } 122 | 123 | int MsRdpEx_Stream_Format(MsRdpEx_Stream* s, const char* format, ...) 124 | { 125 | int status; 126 | size_t size; 127 | va_list args; 128 | 129 | /* vsnprintf: http://www.cplusplus.com/reference/cstdio/vsnprintf/ */ 130 | 131 | va_start(args, format); 132 | size = s->capacity - MsRdpEx_Stream_GetPosition(s); 133 | status = vsnprintf((char*) s->pointer, size, format, args); 134 | va_end(args); 135 | 136 | if (status < 0) 137 | return -1; 138 | 139 | if (status < size) 140 | { 141 | MsRdpEx_StreamSeek(s, (size_t) status); 142 | } 143 | else 144 | { 145 | if (!MsRdpEx_Stream_CheckSafeWrite(s, status + 1)) 146 | return -1; 147 | 148 | va_start(args, format); 149 | size = s->capacity - MsRdpEx_Stream_GetPosition(s); 150 | status = vsnprintf((char*) s->pointer, size, format, args); 151 | va_end(args); 152 | 153 | if ((status < 0) || (status >= size)) 154 | return -1; 155 | 156 | MsRdpEx_StreamSeek(s, (size_t) status); 157 | } 158 | 159 | return status; 160 | } 161 | 162 | MsRdpEx_Stream* MsRdpEx_Stream_Init(MsRdpEx_Stream* s, uint8_t* buffer, size_t size, bool owner) 163 | { 164 | if (!s) 165 | return NULL; 166 | 167 | s->owner = owner; 168 | 169 | if (buffer) 170 | { 171 | s->buffer = buffer; 172 | } 173 | else 174 | { 175 | s->buffer = (uint8_t*) malloc(size); 176 | s->owner = true; 177 | } 178 | 179 | if (!s->buffer) 180 | return NULL; 181 | 182 | s->pointer = s->buffer; 183 | s->capacity = size; 184 | s->length = size; 185 | 186 | return s; 187 | } 188 | 189 | void MsRdpEx_Stream_Uninit(MsRdpEx_Stream* s) 190 | { 191 | if (!s) 192 | return; 193 | 194 | if (s->owner) 195 | { 196 | free(s->buffer); 197 | s->buffer = NULL; 198 | } 199 | 200 | s->owner = false; 201 | s->pointer = NULL; 202 | s->capacity = 0; 203 | s->length = 0; 204 | } 205 | 206 | MsRdpEx_Stream* MsRdpEx_Stream_New(uint8_t* buffer, size_t size, bool owner) 207 | { 208 | MsRdpEx_Stream* s; 209 | 210 | s = (MsRdpEx_Stream*) malloc(sizeof(MsRdpEx_Stream)); 211 | 212 | if (!s) 213 | return NULL; 214 | 215 | if (!MsRdpEx_Stream_Init(s, buffer, size, owner)) 216 | { 217 | free(s); 218 | return NULL; 219 | } 220 | 221 | return s; 222 | } 223 | 224 | void MsRdpEx_Stream_Free(MsRdpEx_Stream* s) 225 | { 226 | if (!s) 227 | return; 228 | 229 | MsRdpEx_Stream_Uninit(s); 230 | 231 | free(s); 232 | } 233 | -------------------------------------------------------------------------------- /dll/TSObjects.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | #include "TSObjects.h" 7 | 8 | const char* GetTSPropertyTypeName(uint8_t propType) 9 | { 10 | const char* name = "none"; 11 | 12 | switch (propType) 13 | { 14 | case 1: name = "ULONG"; break; 15 | case 2: name = "INT"; break; 16 | case 3: name = "BOOL"; break; 17 | case 4: name = "String"; break; 18 | case 5: name = "Binary"; break; 19 | case 6: name = "SecureString"; break; 20 | case 7: name = "IUnknown"; break; 21 | } 22 | 23 | return name; 24 | } 25 | 26 | PROPERTY_ENTRY_EX* FindTSProperty(ITSPropertySet* pTSPropertySet, const char* name) 27 | { 28 | PROPERTY_ENTRY_EX* found = NULL; 29 | uint32_t propCount = pTSPropertySet->propCount; 30 | PROPERTY_ENTRY_EX* propMap = pTSPropertySet->propMap; 31 | 32 | for (int i = 0; i < propCount; i++) 33 | { 34 | PROPERTY_ENTRY_EX* prop = &propMap[i]; 35 | 36 | if (MsRdpEx_StringEquals(name, prop->propName)) { 37 | found = prop; 38 | break; 39 | } 40 | } 41 | 42 | return found; 43 | } 44 | 45 | bool GetTSPropertyType(ITSPropertySet* pTSPropertySet, const char* name, uint8_t* pPropType) 46 | { 47 | PROPERTY_ENTRY_EX* prop = FindTSProperty(pTSPropertySet, name); 48 | 49 | *pPropType = 0; 50 | 51 | if (prop) { 52 | *pPropType = prop->propType; 53 | } 54 | 55 | return prop ? true : false; 56 | } 57 | 58 | void DumpTSPropertyMap(ITSPropertySet* pTSPropertySet, const char* name) 59 | { 60 | uint32_t propCount = pTSPropertySet->propCount; 61 | PROPERTY_ENTRY_EX* propMap = pTSPropertySet->propMap; 62 | 63 | MsRdpEx_LogPrint(DEBUG, "TSPropertySet: %s (%d)", name, propCount); 64 | 65 | for (int i = 0; i < propCount; i++) 66 | { 67 | PROPERTY_ENTRY_EX* prop = &propMap[i]; 68 | MsRdpEx_LogPrint(DEBUG, "%s (%s)", prop->propName, GetTSPropertyTypeName(prop->propType)); 69 | } 70 | } 71 | 72 | bool TsPropertyMap_IsCoreProps(ITSPropertySet* pTSPropertySet) 73 | { 74 | uint32_t propCount; 75 | PROPERTY_ENTRY_EX* propMap; 76 | 77 | if (!MsRdpEx_CanReadUnsafePtr((void*)pTSPropertySet, sizeof(ITSPropertySet))) 78 | return false; 79 | 80 | propCount = pTSPropertySet->propCount; 81 | propMap = pTSPropertySet->propMap; 82 | 83 | if ((propCount < 100) || (propCount > 500)) 84 | return false; 85 | 86 | if (!MsRdpEx_CanReadUnsafePtr((void*)propMap, propCount * sizeof(PROPERTY_ENTRY_EX))) 87 | return false; 88 | 89 | for (int i = 0; i < 10; i++) 90 | { 91 | PROPERTY_ENTRY_EX* prop = &propMap[i]; 92 | 93 | if (MsRdpEx_StringIEqualsUnsafePtr(prop->propName, "ServerName")) { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | bool TsPropertyMap_IsBaseProps(ITSPropertySet* pTSPropertySet) 102 | { 103 | uint32_t propCount; 104 | PROPERTY_ENTRY_EX* propMap; 105 | 106 | if (!MsRdpEx_CanReadUnsafePtr((void*)pTSPropertySet, sizeof(ITSPropertySet))) 107 | return false; 108 | 109 | propCount = pTSPropertySet->propCount; 110 | propMap = pTSPropertySet->propMap; 111 | 112 | if ((propCount < 100) || (propCount > 500)) 113 | return false; 114 | 115 | if (!MsRdpEx_CanReadUnsafePtr((void*)propMap, propCount * sizeof(PROPERTY_ENTRY_EX))) 116 | return false; 117 | 118 | for (int i = 0; i < 10; i++) 119 | { 120 | PROPERTY_ENTRY_EX* prop = &propMap[i]; 121 | 122 | if (MsRdpEx_StringIEqualsUnsafePtr(prop->propName, "FullScreen")) { 123 | return true; 124 | } 125 | } 126 | 127 | return false; 128 | } 129 | 130 | bool TsPropertyMap_IsTransportProps(ITSPropertySet* pTSPropertySet) 131 | { 132 | uint32_t propCount; 133 | PROPERTY_ENTRY_EX* propMap; 134 | 135 | if (!MsRdpEx_CanReadUnsafePtr((void*)pTSPropertySet, sizeof(ITSPropertySet))) 136 | return false; 137 | 138 | propCount = pTSPropertySet->propCount; 139 | propMap = pTSPropertySet->propMap; 140 | 141 | if ((propCount < 25) || (propCount > 500)) 142 | return false; 143 | 144 | if (!MsRdpEx_CanReadUnsafePtr((void*)propMap, propCount * sizeof(PROPERTY_ENTRY_EX))) 145 | return false; 146 | 147 | for (int i = 0; i < 10; i++) 148 | { 149 | PROPERTY_ENTRY_EX* prop = &propMap[i]; 150 | 151 | if (MsRdpEx_StringIEqualsUnsafePtr(prop->propName, "GatewayHostname")) { 152 | return true; 153 | } 154 | } 155 | 156 | return false; 157 | } 158 | 159 | typedef struct 160 | { 161 | void* param1; 162 | void* param2; 163 | void* name; 164 | void* marker; 165 | } COPWnd; 166 | 167 | void CDECL MsRdpEx_OutputWindow_OnCreate(HWND hWnd, void* pUserData) 168 | { 169 | COPWnd* pOPWnd = (COPWnd*) pUserData; 170 | 171 | MsRdpEx_LogPrint(DEBUG, "WindowCreate: %s name: %s hWnd: %p", pOPWnd->name, hWnd); 172 | } 173 | -------------------------------------------------------------------------------- /dotnet/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | .vs/ 4 | *.csproj.user 5 | Directory.Build.props 6 | launchSettings.json -------------------------------------------------------------------------------- /dotnet/AxInterop.MSTSCLib/AxInterop.MSTSCLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | AxInterop.MSTSCLib 6 | AxInterop.MSTSCLib 7 | 1.0.0.0 8 | Library 9 | true 10 | disable 11 | True 12 | $(CMakeOutputPath) 13 | 14 | 15 | 16 | 17 | <_Parameter1>11/09/2021 16:17:20 18 | 19 | 20 | 21 | 22 | 23 | True 24 | ..\Interop.MSTSCLib\Interop.MSTSCLib.dll 25 | False 26 | 27 | 28 | -------------------------------------------------------------------------------- /dotnet/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | include_external_msproject(AxInterop.MSTSCLib 3 | "${CMAKE_CURRENT_SOURCE_DIR}/AxInterop.MSTSCLib/AxInterop.MSTSCLib.csproj") 4 | 5 | include_external_msproject(Devolutions.MsRdpEx 6 | "${CMAKE_CURRENT_SOURCE_DIR}/Devolutions.MsRdpEx/Devolutions.MsRdpEx.csproj") 7 | 8 | include_external_msproject(MsRdpEx_App 9 | "${CMAKE_CURRENT_SOURCE_DIR}/MsRdpEx_App/MsRdpEx_App.csproj") 10 | 11 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Directory.Build.props.in" 12 | "${CMAKE_CURRENT_SOURCE_DIR}/Directory.Build.props" @ONLY) 13 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Devolutions.MsRdpEx 6 | Devolutions.MsRdpEx 7 | 1.0.0.0 8 | mamoreau@devolutions.net 9 | Devolutions 10 | Microsoft RDP Extensions 11 | Library 12 | True 13 | $(CMakeOutputPath) 14 | enable 15 | True 16 | True 17 | 18 | 19 | 20 | 21 | False 22 | ..\Interop.MSTSCLib\Interop.MSTSCLib.dll 23 | False 24 | True 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/Devolutions.MsRdpEx.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runtimes\win-x86\native\MsRdpEx.dll 6 | Included 7 | False 8 | PreserveNewest 9 | true 10 | false 11 | 12 | 13 | runtimes\win-x64\native\MsRdpEx.dll 14 | Included 15 | False 16 | PreserveNewest 17 | true 18 | false 19 | 20 | 21 | runtimes\win-arm64\native\MsRdpEx.dll 22 | Included 23 | False 24 | PreserveNewest 25 | true 26 | false 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/MarshalHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net.NetworkInformation; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace MsRdpEx 9 | { 10 | public class MarshalHelpers 11 | { 12 | // Replacement for native strlen() function 13 | public static unsafe int PtrStringLength(IntPtr ptr) 14 | { 15 | byte* p = (byte*)ptr; 16 | int length = 0; 17 | 18 | while (*p != 0) 19 | { 20 | p++; 21 | length++; 22 | } 23 | 24 | return length; 25 | } 26 | 27 | // Replacement for Marshal.PtrToStringUTF8 28 | public static string PtrToStringUTF8(IntPtr ptr) 29 | { 30 | int length = PtrStringLength(ptr); 31 | byte[] buffer = new byte[length]; 32 | Marshal.Copy(ptr, buffer, 0, length); 33 | return System.Text.Encoding.UTF8.GetString(buffer); 34 | } 35 | 36 | // Replacement for Marshal.StringToCoTaskMemUTF8 37 | public static IntPtr StringToCoTaskMemUTF8(string str) 38 | { 39 | if (str == null) 40 | { 41 | return IntPtr.Zero; 42 | } 43 | 44 | byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(str); 45 | IntPtr unmanagedMemory = Marshal.AllocCoTaskMem(utf8Bytes.Length + 1); 46 | Marshal.Copy(utf8Bytes, 0, unmanagedMemory, utf8Bytes.Length); 47 | Marshal.WriteByte(unmanagedMemory, utf8Bytes.Length, 0); 48 | 49 | return unmanagedMemory; 50 | } 51 | 52 | // Replacement for [MarshalAs(UnmanagedType.LPStr)] 53 | // [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MarshalHelpers.LPUTF8Str))] 54 | 55 | public class LPUTF8Str : ICustomMarshaler 56 | { 57 | public void CleanUpManagedData(object ManagedObj) 58 | { 59 | 60 | } 61 | 62 | public void CleanUpNativeData(IntPtr pNativeData) 63 | { 64 | Marshal.FreeCoTaskMem(pNativeData); 65 | } 66 | 67 | public int GetNativeDataSize() 68 | { 69 | throw new NotImplementedException(); 70 | } 71 | 72 | public IntPtr MarshalManagedToNative(object ManagedObj) 73 | { 74 | return MarshalHelpers.StringToCoTaskMemUTF8((string)ManagedObj); 75 | } 76 | 77 | public object MarshalNativeToManaged(IntPtr pNativeData) 78 | { 79 | return MarshalHelpers.PtrToStringUTF8(pNativeData); 80 | } 81 | 82 | public static ICustomMarshaler GetInstance(string cookie) => new LPUTF8Str(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/RdpCoreApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace MsRdpEx 5 | { 6 | public class RdpCoreApi 7 | { 8 | public IMsRdpExCoreApi iface; 9 | 10 | public RdpCoreApi() 11 | { 12 | iface = Bindings.GetCoreApi(); 13 | } 14 | 15 | public void Load() 16 | { 17 | iface.Load(); 18 | } 19 | 20 | public void Unload() 21 | { 22 | iface.Unload(); 23 | } 24 | 25 | public string MsRdpExDllPath 26 | { 27 | get { return MarshalHelpers.PtrToStringUTF8(iface.GetMsRdpExDllPath()); } 28 | } 29 | 30 | public bool LogEnabled 31 | { 32 | set { iface.SetLogEnabled(value); } 33 | } 34 | 35 | public MsRdpEx_LogLevel LogLevel 36 | { 37 | set { iface.SetLogLevel(value); } 38 | } 39 | 40 | public string LogFilePath 41 | { 42 | set { iface.SetLogFilePath(value); } 43 | } 44 | 45 | public bool PcapEnabled 46 | { 47 | set { iface.SetPcapEnabled(value); } 48 | } 49 | 50 | public string PcapFilePath 51 | { 52 | set { iface.SetPcapFilePath(value); } 53 | } 54 | 55 | public bool AxHookEnabled 56 | { 57 | set { iface.SetAxHookEnabled(value); } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/RdpInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace MsRdpEx 5 | { 6 | public class RdpInstance 7 | { 8 | public IMsRdpExInstance iface; 9 | 10 | public RdpInstance(IMsRdpExInstance iface) { 11 | this.iface = iface; 12 | } 13 | 14 | public Guid SessionId 15 | { 16 | get { Guid val; iface.GetSessionId(out val); return val; } 17 | } 18 | 19 | public bool OutputMirrorEnabled { 20 | get { bool val; iface.GetOutputMirrorEnabled(out val); return val; } 21 | set { iface.SetOutputMirrorEnabled(value); } 22 | } 23 | 24 | public bool VideoRecordingEnabled 25 | { 26 | get { bool val; iface.GetVideoRecordingEnabled(out val); return val; } 27 | set { iface.SetVideoRecordingEnabled(value); } 28 | } 29 | 30 | public bool DumpBitmapUpdates 31 | { 32 | get { bool val; iface.GetDumpBitmapUpdates(out val); return val; } 33 | set { iface.SetDumpBitmapUpdates(value); } 34 | } 35 | 36 | public bool GetShadowBitmap(ref IntPtr phDC, ref IntPtr phBitmap, ref IntPtr pBitmapData, 37 | ref UInt32 pBitmapWidth, ref UInt32 pBitmapHeight, ref UInt32 pBitmapStep) 38 | { 39 | return iface.GetShadowBitmap(ref phDC, ref phBitmap, ref pBitmapData, 40 | ref pBitmapWidth, ref pBitmapHeight, ref pBitmapStep); 41 | } 42 | 43 | public object WTSPlugin 44 | { 45 | set { iface.SetWTSPluginObject(Marshal.GetIUnknownForObject(value)); } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dotnet/Devolutions.MsRdpEx/RdpProcess.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Diagnostics; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | 7 | namespace MsRdpEx 8 | { 9 | public class RdpProcess 10 | { 11 | public IMsRdpExProcess iface; 12 | 13 | public RdpProcess(string[] args, string appName, string axName) { 14 | iface = Bindings.StartProcess(args, appName, axName); 15 | } 16 | 17 | public RdpProcess(ProcessStartInfo startInfo) 18 | { 19 | iface = Bindings.CreateProcess(); 20 | 21 | this.FileName = startInfo.FileName; 22 | this.WorkingDirectory = startInfo.WorkingDirectory; 23 | this.SetArguments(startInfo.Arguments); 24 | SetEnvironmentBlock(startInfo.Environment); 25 | } 26 | 27 | public void SetArguments(string arguments) 28 | { 29 | iface.SetArguments(arguments); 30 | } 31 | 32 | public void SetArgumentVector(string[] args) 33 | { 34 | StringBuilder sb = new StringBuilder(); 35 | foreach (string arg in args) 36 | { 37 | sb.AppendFormat("{0}\0", arg); 38 | } 39 | sb.Append("\0"); 40 | 41 | string argumentBlock = sb.ToString(); 42 | iface.SetArgumentBlock(argumentBlock); 43 | } 44 | 45 | private void SetEnvironmentBlock(IDictionary environment) 46 | { 47 | StringBuilder sb = new StringBuilder(); 48 | foreach (KeyValuePair envvar in environment) 49 | { 50 | sb.AppendFormat("{0}={1}\0", envvar.Key, envvar.Value); 51 | } 52 | sb.Append("\0"); 53 | 54 | string environmentBlock = sb.ToString(); 55 | iface.SetEnvironmentBlock(environmentBlock); 56 | } 57 | 58 | public string FileName 59 | { 60 | set { iface.SetFileName(value); } 61 | } 62 | 63 | public string WorkingDirectory 64 | { 65 | set { iface.SetWorkingDirectory(value); } 66 | } 67 | 68 | public static Process StartProcess(ProcessStartInfo startInfo) 69 | { 70 | RdpProcess rdpProcess = new RdpProcess(startInfo); 71 | return rdpProcess.Start(); 72 | } 73 | 74 | public Process Start() 75 | { 76 | iface.StartWithInfo(); 77 | uint processId = this.GetProcessId(); 78 | return Process.GetProcessById((int)processId); 79 | } 80 | 81 | public void Stop(UInt32 exitCode) 82 | { 83 | iface.Stop(exitCode); 84 | } 85 | 86 | public void Wait(UInt32 milliseconds) 87 | { 88 | iface.Wait(milliseconds); 89 | } 90 | 91 | public uint GetProcessId() 92 | { 93 | return iface.GetProcessId(); 94 | } 95 | 96 | public uint GetExitCode() 97 | { 98 | return iface.GetExitCode(); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /dotnet/Directory.Build.props.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | @CMAKE_SOURCE_DIR@ 4 | @CMAKE_BINARY_DIR@ 5 | $(CMakeBinaryDir)/$(Configuration) 6 | 7 | -------------------------------------------------------------------------------- /dotnet/Interop.MSTSCLib/Interop.MSTSCLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/dotnet/Interop.MSTSCLib/Interop.MSTSCLib.dll -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/MainDlg.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/MsRdpEx_App.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | Win32;x64;ARM64 6 | net8.0-windows 7 | True 8 | disable 9 | True 10 | $(CMakeOutputPath) 11 | True 12 | 11 13 | 14 | 15 | 16 | MsRdpEx 17 | MsRdpEx 18 | 1.0.0 19 | 1.0.0.0 20 | 1.0.0.0 21 | Always 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | True 43 | ..\Interop.MSTSCLib\Interop.MSTSCLib.dll 44 | 45 | 46 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/NowProtoPipeTransport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Pipes; 4 | using System.Threading.Tasks; 5 | 6 | namespace MsRdpEx_App 7 | { 8 | internal class NowProtoPipeTransport: IDisposable, IAsyncDisposable 9 | { 10 | public NowProtoPipeTransport(NamedPipeServerStream pipe) 11 | { 12 | _pipe = pipe; 13 | } 14 | 15 | public async Task Write(byte[] data) 16 | { 17 | await _pipe.WriteAsync(data); 18 | await _pipe.FlushAsync(); 19 | } 20 | 21 | public async Task Read() 22 | { 23 | var bytesRead = await _pipe.ReadAsync(_buffer, 0, _buffer.Length); 24 | 25 | if (bytesRead == 0) 26 | { 27 | throw new EndOfStreamException("End of stream reached (DVC)"); 28 | } 29 | 30 | return _buffer[..bytesRead]; 31 | } 32 | 33 | public void Dispose() 34 | { 35 | _pipe?.Dispose(); 36 | } 37 | 38 | public async ValueTask DisposeAsync() 39 | { 40 | if (_pipe != null) await _pipe.DisposeAsync(); 41 | } 42 | 43 | private readonly NamedPipeServerStream _pipe; 44 | // 64K message buffer 45 | private readonly byte[] _buffer = new byte[64 * 1024]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | 8 | namespace MsRdpEx_App 9 | { 10 | static class Program 11 | { 12 | /// 13 | /// The main entry point for the application. 14 | /// 15 | [STAThread] 16 | static void Main() 17 | { 18 | Application.EnableVisualStyles(); 19 | Application.SetCompatibleTextRenderingDefault(false); 20 | Application.Run(new MainDlg()); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MsRdpEx.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MsRdpEx.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | text/microsoft-resx 48 | 49 | 50 | 2.0 51 | 52 | 53 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 54 | 55 | 56 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 57 | 58 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | 12 | namespace MsRdpEx.Properties 13 | { 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 17 | { 18 | 19 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 20 | 21 | public static Settings Default 22 | { 23 | get 24 | { 25 | return defaultInstance; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/RdpManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Runtime.InteropServices; 5 | 6 | using MsRdpEx; 7 | 8 | namespace MsRdpEx_App 9 | { 10 | public class MsRdpExManager 11 | { 12 | private static readonly RdpCoreApi coreApi; 13 | 14 | private static bool axHookEnabled = true; 15 | 16 | public RdpCoreApi CoreApi { get => coreApi; } 17 | 18 | public bool AxHookEnabled { get => axHookEnabled; } 19 | 20 | private static RdpCoreApi LoadCoreApi() 21 | { 22 | RdpCoreApi coreApi = new RdpCoreApi(); 23 | 24 | string logFilePath = Environment.ExpandEnvironmentVariables("%LocalAppData%\\MsRdpEx\\HostApp.log"); 25 | string pcapFilePath = Environment.ExpandEnvironmentVariables("%LocalAppData%\\MsRdpEx\\capture.pcap"); 26 | 27 | coreApi.LogEnabled = true; 28 | coreApi.LogLevel = MsRdpEx_LogLevel.Trace; 29 | coreApi.LogFilePath = logFilePath; 30 | coreApi.PcapEnabled = false; 31 | coreApi.PcapFilePath = pcapFilePath; 32 | coreApi.AxHookEnabled = axHookEnabled; 33 | coreApi.Load(); 34 | 35 | return coreApi; 36 | } 37 | 38 | private static MsRdpExManager instance = null; 39 | private static readonly object padlock = new object(); 40 | 41 | public static MsRdpExManager Instance 42 | { 43 | get 44 | { 45 | lock (padlock) 46 | { 47 | if (instance == null) 48 | { 49 | instance = new MsRdpExManager(); 50 | } 51 | 52 | return instance; 53 | } 54 | } 55 | } 56 | 57 | static MsRdpExManager() 58 | { 59 | coreApi = LoadCoreApi(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /dotnet/MsRdpEx_App/RdpView.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /dotnet/common.build.pre.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net48;net8.0-windows 6 | True 7 | disable 8 | 11 9 | Win32;x64;ARM64 10 | AnyCPU 11 | MSIL 12 | True 13 | 14 | 15 | 16 | False 17 | Full 18 | 19 | 20 | 21 | True 22 | None 23 | 24 | -------------------------------------------------------------------------------- /exe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_subdirectory(mstscex) 3 | add_subdirectory(msrdcex) 4 | add_subdirectory(vmconnectex) 5 | -------------------------------------------------------------------------------- /exe/msrdcex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # msrdcex launcher executable 2 | 3 | windows_rc_generate_version_info( 4 | NAME "msrdcex" TYPE "EXE" 5 | VERSION "${MSRDPEX_VERSION}" 6 | FILENAME "msrdcex.exe" 7 | VENDOR "${MSRDPEX_VENDOR}" 8 | COPYRIGHT "${MSRDPEX_COPYRIGHT}" 9 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.rc) 10 | 11 | source_group("Resources" FILES msrdcex.rc) 12 | 13 | add_executable(msrdcex WIN32 14 | msrdcex.cpp 15 | msrdcex.rc) 16 | 17 | target_link_libraries(msrdcex MsRdpEx_Dll) 18 | -------------------------------------------------------------------------------- /exe/msrdcex/msrdcex.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../dll/AxHost/RdpWinMain.h" 3 | 4 | int WINAPI wWinMain( 5 | _In_ HINSTANCE hInstance, 6 | _In_opt_ HINSTANCE hPrevInstance, 7 | _In_ LPWSTR lpCmdLine, 8 | _In_ int nShowCmd) 9 | { 10 | return MsRdpEx_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd, "msrdc"); 11 | } 12 | -------------------------------------------------------------------------------- /exe/msrdcex/msrdcex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/exe/msrdcex/msrdcex.ico -------------------------------------------------------------------------------- /exe/msrdcex/msrdcex.rc: -------------------------------------------------------------------------------- 1 | 2 | IDI_APP_ICON ICON "msrdcex.ico" 3 | 4 | #include "version.rc" 5 | -------------------------------------------------------------------------------- /exe/mstscex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # mstscex launcher executable 2 | 3 | windows_rc_generate_version_info( 4 | NAME "mstscex" TYPE "EXE" 5 | VERSION "${MSRDPEX_VERSION}" 6 | FILENAME "mstscex.exe" 7 | VENDOR "${MSRDPEX_VENDOR}" 8 | COPYRIGHT "${MSRDPEX_COPYRIGHT}" 9 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.rc) 10 | 11 | source_group("Resources" FILES mstscex.rc) 12 | 13 | add_executable(mstscex WIN32 14 | mstscex.cpp 15 | mstscex.rc) 16 | 17 | target_link_libraries(mstscex MsRdpEx_Dll) 18 | -------------------------------------------------------------------------------- /exe/mstscex/mstscex.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../dll/AxHost/RdpWinMain.h" 3 | 4 | int WINAPI wWinMain( 5 | _In_ HINSTANCE hInstance, 6 | _In_opt_ HINSTANCE hPrevInstance, 7 | _In_ LPWSTR lpCmdLine, 8 | _In_ int nShowCmd) 9 | { 10 | return MsRdpEx_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd, "mstsc"); 11 | } 12 | -------------------------------------------------------------------------------- /exe/mstscex/mstscex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/exe/mstscex/mstscex.ico -------------------------------------------------------------------------------- /exe/mstscex/mstscex.rc: -------------------------------------------------------------------------------- 1 | 2 | IDI_APP_ICON ICON "mstscex.ico" 3 | 4 | #include "version.rc" 5 | -------------------------------------------------------------------------------- /exe/vmconnectex/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # vmconnectex launcher executable 2 | 3 | windows_rc_generate_version_info( 4 | NAME "vmconnectex" TYPE "EXE" 5 | VERSION "${MSRDPEX_VERSION}" 6 | FILENAME "vmconnectex.exe" 7 | VENDOR "${MSRDPEX_VENDOR}" 8 | COPYRIGHT "${MSRDPEX_COPYRIGHT}" 9 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/version.rc) 10 | 11 | source_group("Resources" FILES vmconnectex.rc) 12 | 13 | add_executable(vmconnectex WIN32 14 | vmconnectex.cpp 15 | vmconnectex.rc) 16 | 17 | target_link_libraries(vmconnectex MsRdpEx_Dll) 18 | -------------------------------------------------------------------------------- /exe/vmconnectex/vmconnectex.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../dll/AxHost/RdpWinMain.h" 3 | 4 | int WINAPI wWinMain( 5 | _In_ HINSTANCE hInstance, 6 | _In_opt_ HINSTANCE hPrevInstance, 7 | _In_ LPWSTR lpCmdLine, 8 | _In_ int nShowCmd) 9 | { 10 | return MsRdpEx_WinMain(hInstance, hPrevInstance, lpCmdLine, nShowCmd, "vmconnect"); 11 | } 12 | -------------------------------------------------------------------------------- /exe/vmconnectex/vmconnectex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/exe/vmconnectex/vmconnectex.ico -------------------------------------------------------------------------------- /exe/vmconnectex/vmconnectex.rc: -------------------------------------------------------------------------------- 1 | 2 | IDI_APP_ICON ICON "vmconnectex.ico" 3 | 4 | #include "version.rc" 5 | -------------------------------------------------------------------------------- /images/MsRdpEx_installed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Devolutions/MsRdpEx/c66219fb32d03d2fdadd6b631efb0376ffe8db88/images/MsRdpEx_installed.png -------------------------------------------------------------------------------- /include/MsRdpEx/ArrayList.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_ARRAY_LIST_H 2 | #define MSRDPEX_ARRAY_LIST_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef void* (*MSRDPEX_OBJECT_NEW_FN)(void); 11 | typedef void (*MSRDPEX_OBJECT_INIT_FN)(void* obj); 12 | typedef void (*MSRDPEX_OBJECT_UNINIT_FN)(void* obj); 13 | typedef void (*MSRDPEX_OBJECT_FREE_FN)(void* obj); 14 | typedef bool (*MSRDPEX_OBJECT_EQUALS_FN)(void* objA, void* objB); 15 | 16 | typedef bool (*MSRDPEX_OBJECT_MATCH_FN)(void* obj, void* param); 17 | typedef int (*MSRDPEX_OBJECT_COMPARE_FN)(void* objA, void* objB); 18 | 19 | struct msrdpex_object 20 | { 21 | MSRDPEX_OBJECT_NEW_FN fnObjectNew; 22 | MSRDPEX_OBJECT_INIT_FN fnObjectInit; 23 | MSRDPEX_OBJECT_UNINIT_FN fnObjectUninit; 24 | MSRDPEX_OBJECT_FREE_FN fnObjectFree; 25 | MSRDPEX_OBJECT_EQUALS_FN fnObjectEquals; 26 | }; 27 | typedef struct msrdpex_object MsRdpEx_Object; 28 | 29 | typedef struct msrdpex_array_list MsRdpEx_ArrayList; 30 | typedef struct msrdpex_array_list_it MsRdpEx_ArrayListIt; 31 | 32 | #define MSRDPEX_ITERATOR_FLAG_EXCLUSIVE 0x00000001 33 | #define MSRDPEX_ITERATOR_FLAG_DUPLICATE 0x00000002 34 | #define MSRDPEX_ITERATOR_FLAG_REVERSE 0x00000004 35 | 36 | int MsRdpEx_ArrayListIt_Count(MsRdpEx_ArrayListIt* it); 37 | void MsRdpEx_ArrayListIt_Reset(MsRdpEx_ArrayListIt* it); 38 | void* MsRdpEx_ArrayListIt_Current(MsRdpEx_ArrayListIt* it); 39 | bool MsRdpEx_ArrayListIt_Move(MsRdpEx_ArrayListIt* it); 40 | void* MsRdpEx_ArrayListIt_Next(MsRdpEx_ArrayListIt* it); 41 | bool MsRdpEx_ArrayListIt_Done(MsRdpEx_ArrayListIt* it); 42 | 43 | MsRdpEx_ArrayListIt* MsRdpEx_ArrayList_It(MsRdpEx_ArrayList* ctx, uint32_t flags); 44 | void MsRdpEx_ArrayListIt_Finish(MsRdpEx_ArrayListIt* it); 45 | 46 | int MsRdpEx_ArrayList_Capacity(MsRdpEx_ArrayList* ctx); 47 | int MsRdpEx_ArrayList_Count(MsRdpEx_ArrayList* ctx); 48 | bool MsRdpEx_ArrayList_IsEmpty(MsRdpEx_ArrayList* ctx); 49 | int MsRdpEx_ArrayList_Items(MsRdpEx_ArrayList* ctx, uintptr_t** ppItems); 50 | bool MsRdpEx_ArrayList_IsSynchronized(MsRdpEx_ArrayList* ctx); 51 | 52 | void MsRdpEx_ArrayList_Lock(MsRdpEx_ArrayList* ctx); 53 | void MsRdpEx_ArrayList_Unlock(MsRdpEx_ArrayList* ctx); 54 | 55 | void* MsRdpEx_ArrayList_GetItem(MsRdpEx_ArrayList* ctx, int index); 56 | bool MsRdpEx_ArrayList_SetItem(MsRdpEx_ArrayList* ctx, int index, void* obj); 57 | 58 | void* MsRdpEx_ArrayList_GetHead(MsRdpEx_ArrayList* ctx); 59 | void* MsRdpEx_ArrayList_GetTail(MsRdpEx_ArrayList* ctx); 60 | 61 | bool MsRdpEx_ArrayList_DefaultEquals(void* objA, void* objB); 62 | 63 | MsRdpEx_Object* MsRdpEx_ArrayList_Object(MsRdpEx_ArrayList* ctx); 64 | 65 | void* MsRdpEx_ArrayList_Find(MsRdpEx_ArrayList* ctx, MSRDPEX_OBJECT_MATCH_FN fnMatch, void* param); 66 | 67 | void MsRdpEx_ArrayList_Clear(MsRdpEx_ArrayList* ctx, bool free); 68 | bool MsRdpEx_ArrayList_Contains(MsRdpEx_ArrayList* ctx, void* obj); 69 | 70 | int MsRdpEx_ArrayList_Add(MsRdpEx_ArrayList* ctx, void* obj); 71 | bool MsRdpEx_ArrayList_InsertAt(MsRdpEx_ArrayList* ctx, int index, void* obj); 72 | int MsRdpEx_ArrayList_Insert(MsRdpEx_ArrayList* ctx, void* obj, MSRDPEX_OBJECT_COMPARE_FN fnCompare); 73 | 74 | void* MsRdpEx_ArrayList_Remove(MsRdpEx_ArrayList* ctx, void* obj, bool free); 75 | void* MsRdpEx_ArrayList_RemoveAt(MsRdpEx_ArrayList* ctx, int index, bool free); 76 | 77 | void* MsRdpEx_ArrayList_RemoveHead(MsRdpEx_ArrayList* ctx, bool free); 78 | void* MsRdpEx_ArrayList_RemoveTail(MsRdpEx_ArrayList* ctx, bool free); 79 | 80 | int MsRdpEx_ArrayList_RemoveAll(MsRdpEx_ArrayList* ctx, MSRDPEX_OBJECT_MATCH_FN fnMatch, void* param, bool free); 81 | int MsRdpEx_ArrayList_RemoveDuplicates(MsRdpEx_ArrayList* ctx, MSRDPEX_OBJECT_EQUALS_FN fnEquals, bool free); 82 | 83 | int MsRdpEx_ArrayList_IndexOf(MsRdpEx_ArrayList* ctx, void* obj, int startIndex, int count); 84 | int MsRdpEx_ArrayList_LastIndexOf(MsRdpEx_ArrayList* ctx, void* obj, int startIndex, int count); 85 | 86 | MsRdpEx_ArrayList* MsRdpEx_ArrayList_New(bool synchronized); 87 | void MsRdpEx_ArrayList_Free(MsRdpEx_ArrayList* ctx); 88 | 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | 93 | #endif /* MSRDPEX_ARRAY_LIST_H */ 94 | -------------------------------------------------------------------------------- /include/MsRdpEx/Detours.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_DETOURS_H 2 | #define MSRDPEX_DETOURS_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define MSRDPEX_DETOUR_ATTACH(_realFn, _hookFn) \ 9 | if (_realFn) DetourAttach((PVOID*)(&_realFn), _hookFn); 10 | 11 | #define MSRDPEX_DETOUR_DETACH(_realFn, _hookFn) \ 12 | if (_realFn) DetourDetach((PVOID*)(&_realFn), _hookFn); 13 | 14 | #define MSRDPEX_GETPROCADDRESS(_funcPtr, _funcType, _hModule, _funcName) \ 15 | _funcPtr = ( _funcType ) GetProcAddress(_hModule, _funcName); 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif // MSRDPEX_DETOURS_H -------------------------------------------------------------------------------- /include/MsRdpEx/Environment.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_ENVIRONMENT_H 2 | #define MSRDPEX_ENVIRONMENT_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | bool MsRdpEx_SetEnv(const char* name, const char* value); 11 | char* MsRdpEx_GetEnv(const char* name); 12 | 13 | bool MsRdpEx_EnvExists(const char* name); 14 | 15 | bool MsRdpEx_GetEnvBool(const char* name, bool defaultValue); 16 | int MsRdpEx_GetEnvInt(const char* name, int defaultValue); 17 | 18 | char** MsRdpEx_GetEnvironmentVariables(int* envc); 19 | void MsRdpEx_FreeEnvironmentVariables(int envc, char** envs); 20 | 21 | char* MsRdpEx_ReadTextFromNamedPipe(const char* pipeName); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif /* MSRDPEX_ENVIRONMENT_H */ 28 | -------------------------------------------------------------------------------- /include/MsRdpEx/HashTable.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef MSRDPEX_HASHTABLE_H 3 | #define MSRDPEX_HASHTABLE_H 4 | 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | #define MSRDPEX_HASHTABLE_INT_KEY(x) (void*)(size_t)(x) 12 | #define MSRDPEX_HASHTABLE_INT_VALUE(x) (void*)(size_t)(x) 13 | 14 | #define MSRDPEX_HASHTABLE_FLAGS_NONE 0x00 15 | #define MSRDPEX_HASHTABLE_FLAGS_ACCEPT_NULL_KEYS 0x01 16 | #define MSRDPEX_HASHTABLE_FLAGS_ACCEPT_NULL_VALUES 0x02 17 | 18 | typedef uint32_t (*MSRDPEX_HASHTABLE_HASH_FN)(void* key); 19 | typedef bool (*MSRDPEX_HASHTABLE_KEY_COMPARE_FN)(void* key1, void* key2); 20 | typedef bool (*MSRDPEX_HASHTABLE_VALUE_COMPARE_FN)(void* value1, void* value2); 21 | typedef void* (*MSRDPEX_HASHTABLE_KEY_CLONE_FN)(void* key); 22 | typedef void* (*MSRDPEX_HASHTABLE_VALUE_CLONE_FN)(void* value); 23 | typedef void (*MSRDPEX_HASHTABLE_KEY_FREE_FN)(void* key); 24 | typedef void (*MSRDPEX_HASHTABLE_VALUE_FREE_FN)(void* value); 25 | 26 | typedef struct msrdpex_key_value_pair MsRdpEx_KeyValuePair; 27 | typedef struct msrdpex_hash_table MsRdpEx_HashTable; 28 | 29 | void MsRdpEx_HashTable_SetHashFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_HASH_FN hash); 30 | void MsRdpEx_HashTable_SetKeyCompareFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_KEY_COMPARE_FN keyCompare); 31 | void MsRdpEx_HashTable_SetValueCompareFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_VALUE_COMPARE_FN valueCompare); 32 | void MsRdpEx_HashTable_SetKeyCloneFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_KEY_CLONE_FN keyClone); 33 | void MsRdpEx_HashTable_SetValueCloneFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_VALUE_CLONE_FN valueClone); 34 | void MsRdpEx_HashTable_SetKeyFreeFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_KEY_FREE_FN keyFree); 35 | void MsRdpEx_HashTable_SetValueFreeFunction(MsRdpEx_HashTable* table, MSRDPEX_HASHTABLE_VALUE_FREE_FN valueFree); 36 | 37 | /** 38 | * Lock access to the hash table. 39 | */ 40 | void MsRdpEx_HashTable_Lock(MsRdpEx_HashTable* table); 41 | 42 | /** 43 | * Unlock access to the hash table. 44 | */ 45 | void MsRdpEx_HashTable_Unlock(MsRdpEx_HashTable* table); 46 | 47 | /** 48 | * Gets the number of key/value pairs contained in the HashTable. 49 | */ 50 | int MsRdpEx_HashTable_Count(MsRdpEx_HashTable* table); 51 | 52 | /** 53 | * Adds an element with the specified key and value into the HashTable. 54 | */ 55 | int MsRdpEx_HashTable_Add(MsRdpEx_HashTable* table, void* key, void* value); 56 | 57 | /** 58 | * Removes the element with the specified key from the HashTable. 59 | */ 60 | bool MsRdpEx_HashTable_Remove(MsRdpEx_HashTable* table, void* key); 61 | 62 | bool MsRdpEx_HashTable_RemoveValue(MsRdpEx_HashTable* table, void* value); 63 | 64 | /** 65 | * Removes all elements from the HashTable. 66 | */ 67 | void MsRdpEx_HashTable_Clear(MsRdpEx_HashTable* table); 68 | 69 | /** 70 | * Determines whether the HashTable contains a specific key. 71 | */ 72 | bool MsRdpEx_HashTable_ContainsKey(MsRdpEx_HashTable* table, void* key); 73 | 74 | /** 75 | * Determines whether the HashTable contains a specific value. 76 | */ 77 | bool MsRdpEx_HashTable_ContainsValue(MsRdpEx_HashTable* table, void* value); 78 | 79 | /** 80 | * Get an item value using key. 81 | */ 82 | void* MsRdpEx_HashTable_GetItemValue(MsRdpEx_HashTable* table, void* key); 83 | 84 | /** 85 | * Set an item value using key. 86 | */ 87 | bool MsRdpEx_HashTable_SetItemValue(MsRdpEx_HashTable* table, void* key, void* value); 88 | 89 | /** 90 | * Gets the list of keys as an array. 91 | */ 92 | int MsRdpEx_HashTable_GetKeys(MsRdpEx_HashTable* table, ULONG_PTR** ppKeys); 93 | 94 | uint32_t MsRdpEx_HashTable_PointerHash(void* pointer); 95 | bool MsRdpEx_HashTable_PointerCompare(void* pointer1, void* pointer2); 96 | uint32_t MsRdpEx_HashTable_StringHash(void* key); 97 | bool MsRdpEx_HashTable_StringCompare(void* string1, void* string2); 98 | void* MsRdpEx_HashTable_StringClone(void* str); 99 | void MsRdpEx_HashTable_StringFree(void* str); 100 | bool MsRdpEx_HashTable_UInt32Compare(void* v1, void* v2); 101 | uint32_t MsRdpEx_HashTable_UInt32Hash(void* v); 102 | 103 | MsRdpEx_HashTable* MsRdpEx_HashTable_New(bool synchronized, uint8_t flags); 104 | void MsRdpEx_HashTable_Free(MsRdpEx_HashTable* table); 105 | 106 | #ifdef __cplusplus 107 | } 108 | #endif 109 | 110 | #endif /* MSRDPEX_HASHTABLE_H */ -------------------------------------------------------------------------------- /include/MsRdpEx/KeyMaps.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_KEYMAPS_H 2 | #define MSRDPEX_KEYMAPS_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | uint32_t MsRdpEx_KeyNameToVKCode(const char* keyName); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif 15 | 16 | #endif // MSRDPEX_KEYMAPS_H 17 | -------------------------------------------------------------------------------- /include/MsRdpEx/Memory.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_MEMORY_H 2 | #define MSRDPEX_MEMORY_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | bool MsRdpEx_CanReadUnsafePtr(void* ptr, size_t size); 11 | bool MsRdpEx_StringEqualsUnsafePtr(const char* ptr, const char* str); 12 | bool MsRdpEx_StringIEqualsUnsafePtr(const char* ptr, const char* str); 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | 18 | #endif // MSRDPEX_MEMORY_H 19 | -------------------------------------------------------------------------------- /include/MsRdpEx/NameResolver.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_NAME_RESOLVER_H 2 | #define MSRDPEX_NAME_RESOLVER_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct _MsRdpEx_NameResolver MsRdpEx_NameResolver; 11 | 12 | bool MsRdpEx_NameResolver_GetMapping(const char* oldName, char** pNewName); 13 | 14 | bool MsRdpEx_NameResolver_RemapName(const char* oldName, const char* newName); 15 | bool MsRdpEx_NameResolver_UnmapName(const char* name); 16 | 17 | MsRdpEx_NameResolver* MsRdpEx_NameResolver_Get(); 18 | void MsRdpEx_NameResolver_Release(); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif // MSRDPEX_NAME_RESOLVER_H 25 | -------------------------------------------------------------------------------- /include/MsRdpEx/NamedPipe.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_NAMED_PIPE_H 2 | #define MSRDPEX_NAMED_PIPE_H 3 | 4 | #include "MsRdpEx.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | int MsRdpEx_NamedPipe_Read(HANDLE np_handle, uint8_t* data, size_t size); 11 | int MsRdpEx_NamedPipe_Write(HANDLE np_handle, const uint8_t* data, size_t size); 12 | 13 | HANDLE MsRdpEx_NamedPipe_Open(const char* np_name); 14 | HANDLE MsRdpEx_NamedPipe_Create(const char* np_name, int max_clients); 15 | HANDLE MsRdpEx_NamedPipe_Accept(HANDLE np_handle); 16 | 17 | void MsRdpEx_NamedPipe_Close(HANDLE np_handle); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif /* MSRDPEX_NAMED_PIPE_H */ -------------------------------------------------------------------------------- /include/MsRdpEx/OutputMirror.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_OUTPUT_MIRROR_H 2 | #define MSRDPEX_OUTPUT_MIRROR_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | typedef struct _MsRdpEx_OutputMirror MsRdpEx_OutputMirror; 13 | 14 | void MsRdpEx_OutputMirror_SetSourceDC(MsRdpEx_OutputMirror* ctx, HDC hSourceDC); 15 | HDC MsRdpEx_OutputMirror_GetShadowDC(MsRdpEx_OutputMirror* ctx); 16 | 17 | void MsRdpEx_OutputMirror_SetFrameSize(MsRdpEx_OutputMirror* ctx, uint32_t frameWidth, uint32_t frameHeight); 18 | void MsRdpEx_OutputMirror_GetFrameSize(MsRdpEx_OutputMirror* ctx, uint32_t* frameWidth, uint32_t* frameHeight); 19 | 20 | bool MsRdpEx_OutputMirror_DumpFrame(MsRdpEx_OutputMirror* ctx); 21 | 22 | void MsRdpEx_OutputMirror_SetDumpBitmapUpdates(MsRdpEx_OutputMirror* ctx, bool dumpBitmapUpdates); 23 | void MsRdpEx_OutputMirror_SetVideoRecordingEnabled(MsRdpEx_OutputMirror* ctx, bool videoRecordingEnabled); 24 | void MsRdpEx_OutputMirror_SetVideoQualityLevel(MsRdpEx_OutputMirror* ctx, uint32_t videoQualityLevel); 25 | void MsRdpEx_OutputMirror_SetRecordingPath(MsRdpEx_OutputMirror* ctx, const char* recordingPath); 26 | void MsRdpEx_OutputMirror_SetRecordingPipeName(MsRdpEx_OutputMirror* ctx, const char* recordingPipeName); 27 | void MsRdpEx_OutputMirror_SetSessionId(MsRdpEx_OutputMirror* ctx, const char* sessionId); 28 | 29 | bool MsRdpEx_OutputMirror_GetShadowBitmap(MsRdpEx_OutputMirror* ctx, 30 | HDC* phDC, HBITMAP* phBitmap, uint8_t** pBitmapData, 31 | uint32_t* pBitmapWidth, uint32_t* pBitmapHeight, uint32_t* pBitmapStep); 32 | 33 | void MsRdpEx_OutputMirror_Lock(MsRdpEx_OutputMirror* ctx); 34 | void MsRdpEx_OutputMirror_Unlock(MsRdpEx_OutputMirror* ctx); 35 | 36 | bool MsRdpEx_OutputMirror_Init(MsRdpEx_OutputMirror* ctx); 37 | bool MsRdpEx_OutputMirror_Uninit(MsRdpEx_OutputMirror* ctx); 38 | 39 | MsRdpEx_OutputMirror* MsRdpEx_OutputMirror_New(); 40 | void MsRdpEx_OutputMirror_Free(MsRdpEx_OutputMirror* ctx); 41 | 42 | #ifdef __cplusplus 43 | } 44 | #endif 45 | 46 | #endif // MSRDPEX_OUTPUT_MIRROR_H 47 | -------------------------------------------------------------------------------- /include/MsRdpEx/Pcap.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_PCAP_H 2 | #define MSRDPEX_PCAP_H 3 | 4 | #include 5 | 6 | struct _pcap_file_header 7 | { 8 | uint32_t magic_number; /* magic number */ 9 | uint16_t version_major; /* major version number */ 10 | uint16_t version_minor; /* minor version number */ 11 | int32_t thiszone; /* GMT to local correction */ 12 | uint32_t sigfigs; /* accuracy of timestamps */ 13 | uint32_t snaplen; /* max length of captured packets, in octets */ 14 | uint32_t network; /* data link type */ 15 | }; 16 | typedef struct _pcap_file_header PCAP_FILE_HEADER; 17 | 18 | struct _pcap_record_header 19 | { 20 | uint32_t ts_sec; /* timestamp seconds */ 21 | uint32_t ts_usec; /* timestamp microseconds */ 22 | uint32_t incl_len; /* number of octets of packet saved in file */ 23 | uint32_t orig_len; /* actual length of packet */ 24 | }; 25 | typedef struct _pcap_record_header PCAP_RECORD_HEADER; 26 | 27 | typedef struct _pcap_record PCAP_RECORD; 28 | 29 | struct _pcap_record 30 | { 31 | PCAP_RECORD_HEADER header; 32 | union 33 | { 34 | void* data; 35 | const void* cdata; 36 | }; 37 | uint32_t length; 38 | PCAP_RECORD* next; 39 | }; 40 | 41 | typedef struct _MsRdpEx_Pcap MsRdpEx_PcapFile; 42 | 43 | #pragma pack(push, 1) 44 | 45 | struct _PCAP_ETHERNET_HEADER 46 | { 47 | uint8_t Destination[6]; 48 | uint8_t Source[6]; 49 | uint16_t Type; 50 | }; 51 | typedef struct _PCAP_ETHERNET_HEADER PCAP_ETHERNET_HEADER; 52 | 53 | struct _PCAP_IPV4_HEADER 54 | { 55 | uint8_t Version; 56 | uint8_t InternetHeaderLength; 57 | uint8_t TypeOfService; 58 | uint16_t TotalLength; 59 | uint16_t Identification; 60 | uint8_t InternetProtocolFlags; 61 | uint16_t FragmentOffset; 62 | uint8_t TimeToLive; 63 | uint8_t Protocol; 64 | uint16_t HeaderChecksum; 65 | uint32_t SourceAddress; 66 | uint32_t DestinationAddress; 67 | }; 68 | typedef struct _PCAP_IPV4_HEADER PCAP_IPV4_HEADER; 69 | 70 | struct _PCAP_TCP_HEADER 71 | { 72 | uint16_t SourcePort; 73 | uint16_t DestinationPort; 74 | uint32_t SequenceNumber; 75 | uint32_t AcknowledgementNumber; 76 | uint8_t Offset; 77 | uint8_t Reserved; 78 | uint8_t TcpFlags; 79 | uint16_t Window; 80 | uint16_t Checksum; 81 | uint16_t UrgentPointer; 82 | }; 83 | typedef struct _PCAP_TCP_HEADER PCAP_TCP_HEADER; 84 | 85 | #pragma pack(pop) 86 | 87 | #define PCAP_PACKET_FLAG_INBOUND 0x00000001 88 | #define PCAP_PACKET_FLAG_OUTBOUND 0x00000002 89 | 90 | #ifdef __cplusplus 91 | extern "C" { 92 | #endif 93 | 94 | MsRdpEx_PcapFile* MsRdpEx_PcapFile_Open(const char* name, bool write); 95 | void MsRdpEx_PcapFile_Close(MsRdpEx_PcapFile* pcap); 96 | 97 | bool MsRdpEx_PcapFile_AddRecord(MsRdpEx_PcapFile* pcap, const uint8_t* data, uint32_t length); 98 | bool MsRdpEx_PcapFile_HasNextRecord(MsRdpEx_PcapFile* pcap); 99 | bool MsRdpEx_PcapFile_GetNextRecord(MsRdpEx_PcapFile* pcap, PCAP_RECORD* record); 100 | bool MsRdpEx_PcapFile_GetNextRecordHeader(MsRdpEx_PcapFile* pcap, PCAP_RECORD* record); 101 | bool MsRdpEx_PcapFile_GetNextRecordContent(MsRdpEx_PcapFile* pcap, PCAP_RECORD* record); 102 | void MsRdpEx_PcapFile_Flush(MsRdpEx_PcapFile* pcap); 103 | 104 | void MsRdpEx_PcapFile_Lock(MsRdpEx_PcapFile* pcap); 105 | void MsRdpEx_PcapFile_Unlock(MsRdpEx_PcapFile* pcap); 106 | 107 | bool MsRdpEx_PcapFile_WritePacket(MsRdpEx_PcapFile* pcap, const uint8_t* data, size_t length, uint32_t flags); 108 | 109 | #ifdef __cplusplus 110 | } 111 | #endif 112 | 113 | #endif // MSRDPEX_PCAP_H 114 | -------------------------------------------------------------------------------- /include/MsRdpEx/RdpCoreApi.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_CORE_API_H 2 | #define MSRDPEX_CORE_API_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | struct __declspec(uuid("13F6E86F-EE7D-44D1-AA94-1136B784441D")) __declspec(novtable) 9 | IMsRdpExCoreApi : public IUnknown 10 | { 11 | public: 12 | virtual HRESULT __stdcall Load(void) = 0; 13 | virtual HRESULT __stdcall Unload(void) = 0; 14 | virtual const char* __stdcall GetMsRdpExDllPath() = 0; 15 | virtual void __stdcall SetLogEnabled(bool enabled) = 0; 16 | virtual void __stdcall SetLogLevel(uint32_t logLevel) = 0; 17 | virtual void __stdcall SetLogFilePath(const char* logFilePath) = 0; 18 | virtual void __stdcall SetPcapEnabled(bool enabled) = 0; 19 | virtual void __stdcall SetPcapFilePath(const char* pcapFilePath) = 0; 20 | virtual void __stdcall SetAxHookEnabled(bool axHookEnabled) = 0; 21 | virtual bool __stdcall QueryInstanceByWindowHandle(HWND hWnd, LPVOID* ppvObject) = 0; 22 | virtual bool __stdcall OpenInstanceForWindowHandle(HWND hWnd, LPVOID* ppvObject) = 0; 23 | }; 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif // MSRDPEX_CORE_API_H 36 | -------------------------------------------------------------------------------- /include/MsRdpEx/RdpFile.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_RDP_FILE_H 2 | #define MSRDPEX_RDP_FILE_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | struct _MsRdpEx_RdpFileEntry 15 | { 16 | char type; 17 | char* name; 18 | char* value; 19 | }; 20 | typedef struct _MsRdpEx_RdpFileEntry MsRdpEx_RdpFileEntry; 21 | 22 | bool MsRdpEx_RdpFileEntry_IsMatch(MsRdpEx_RdpFileEntry* entry, char type, const char* name); 23 | 24 | bool MsRdpEx_RdpFileEntry_GetBoolValue(MsRdpEx_RdpFileEntry* entry, bool* pValue); 25 | bool MsRdpEx_RdpFileEntry_GetVBoolValue(MsRdpEx_RdpFileEntry* entry, _Out_ VARIANT* pVariant); 26 | bool MsRdpEx_RdpFileEntry_GetIntValue(MsRdpEx_RdpFileEntry* entry, _Out_ VARIANT* pVariant); 27 | 28 | MsRdpEx_RdpFileEntry* MsRdpEx_RdpFileEntry_New(char type, const char* name, const char* value); 29 | void MsRdpEx_RdpFileEntry_Free(MsRdpEx_RdpFileEntry* entry); 30 | 31 | struct _MsRdpEx_RdpFile 32 | { 33 | MsRdpEx_ArrayList* entries; 34 | }; 35 | typedef struct _MsRdpEx_RdpFile MsRdpEx_RdpFile; 36 | 37 | char* MsRdpEx_GetRdpFilenameFromCommandLine(); 38 | 39 | bool MsRdpEx_RdpFile_Load(MsRdpEx_RdpFile* ctx, const char* filename); 40 | bool MsRdpEx_RdpFile_LoadText(MsRdpEx_RdpFile* ctx, const char* text); 41 | 42 | MsRdpEx_RdpFile* MsRdpEx_RdpFile_New(); 43 | void MsRdpEx_RdpFile_Free(MsRdpEx_RdpFile* ctx); 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | #endif // MSRDPEX_RDP_FILE_H 50 | -------------------------------------------------------------------------------- /include/MsRdpEx/RdpInstance.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_INSTANCE_H 2 | #define MSRDPEX_INSTANCE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | struct __declspec(novtable) 11 | IMsRdpExInstance : public IUnknown 12 | { 13 | public: 14 | virtual HRESULT __stdcall GetSessionId(GUID* pSessionId) = 0; 15 | virtual HRESULT __stdcall GetRdpClient(LPVOID* ppvObject) = 0; 16 | virtual HRESULT __stdcall GetOutputMirrorObject(LPVOID* ppvObject) = 0; 17 | virtual HRESULT __stdcall SetOutputMirrorObject(LPVOID pvObject) = 0; 18 | virtual HRESULT __stdcall GetOutputMirrorEnabled(bool* outputMirrorEnabled) = 0; 19 | virtual HRESULT __stdcall SetOutputMirrorEnabled(bool outputMirrorEnabled) = 0; 20 | virtual HRESULT __stdcall GetVideoRecordingEnabled(bool* videoRecordingEnabled) = 0; 21 | virtual HRESULT __stdcall SetVideoRecordingEnabled(bool videoRecordingEnabled) = 0; 22 | virtual HRESULT __stdcall GetDumpBitmapUpdates(bool* dumpBitmapUpdates) = 0; 23 | virtual HRESULT __stdcall SetDumpBitmapUpdates(bool dumpBitmapUpdates) = 0; 24 | virtual HRESULT __stdcall GetCorePropsRawPtr(LPVOID* ppCorePropsRaw) = 0; 25 | virtual HRESULT __stdcall SetCorePropsRawPtr(LPVOID pCorePropsRaw) = 0; 26 | virtual HRESULT __stdcall AttachInputWindow(HWND hOutputWnd, void* pUserData) = 0; 27 | virtual HRESULT __stdcall GetInputWindow(HWND* phInputCaptureWnd) = 0; 28 | virtual HRESULT __stdcall AttachOutputWindow(HWND hOutputWnd, void* pUserData) = 0; 29 | virtual HRESULT __stdcall GetOutputWindow(HWND* phOutputPresenterWnd) = 0; 30 | virtual HRESULT __stdcall AttachTscShellContainerWindow(HWND hTscShellContainerWnd) = 0; 31 | virtual HRESULT __stdcall GetTscShellContainerWindow(HWND* phTscShellContainerWnd) = 0; 32 | virtual HRESULT __stdcall AttachExtendedSettings(CMsRdpExtendedSettings* pExtendedSettings) = 0; 33 | virtual bool __stdcall GetExtendedSettings(CMsRdpExtendedSettings** ppExtendedSettings) = 0; 34 | virtual bool __stdcall GetShadowBitmap(HDC* phDC, HBITMAP* phBitmap, uint8_t** pBitmapData, 35 | uint32_t* pBitmapWidth, uint32_t* pBitmapHeight, uint32_t* pBitmapStep) = 0; 36 | virtual void __stdcall LockShadowBitmap() = 0; 37 | virtual void __stdcall UnlockShadowBitmap() = 0; 38 | virtual void __stdcall GetLastMousePosition(int32_t* posX, int32_t* posY) = 0; 39 | virtual void __stdcall SetLastMousePosition(int32_t posX, int32_t posY) = 0; 40 | virtual HRESULT __stdcall GetWTSPluginObject(LPVOID* ppvObject) = 0; 41 | virtual HRESULT __stdcall SetWTSPluginObject(LPVOID pvObject) = 0; 42 | }; 43 | 44 | class CMsRdpExInstance; 45 | class CMsRdpClient; 46 | 47 | #ifdef __cplusplus 48 | extern "C" { 49 | #endif 50 | 51 | typedef struct _MsRdpEx_InstanceManager MsRdpEx_InstanceManager; 52 | 53 | bool MsRdpEx_InstanceManager_Add(CMsRdpExInstance* instance); 54 | bool MsRdpEx_InstanceManager_Remove(CMsRdpExInstance* instance); 55 | 56 | CMsRdpExInstance* MsRdpEx_InstanceManager_FindByOutputPresenterHwnd(HWND hWnd); 57 | 58 | CMsRdpExInstance* MsRdpEx_InstanceManager_AttachOutputWindow(HWND hOutputWnd, void* pUserData); 59 | 60 | CMsRdpExInstance* MsRdpEx_InstanceManager_FindByInputCaptureHwnd(HWND hWnd); 61 | 62 | CMsRdpExInstance* MsRdpEx_InstanceManager_AttachInputWindow(HWND hInputWnd, void* pUserData); 63 | 64 | CMsRdpExInstance* MsRdpEx_InstanceManager_FindByTscShellContainerWnd(HWND hWnd); 65 | 66 | CMsRdpExInstance* MsRdpEx_InstanceManager_FindBySessionId(GUID* sessionId); 67 | 68 | CMsRdpExtendedSettings* MsRdpEx_FindExtendedSettingsBySessionId(GUID* sessionId); 69 | 70 | MsRdpEx_InstanceManager* MsRdpEx_InstanceManager_Get(); 71 | void MsRdpEx_InstanceManager_Release(); 72 | 73 | CMsRdpExInstance* CMsRdpExInstance_New(CMsRdpClient* pMsRdpClient); 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif // MSRDPEX_INSTANCE_H 80 | -------------------------------------------------------------------------------- /include/MsRdpEx/RdpProcess.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_PROCESS_LAUNCHER_H 2 | #define MSRDPEX_PROCESS_LAUNCHER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | struct __declspec(novtable) 9 | IMsRdpExProcess : public IUnknown 10 | { 11 | public: 12 | virtual void __stdcall SetFileName(const char* filename) = 0; 13 | virtual void __stdcall SetArguments(const char* arguments) = 0; 14 | virtual void __stdcall SetArgumentBlock(const char* argumentBlock) = 0; 15 | virtual void __stdcall SetEnvironmentBlock(const char* environmentBlock) = 0; 16 | virtual void __stdcall SetWorkingDirectory(const char* workingDirectory) = 0; 17 | virtual HRESULT __stdcall StartWithInfo() = 0; 18 | virtual HRESULT __stdcall Start(int argc, char** argv, const char* appName, const char* axName) = 0; 19 | virtual HRESULT __stdcall Stop(uint32_t exitCode) = 0; 20 | virtual HRESULT __stdcall Wait(uint32_t milliseconds) = 0; 21 | virtual uint32_t __stdcall GetProcessId() = 0; 22 | virtual uint32_t __stdcall GetExitCode() = 0; 23 | }; 24 | 25 | #ifdef __cplusplus 26 | extern "C" { 27 | #endif 28 | 29 | HRESULT MsRdpEx_LaunchProcess(int argc, char** argv, const char* appName, const char* axName); 30 | 31 | char** MsRdpEx_GetArgumentVector(int* argc); 32 | void MsRdpEx_FreeArgumentVector(int argc, char** argv); 33 | 34 | HRESULT MsRdpExProcess_CreateInstance(LPVOID* ppvObject); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | 40 | #endif // MSRDPEX_PROCESS_LAUNCHER_H 41 | -------------------------------------------------------------------------------- /include/MsRdpEx/RdpSettings.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_SETTINGS_H 2 | #define MSRDPEX_SETTINGS_H 3 | 4 | #include 5 | 6 | #include "TSObjects.h" 7 | 8 | #include 9 | 10 | #define MOUSE_JIGGLER_METHOD_MOUSE_MOVE 0 11 | #define MOUSE_JIGGLER_METHOD_SPECIAL_KEY 1 12 | 13 | class CMsRdpExtendedSettings; 14 | class CMsRdpPropertySet; 15 | 16 | class CMsRdpExtendedSettings : public IMsRdpExtendedSettings 17 | { 18 | public: 19 | CMsRdpExtendedSettings(IUnknown* pUnknown, GUID* pSessionId); 20 | ~CMsRdpExtendedSettings(); 21 | 22 | // IUnknown interface 23 | public: 24 | HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID* ppvObject); 25 | ULONG STDMETHODCALLTYPE AddRef(); 26 | ULONG STDMETHODCALLTYPE Release(); 27 | 28 | // IMsRdpExtendedSettings 29 | public: 30 | HRESULT __stdcall put_Property(BSTR bstrPropertyName, VARIANT* pValue); 31 | HRESULT __stdcall get_Property(BSTR bstrPropertyName, VARIANT* pValue); 32 | 33 | // additional stuff 34 | public: 35 | HRESULT __stdcall put_CoreProperty(BSTR bstrPropertyName, VARIANT* pValue); 36 | HRESULT __stdcall get_CoreProperty(BSTR bstrPropertyName, VARIANT* pValue); 37 | HRESULT __stdcall put_BaseProperty(BSTR bstrPropertyName, VARIANT* pValue); 38 | HRESULT __stdcall get_BaseProperty(BSTR bstrPropertyName, VARIANT* pValue); 39 | HRESULT __stdcall SetTargetPassword(const char* password); 40 | HRESULT __stdcall SetGatewayPassword(const char* password); 41 | HRESULT __stdcall SetKdcProxyUrl(const char* kdcProxyUrl); 42 | HRESULT __stdcall SetRecordingPath(const char* recordingPath); 43 | HRESULT __stdcall SetRecordingPipeName(const char* recordingPipeName); 44 | HRESULT __stdcall AttachRdpClient(IMsTscAx* pMsTscAx); 45 | HRESULT __stdcall ApplyRdpFile(void* rdpFilePtr); 46 | HRESULT __stdcall LoadRdpFile(const char* rdpFileName); 47 | HRESULT __stdcall LoadRdpFileFromNamedPipe(const char* pipeName); 48 | HRESULT __stdcall GetCorePropsRawPtr(LPVOID* ppCorePropsRaw); 49 | HRESULT __stdcall PrepareSspiSessionIdHack(); 50 | HRESULT __stdcall PrepareMouseJiggler(); 51 | HRESULT __stdcall PrepareVideoRecorder(); 52 | HRESULT __stdcall PrepareExtraSystemMenu(); 53 | char* __stdcall GetKdcProxyUrl(); 54 | char* __stdcall GetKdcProxyName(); 55 | bool GetMouseJigglerEnabled(); 56 | uint32_t GetMouseJigglerInterval(); 57 | uint32_t GetMouseJigglerMethod(); 58 | bool GetKeyboardHookToggleShortcutEnabled(); 59 | const char* GetKeyboardHookToggleShortcutKey(); 60 | const char* GetSessionId(); 61 | bool GetOutputMirrorEnabled(); 62 | bool GetVideoRecordingEnabled(); 63 | uint32_t GetVideoRecordingQuality(); 64 | char* GetRecordingPath(); 65 | char* GetRecordingPipeName(); 66 | bool GetDumpBitmapUpdates(); 67 | bool GetExtraSystemMenuEnabled(); 68 | 69 | private: 70 | GUID m_sessionId; 71 | char m_sessionIdStr[MSRDPEX_GUID_STRING_SIZE]; 72 | ULONG m_refCount = 0; 73 | IUnknown* m_pUnknown = NULL; 74 | IMsTscAx* m_pMsTscAx = NULL; 75 | IMsRdpClient7* m_pMsRdpClient7 = NULL; 76 | IMsRdpExtendedSettings* m_pMsRdpExtendedSettings = NULL; 77 | IMsRdpClientTransportSettings2* m_pMsRdpClientTransportSettings2 = NULL; 78 | ITSPropertySet* m_pCorePropsRaw = NULL; 79 | CMsRdpPropertySet* m_CoreProps = NULL; 80 | CMsRdpPropertySet* m_BaseProps = NULL; 81 | CMsRdpPropertySet* m_TransportProps = NULL; 82 | char* m_KdcProxyUrl = NULL; 83 | bool m_MouseJigglerEnabled = false; 84 | uint32_t m_MouseJigglerInterval = 60; 85 | uint32_t m_MouseJigglerMethod = 0; 86 | bool m_OutputMirrorEnabled = false; 87 | bool m_VideoRecordingEnabled = false; 88 | uint32_t m_VideoRecordingQuality = 5; 89 | char* m_RecordingPath = NULL; 90 | char* m_RecordingPipeName = NULL; 91 | bool m_DumpBitmapUpdates = false; 92 | bool m_ExtraSystemMenuEnabled = true; 93 | bool m_KeyboardHookToggleShortcutEnabled = false; 94 | char m_KeyboardHookToggleShortcutKey[32]; 95 | IUnknown* m_pWTSPlugin = NULL; 96 | }; 97 | 98 | #ifdef __cplusplus 99 | extern "C" { 100 | #endif 101 | 102 | CMsRdpExtendedSettings* CMsRdpExtendedSettings_New(IUnknown* pUnknown, IUnknown* pMsTscAx, GUID* pSessionId); 103 | 104 | char* MsRdpEx_KdcProxyUrlToName(const char* kdcProxyUrl); 105 | 106 | #ifdef __cplusplus 107 | } 108 | #endif 109 | 110 | #endif // MSRDPEX_SETTINGS_H 111 | -------------------------------------------------------------------------------- /include/MsRdpEx/RecordingManifest.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_RECORDING_MANIFEST_H 2 | #define MSRDPEX_RECORDING_MANIFEST_H 3 | 4 | #include "MsRdpEx.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct _MsRdpEx_JrecFile MsRdpEx_JrecFile; 11 | 12 | typedef struct _MsRdpEx_RecordingManifest MsRdpEx_RecordingManifest; 13 | 14 | void MsRdpEx_RecordingManifest_SetSessionId(MsRdpEx_RecordingManifest* ctx, GUID* sessionId); 15 | void MsRdpEx_RecordingManifest_SetStartTime(MsRdpEx_RecordingManifest* ctx, int64_t startTime); 16 | void MsRdpEx_RecordingManifest_SetDuration(MsRdpEx_RecordingManifest* ctx, int64_t duration); 17 | 18 | bool MsRdpEx_RecordingManifest_AddFile(MsRdpEx_RecordingManifest* ctx, const char* fileName, int64_t startTime, int64_t duration); 19 | void MsRdpEx_RecordingManifest_FinalizeFile(MsRdpEx_RecordingManifest* ctx, int64_t endTime); 20 | 21 | char* MsRdpEx_RecordingManifest_WriteJsonData(MsRdpEx_RecordingManifest* ctx); 22 | bool MsRdpEx_RecordingManifest_WriteJsonFile(MsRdpEx_RecordingManifest* ctx, const char* filename); 23 | 24 | MsRdpEx_RecordingManifest* MsRdpEx_RecordingManifest_New(); 25 | void MsRdpEx_RecordingManifest_Free(MsRdpEx_RecordingManifest* ctx); 26 | 27 | #ifdef __cplusplus 28 | } 29 | #endif 30 | 31 | #endif /* MSRDPEX_RECORDING_MANIFEST_H */ 32 | -------------------------------------------------------------------------------- /include/MsRdpEx/Sspi.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_SSPI_H 2 | #define MSRDPEX_SSPI_H 3 | 4 | #include 5 | 6 | #define SECURITY_WIN32 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | LONG MsRdpEx_AttachSspiHooks(); 17 | LONG MsRdpEx_DetachSspiHooks(); 18 | 19 | #ifdef __cplusplus 20 | } 21 | #endif 22 | 23 | #endif // MSRDPEX_SSPI_H 24 | -------------------------------------------------------------------------------- /include/MsRdpEx/Stopwatch.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_STOPWATCH_H 2 | #define MSRDPEX_STOPWATCH_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct msrdpex_stopwatch 11 | { 12 | LARGE_INTEGER time; 13 | ULONGLONG tickCount; 14 | bool enabled; 15 | bool highPrecision; 16 | }; 17 | 18 | typedef struct msrdpex_stopwatch MsRdpEx_Stopwatch; 19 | 20 | void MsRdpEx_ProfilingInit(); 21 | 22 | void MsRdpEx_Stopwatch_Init(MsRdpEx_Stopwatch* stopwatch, uint32_t profilingLevel, bool start); 23 | void MsRdpEx_Stopwatch_InitEx(MsRdpEx_Stopwatch* stopwatch, uint32_t profilingLevel, bool start, bool highPrecision); 24 | void MsRdpEx_Stopwatch_Start(MsRdpEx_Stopwatch* stopwatch); 25 | double MsRdpEx_Stopwatch_GetTime(MsRdpEx_Stopwatch* stopwatch); 26 | void MsRdpEx_Stopwatch_Print(MsRdpEx_Stopwatch* stopwatch, uint32_t logLevel, const char* message); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif /* MSRDPEX_STOPWATCH_H */ 33 | -------------------------------------------------------------------------------- /include/MsRdpEx/VideoRecorder.h: -------------------------------------------------------------------------------- 1 | #ifndef MSRDPEX_VIDEO_RECORDER_H 2 | #define MSRDPEX_VIDEO_RECORDER_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef void XmfRecorder; 11 | typedef void XmfWebMMuxer; 12 | typedef void XmfBipBlock; 13 | typedef void XmfBipBuffer; 14 | 15 | typedef XmfRecorder* (CDECL * fnXmfRecorder_New)(); 16 | typedef bool (CDECL* fnXmfRecorder_Init)(XmfRecorder* ctx); 17 | typedef void (CDECL* fnXmfRecorder_Uninit)(XmfRecorder* ctx); 18 | typedef void (CDECL* fnXmfRecorder_SetFrameSize)(XmfRecorder* ctx, uint32_t width, uint32_t height); 19 | typedef void (CDECL* fnXmfRecorder_SetFrameRate)(XmfRecorder* ctx, uint32_t frameRate); 20 | typedef void (CDECL* fnXmfRecorder_SetVideoQuality)(XmfRecorder* ctx, uint32_t videoQuality); 21 | typedef void (CDECL* fnXmfRecorder_SetFileName)(XmfRecorder* ctx, const char* filename); 22 | typedef void (CDECL* fnXmfRecorder_SetBipBuffer)(XmfRecorder* ctx, XmfBipBuffer* bb); 23 | typedef void (CDECL* fnXmfRecorder_UpdateFrame)(XmfRecorder* ctx, uint8_t* buffer, uint32_t updateX, 24 | uint32_t updateY, uint32_t updateWidth, uint32_t updateHeight, uint32_t surfaceStep); 25 | typedef void (CDECL* fnXmfRecorder_Timeout)(XmfRecorder* ctx); 26 | typedef uint32_t (CDECL* fnXmfRecorder_GetTimeout)(XmfRecorder* ctx); 27 | typedef void (CDECL* fnXmfRecorder_Free)(XmfRecorder* ctx); 28 | 29 | typedef XmfWebMMuxer* (CDECL* fnXmfWebMMuxer_New)(); 30 | typedef int (CDECL* fnXmfWebMMuxer_Remux)(XmfWebMMuxer* ctx, const char* inputFile, const char* outputFile); 31 | typedef void (CDECL* fnXmfWebMMuxer_Free)(XmfWebMMuxer* ctx); 32 | 33 | typedef bool (CDECL* fnXmfBipBuffer_Grow)(XmfBipBuffer* ctx, size_t size); 34 | typedef void (CDECL* fnXmfBipBuffer_Clear)(XmfBipBuffer* ctx); 35 | typedef size_t(CDECL* fnXmfBipBuffer_UsedSize)(XmfBipBuffer* ctx); 36 | typedef size_t(CDECL* fnXmfBipBuffer_BufferSize)(XmfBipBuffer* ctx); 37 | typedef uint8_t* (CDECL* fnXmfBipBuffer_WriteReserve)(XmfBipBuffer* ctx, size_t size); 38 | typedef uint8_t* (CDECL* fnXmfBipBuffer_WriteTryReserve)(XmfBipBuffer* ctx, size_t size, size_t* reserved); 39 | typedef void (CDECL* fnXmfBipBuffer_WriteCommit)(XmfBipBuffer* ctx, size_t size); 40 | typedef uint8_t* (CDECL* fnXmfBipBuffer_ReadReserve)(XmfBipBuffer* ctx, size_t size); 41 | typedef uint8_t* (CDECL* fnXmfBipBuffer_ReadTryReserve)(XmfBipBuffer* ctx, size_t size, size_t* reserved); 42 | typedef void (CDECL* fnXmfBipBuffer_ReadCommit)(XmfBipBuffer* ctx, size_t size); 43 | typedef int (CDECL* fnXmfBipBuffer_Read)(XmfBipBuffer* ctx, uint8_t* data, size_t size); 44 | typedef int (CDECL* fnXmfBipBuffer_Write)(XmfBipBuffer* ctx, const uint8_t* data, size_t size); 45 | typedef void (CDECL* fnXmfBipBuffer_SetSignaledState)(XmfBipBuffer* ctx, bool signaled); 46 | typedef bool (CDECL* fnXmfBipBuffer_GetSignaledState)(XmfBipBuffer* ctx); 47 | typedef XmfBipBuffer* (CDECL* fnXmfBipBuffer_New)(size_t size); 48 | typedef void (CDECL* fnXmfBipBuffer_Free)(XmfBipBuffer* ctx); 49 | 50 | struct _MsRdpEx_VideoRecorder 51 | { 52 | HMODULE hModule; 53 | XmfRecorder* recorder; 54 | fnXmfRecorder_New XmfRecorder_New; 55 | fnXmfRecorder_Init XmfRecorder_Init; 56 | fnXmfRecorder_Uninit XmfRecorder_Uninit; 57 | fnXmfRecorder_SetFrameSize XmfRecorder_SetFrameSize; 58 | fnXmfRecorder_SetFrameRate XmfRecorder_SetFrameRate; 59 | fnXmfRecorder_SetVideoQuality XmfRecorder_SetVideoQuality; 60 | fnXmfRecorder_SetFileName XmfRecorder_SetFileName; 61 | fnXmfRecorder_SetBipBuffer XmfRecorder_SetBipBuffer; 62 | fnXmfRecorder_UpdateFrame XmfRecorder_UpdateFrame; 63 | fnXmfRecorder_Timeout XmfRecorder_Timeout; 64 | fnXmfRecorder_GetTimeout XmfRecorder_GetTimeout; 65 | fnXmfRecorder_Free XmfRecorder_Free; 66 | 67 | fnXmfWebMMuxer_New XmfWebMMuxer_New; 68 | fnXmfWebMMuxer_Remux XmfWebMMuxer_Remux; 69 | fnXmfWebMMuxer_Free XmfWebMMuxer_Free; 70 | 71 | fnXmfBipBuffer_Grow XmfBipBuffer_Grow; 72 | fnXmfBipBuffer_Clear XmfBipBuffer_Clear; 73 | fnXmfBipBuffer_UsedSize XmfBipBuffer_UsedSize; 74 | fnXmfBipBuffer_BufferSize XmfBipBuffer_BufferSize; 75 | fnXmfBipBuffer_WriteReserve XmfBipBuffer_WriteReserve; 76 | fnXmfBipBuffer_WriteTryReserve XmfBipBuffer_WriteTryReserve; 77 | fnXmfBipBuffer_WriteCommit XmfBipBuffer_WriteCommit; 78 | fnXmfBipBuffer_ReadReserve XmfBipBuffer_ReadReserve; 79 | fnXmfBipBuffer_ReadTryReserve XmfBipBuffer_ReadTryReserve; 80 | fnXmfBipBuffer_ReadCommit XmfBipBuffer_ReadCommit; 81 | fnXmfBipBuffer_Read XmfBipBuffer_Read; 82 | fnXmfBipBuffer_Write XmfBipBuffer_Write; 83 | fnXmfBipBuffer_SetSignaledState XmfBipBuffer_SetSignaledState; 84 | fnXmfBipBuffer_GetSignaledState XmfBipBuffer_GetSignaledState; 85 | fnXmfBipBuffer_New XmfBipBuffer_New; 86 | fnXmfBipBuffer_Free XmfBipBuffer_Free; 87 | 88 | FILE* fp; 89 | HANDLE np_handle; 90 | char* np_name; 91 | bool useBipBuffer; 92 | XmfBipBuffer* bb; 93 | size_t bipBufferSize; 94 | char filename[MSRDPEX_MAX_PATH]; 95 | }; 96 | typedef struct _MsRdpEx_VideoRecorder MsRdpEx_VideoRecorder; 97 | 98 | bool MsRdpEx_VideoRecorder_Init(MsRdpEx_VideoRecorder* ctx); 99 | void MsRdpEx_VideoRecorder_Uninit(MsRdpEx_VideoRecorder* ctx); 100 | 101 | void MsRdpEx_VideoRecorder_SetFrameSize(MsRdpEx_VideoRecorder* ctx, uint32_t width, uint32_t height); 102 | void MsRdpEx_VideoRecorder_SetFrameRate(MsRdpEx_VideoRecorder* ctx, uint32_t frameRate); 103 | void MsRdpEx_VideoRecorder_SetVideoQuality(MsRdpEx_VideoRecorder* ctx, uint32_t videoQuality); 104 | 105 | void MsRdpEx_VideoRecorder_SetFileName(MsRdpEx_VideoRecorder* ctx, const char* filename); 106 | void MsRdpEx_VideoRecorder_SetPipeName(MsRdpEx_VideoRecorder* ctx, const char* pipeName); 107 | 108 | void MsRdpEx_VideoRecorder_UpdateFrame(MsRdpEx_VideoRecorder* ctx, 109 | uint8_t* buffer, uint32_t updateX, uint32_t updateY, 110 | uint32_t updateWidth, uint32_t updateHeight, uint32_t surfaceStep); 111 | 112 | void MsRdpEx_VideoRecorder_Timeout(MsRdpEx_VideoRecorder* ctx); 113 | uint32_t MsRdpEx_VideoRecorder_GetTimeout(MsRdpEx_VideoRecorder* ctx); 114 | 115 | bool MsRdpEx_VideoRecorder_Remux(MsRdpEx_VideoRecorder* ctx, const char* filename); 116 | 117 | MsRdpEx_VideoRecorder* MsRdpEx_VideoRecorder_New(); 118 | void MsRdpEx_VideoRecorder_Free(MsRdpEx_VideoRecorder* ctx); 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #endif // MSRDPEX_VIDEO_RECORDER_H 125 | -------------------------------------------------------------------------------- /installer/Folders.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /installer/MsRdpEx.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "MsRdpEx", "MsRdpEx.wixproj", "{A8FFAEDD-525B-46F0-BC38-A984E659DD23}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|ARM64 = Debug|ARM64 11 | Debug|x64 = Debug|x64 12 | Debug|Win32 = Debug|Win32 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | Release|Win32 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|ARM64.ActiveCfg = Debug|ARM64 19 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|ARM64.Build.0 = Debug|ARM64 20 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|x64.ActiveCfg = Debug|x64 21 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|x64.Build.0 = Debug|x64 22 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|Win32.ActiveCfg = Debug|Win32 23 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Debug|Win32.Build.0 = Debug|Win32 24 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|ARM64.ActiveCfg = Release|ARM64 25 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|ARM64.Build.0 = Release|ARM64 26 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|x64.ActiveCfg = Release|x64 27 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|x64.Build.0 = Release|x64 28 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|Win32.ActiveCfg = Release|Win32 29 | {A8FFAEDD-525B-46F0-BC38-A984E659DD23}.Release|Win32.Build.0 = Release|Win32 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {E5B39C52-A0A1-4A89-9739-4B0326F040A0} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /installer/MsRdpEx.wixproj: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /installer/MsRdpEx.wxs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /installer/Package.en-us.wxl: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /installer/Package.wxs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /installer/Variables.wxi: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /scripts/SetAssemblyTargetFramework.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string] $AssemblyFilePath, 3 | [string] $FrameworkName = ".NETFramework,Version=v4.8", 4 | [string] $FrameworkDisplayName = ".NET Framework 4.8" 5 | ) 6 | 7 | if (-Not (Test-Path $AssemblyFilePath)) { 8 | throw "Assembly file not found at the specified path: $AssemblyFilePath" 9 | } 10 | 11 | $ilasm = "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ilasm.exe" 12 | $ildasm = "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\ildasm.exe" 13 | 14 | if (-Not (Test-Path $ilasm)) { 15 | throw "ilasm.exe not found at the path: $ilasm" 16 | } 17 | 18 | if (-Not (Test-Path $ildasm)) { 19 | throw "ildasm.exe not found at the path: $ildasm" 20 | } 21 | 22 | if (-Not (Get-Command -Name 'rg' -ErrorAction SilentlyContinue)) { 23 | throw "rg (RipGrep) not found" 24 | } 25 | 26 | $AssemblyName = [System.IO.Path]::GetFileNameWithoutExtension($AssemblyFilePath) 27 | $AssemblyDllFile = $AssemblyFilePath 28 | $AssemblyIlfile = "${AssemblyName}.il" 29 | $AssemblyResfile = "${AssemblyName}.res" 30 | & $ildasm "/OUT=$AssemblyIlfile" /NOBAR $AssemblyDllFile 31 | 32 | # https://learn.microsoft.com/en-us/dotnet/api/system.runtime.versioning.targetframeworkattribute 33 | 34 | # 01 00 35 | # 1A (26) 36 | # 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 38 ".NETFramework,Version=v4.8" 37 | # 01 00 54 0E 38 | # 14 (20) 39 | # 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C 61 79 4E 61 6D 65 "FrameworkDisplayName" 40 | # 12 (18) 41 | # 2E 4E 45 54 20 46 72 61 6D 65 77 6F 72 6B 20 34 2E 38 ".NET Framework 4.8" 42 | 43 | function Convert-StringToByteArray { 44 | param( 45 | [Parameter(Mandatory=$true,Position=0)] 46 | [string] $InputString 47 | ) 48 | 49 | if ($InputString.Length -gt 255) { 50 | throw "String length exceeds the maximum limit for a single byte." 51 | } 52 | 53 | $lengthByte = [byte]$InputString.Length 54 | $utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($InputString) 55 | return , $lengthByte + $utf8Bytes 56 | } 57 | 58 | $ctorBytes = @(0x01, 0x00) 59 | $ctorBytes = $ctorBytes + (Convert-StringToByteArray $FrameworkName) 60 | $ctorBytes = $ctorBytes + @(0x01, 0x00, 0x54, 0x0E) 61 | $ctorBytes = $ctorBytes + (Convert-StringToByteArray "FrameworkDisplayName") 62 | $ctorBytes = $ctorBytes + (Convert-StringToByteArray $FrameworkDisplayName) 63 | $ctorHex = ($ctorBytes | ForEach-Object { $_.ToString("X2") }) -join " " 64 | 65 | $lineToAdd = " .custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( $CtorHex )" 66 | $lineAfter = " .hash algorithm" 67 | $NewContent = rg "$lineAfter" $AssemblyIlfile -r "$lineToAdd`r`n$lineAfter" -N --passthru 68 | Set-Content -Path $AssemblyIlfile -Value $NewContent -Force 69 | 70 | & $ilasm $AssemblyIlfile "/OUTPUT=$AssemblyDllfile" /DLL "/RESOURCE=$AssemblyResfile" 71 | @($AssemblyIlfile, $AssemblyResfile) | Remove-Item -Force -ErrorAction SilentlyContinue 72 | --------------------------------------------------------------------------------