Skip to content

Commit f17620b

Browse files
Add public ActiveX backend mode (#176)
## Summary - add `MSRDPEX_AX_BACKEND=public` to disable private ActiveX layout assumptions for replacement controls - automatically select public mode for `MSRDPEX_MSTSCAX_DLL` paths named `ironrdpax.dll` - retain Microsoft private-layout behavior by default and document the unavailable private integrations in public mode ## Validation - `dotnet test .\dotnet\MsRdpEx_Test\MsRdpEx_Test.csproj -c Release --no-restore` (701 passed) Native compilation was not run because Visual Studio/MSVC is unavailable in the environment. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 24a0ba2 commit f17620b

8 files changed

Lines changed: 78 additions & 11 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ $Env:MSRDPEX_LOG_FILE_PATH="C:\Windows\Temp\MsRdpEx.log"
135135

136136
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.
137137

138+
## Public ActiveX backends
139+
140+
Set `MSRDPEX_AX_BACKEND=public` for a non-Microsoft `mstscax`-compatible replacement selected by `MSRDPEX_MSTSCAX_DLL`. MsRdpEx also automatically uses public mode when that replacement DLL is named `ironrdpax.dll`, unless `MSRDPEX_AX_BACKEND` is explicitly set. MsRdpEx continues to load the replacement DLL and forward its public COM class factory, but does not inspect the replacement client's undocumented Microsoft object layout.
141+
142+
Public mode intentionally disables private extended-settings/property hooks and extended `.RDP` option processing, input/output window association (including output mirroring and recording), SSPI session correlation, and other features that depend on Microsoft private ActiveX internals. The default `private` mode retains standard `mstscax.dll` behavior. Any explicit backend value other than `public` retains private mode; automatic `ironrdpax.dll` detection applies only when the backend option is unset.
143+
138144
## Building from source
139145

140146
Generate the Visual Studio project files for your target platform:

dll/ApiHooks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ LRESULT CALLBACK Hook_OPWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
674674
result = Real_OPWndProc_mstscax(hWnd, uMsg, wParam, lParam);
675675
}
676676

677-
if (uMsg == WM_NCCREATE)
677+
if ((uMsg == WM_NCCREATE) && MsRdpEx_UsePrivateAxLayout())
678678
{
679679
void* pUserData = (void*) GetWindowLongPtrW(hWnd, GWLP_USERDATA);
680680

dll/AxDll.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,19 @@ bool CDECL MsRdpEx_mstscax_Load(MsRdpEx_mstscax* dll, const char* filename)
5555
dll->DllCancelAuthentication = (fnDllCancelAuthentication)GetProcAddress(dll->hModule, "DllCancelAuthentication");
5656
dll->DllDeleteSavedCreds = (fnDllDeleteSavedCreds)GetProcAddress(dll->hModule, "DllDeleteSavedCreds");
5757

58-
dll->tscCtlVer = dll->DllGetTscCtlVer();
58+
if (dll->DllGetTscCtlVer)
59+
{
60+
dll->tscCtlVer = dll->DllGetTscCtlVer();
61+
}
62+
else if (MsRdpEx_UsePrivateAxLayout())
63+
{
64+
MsRdpEx_LogPrint(ERROR, "mstscax_load(%s): missing DllGetTscCtlVer", filename);
65+
goto exit;
66+
}
67+
else
68+
{
69+
MsRdpEx_LogPrint(DEBUG, "mstscax_load(%s): DllGetTscCtlVer is unavailable in public mode", filename);
70+
}
5971

6072
success = true;
6173
exit:

dll/MsRdpClient.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -497,13 +497,16 @@ class CMsRdpClient : public IMsRdpClient10
497497
HRESULT hr;
498498
MsRdpEx_LogPrint(DEBUG, "CMsRdpClient::Connect");
499499

500-
m_pMsRdpExtendedSettings->LoadRdpFile(NULL);
501-
m_pMsRdpExtendedSettings->LoadRdpFileFromNamedPipe(NULL);
502-
m_pMsRdpExtendedSettings->PrepareSspiSessionIdHack();
503-
m_pMsRdpExtendedSettings->DiscardCapturedPinIfNotCertLogon();
504-
m_pMsRdpExtendedSettings->PrepareMouseJiggler();
505-
m_pMsRdpExtendedSettings->PrepareVideoRecorder();
506-
m_pMsRdpExtendedSettings->PrepareExtraSystemMenu();
500+
if (MsRdpEx_UsePrivateAxLayout())
501+
{
502+
m_pMsRdpExtendedSettings->LoadRdpFile(NULL);
503+
m_pMsRdpExtendedSettings->LoadRdpFileFromNamedPipe(NULL);
504+
m_pMsRdpExtendedSettings->PrepareSspiSessionIdHack();
505+
m_pMsRdpExtendedSettings->DiscardCapturedPinIfNotCertLogon();
506+
m_pMsRdpExtendedSettings->PrepareMouseJiggler();
507+
m_pMsRdpExtendedSettings->PrepareVideoRecorder();
508+
m_pMsRdpExtendedSettings->PrepareExtraSystemMenu();
509+
}
507510

508511
BeginSspiSessionScope("connect");
509512
hr = m_pMsTscAx->raw_Connect();
@@ -525,6 +528,9 @@ class CMsRdpClient : public IMsRdpClient10
525528
}
526529

527530
void BeginSspiSessionScope(const char* reason) {
531+
if (!MsRdpEx_UsePrivateAxLayout())
532+
return;
533+
528534
if (m_sspiSessionActive)
529535
return;
530536

dll/MsRdpEx.cpp

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,12 @@ uint64_t DllGetTscCtlVer()
108108
uint64_t version = 0;
109109

110110
if (g_IsOOBClient) {
111-
version = g_rdclientax.DllGetTscCtlVer();
111+
if (g_rdclientax.DllGetTscCtlVer)
112+
version = g_rdclientax.DllGetTscCtlVer();
112113
}
113114
else {
114-
version = g_mstscax.DllGetTscCtlVer();
115+
if (g_mstscax.DllGetTscCtlVer)
116+
version = g_mstscax.DllGetTscCtlVer();
115117
}
116118

117119
MsRdpEx_LogPrint(DEBUG, "DllGetTscCtlVer: 0x%04X", (unsigned int)version);
@@ -725,6 +727,33 @@ HRESULT MsRdpEx_PreCleanUp()
725727
return DllPreCleanUp();
726728
}
727729

730+
bool CDECL MsRdpEx_UsePrivateAxLayout()
731+
{
732+
bool usePrivateAxLayout = true;
733+
char* axBackend = MsRdpEx_GetEnv("MSRDPEX_AX_BACKEND");
734+
char* axDll = NULL;
735+
736+
if (axBackend)
737+
{
738+
if (MsRdpEx_StringIEquals(axBackend, "public"))
739+
usePrivateAxLayout = false;
740+
}
741+
else
742+
{
743+
axDll = MsRdpEx_GetEnv("MSRDPEX_MSTSCAX_DLL");
744+
745+
if (axDll &&
746+
MsRdpEx_StringIEquals(MsRdpEx_FileBase(axDll), "ironrdpax.dll"))
747+
{
748+
usePrivateAxLayout = false;
749+
}
750+
}
751+
752+
free(axDll);
753+
free(axBackend);
754+
return usePrivateAxLayout;
755+
}
756+
728757
bool MsRdpEx_DetectClientProcess(bool* pIsOOBClient)
729758
{
730759
char moduleFilePath[MSRDPEX_MAX_PATH] = { 0 };

dll/MsRdpEx.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ typedef struct _MsRdpEx_rdclientax MsRdpEx_rdclientax;
189189

190190
void MsRdpEx_SetAxHookEnabled(bool axHookEnabled);
191191

192+
bool CDECL MsRdpEx_UsePrivateAxLayout();
193+
192194
HRESULT MsRdpEx_AxDll_DllGetClassObject(fnDllGetClassObject pfnDllGetClassObject, REFCLSID rclsid, REFIID riid, LPVOID* ppv);
193195

194196
bool CDECL MsRdpEx_mstscax_Load(MsRdpEx_mstscax* dll, const char* filename);

dll/RdpInstance.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,9 @@ IMsRdpExInstance* MsRdpEx_InstanceManager_AcquireByOutputPresenterHwnd(HWND hWnd
509509

510510
CMsRdpExInstance* MsRdpEx_InstanceManager_AttachOutputWindow(HWND hOutputWnd, void* pUserData)
511511
{
512+
if (!MsRdpEx_UsePrivateAxLayout())
513+
return NULL;
514+
512515
MsRdpEx_InstanceManager* ctx = g_InstanceManager;
513516

514517
if (!ctx)
@@ -631,6 +634,9 @@ IMsRdpExInstance* MsRdpEx_InstanceManager_AcquireByInputCaptureHwnd(HWND hWnd)
631634

632635
CMsRdpExInstance* MsRdpEx_InstanceManager_AttachInputWindow(HWND hInputWnd, void* pUserData)
633636
{
637+
if (!MsRdpEx_UsePrivateAxLayout())
638+
return NULL;
639+
634640
MsRdpEx_InstanceManager* ctx = g_InstanceManager;
635641

636642
if (!ctx)

dll/RdpSettings.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1201,6 +1201,9 @@ HRESULT CMsRdpExtendedSettings::AttachRdpClient(IMsTscAx* pMsTscAx)
12011201
{
12021202
m_pMsTscAx = pMsTscAx;
12031203

1204+
if (!MsRdpEx_UsePrivateAxLayout())
1205+
return S_OK;
1206+
12041207
ITSObjectBase* pTSWin32CoreApi = NULL;
12051208
ITSPropertySet* pTSCoreProps = NULL;
12061209
ITSPropertySet* pTSBaseProps = NULL;
@@ -1595,6 +1598,9 @@ HRESULT CMsRdpExtendedSettings::PrepareSspiSessionIdHack()
15951598
char fakeKdcProxyName[256];
15961599
char sessionId[MSRDPEX_GUID_STRING_SIZE];
15971600

1601+
if (!MsRdpEx_UsePrivateAxLayout())
1602+
return S_OK;
1603+
15981604
if (!m_CoreProps) {
15991605
MsRdpEx_LogPrint(ERROR, "PrepareSspiSessionIdHack - m_CoreProps is NULL!");
16001606
return E_UNEXPECTED;

0 commit comments

Comments
 (0)