1 /*
2  * PROJECT:     ReactOS Compatibility Layer Shell Extension
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     CLayerUIPropPage implementation
5  * COPYRIGHT:   Copyright 2015-2019 Mark Jansen (mark.jansen@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 #include <shlwapi.h>
11 #include <shellapi.h>
12 #include <shellutils.h>
13 #include <strsafe.h>
14 #include <apphelp.h>
15 #include <windowsx.h>
16 #include <sfc.h>
17 
18 const GUID CLSID_CLayerUIPropPage = { 0x513D916F, 0x2A8E, 0x4F51, { 0xAE, 0xAB, 0x0C, 0xBC, 0x76, 0xFB, 0x1A, 0xF8 } };
19 
20 #define GPLK_USER 1
21 #define GPLK_MACHINE 2
22 #define MAX_LAYER_LENGTH 256
23 
24 static struct {
25     const PCWSTR Display;
26     const PCWSTR Name;
27 } g_CompatModes[] = {
28     { L"Windows 95", L"WIN95" },
29     { L"Windows 98/ME", L"WIN98" },
30     { L"Windows NT 4.0 (SP5)", L"NT4SP5" },
31     { L"Windows 2000", L"WIN2000" },
32     { L"Windows XP (SP3)", L"WINXPSP3" },
33     { L"Windows Server 2003 (SP1)", L"WINSRV03SP1" },
34     { L"Windows Server 2008 (SP1)", L"WINSRV08SP1" },
35     { L"Windows Vista (SP2)", L"VISTASP2" },
36     { L"Windows 7", L"WIN7RTM" },
37     { L"Windows 7 (SP1)", L"WIN7SP1" },
38     { L"Windows 8.1", L"WIN81RTM" },
39     { L"Windows 10", L"WIN10RTM" },
40     { L"Windows Server 2016", L"WINSRV16RTM" },
41     { L"Windows Server 2019", L"WINSRV19RTM" },
42     { NULL, NULL }
43 };
44 
45 static struct {
46     const PCWSTR Name;
47     DWORD Id;
48 } g_Layers[] = {
49     { L"256COLOR", IDC_CHKRUNIN256COLORS },
50     { L"640X480", IDC_CHKRUNIN640480RES },
51     { L"DISABLETHEMES", IDC_CHKDISABLEVISUALTHEMES },
52 #if 0
53     { L"DISABLEDWM", IDC_??, TRUE },
54     { L"HIGHDPIAWARE", IDC_??, TRUE },
55     { L"RUNASADMIN", IDC_??, TRUE },
56 #endif
57     { NULL, 0 }
58 };
59 
60 static const WCHAR* g_AllowedExtensions[] = {
61     L".exe",
62     L".msi",
63     L".pif",
64     L".bat",
65     L".cmd",
66     0
67 };
68 
IsBuiltinLayer(PCWSTR Name)69 BOOL IsBuiltinLayer(PCWSTR Name)
70 {
71     size_t n;
72 
73     for (n = 0; g_Layers[n].Name; ++n)
74     {
75         if (!_wcsicmp(g_Layers[n].Name, Name))
76         {
77             return TRUE;
78         }
79     }
80 
81     for (n = 0; g_CompatModes[n].Name; ++n)
82     {
83         if (!_wcsicmp(g_CompatModes[n].Name, Name))
84         {
85             return TRUE;
86         }
87     }
88     return FALSE;
89 }
90 
91 
ACDBG_FN(PCSTR FunctionName,PCWSTR Format,...)92 void ACDBG_FN(PCSTR FunctionName, PCWSTR Format, ...)
93 {
94     WCHAR Buffer[512];
95     WCHAR* Current = Buffer;
96     size_t Length = _countof(Buffer);
97 
98     StringCchPrintfExW(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, L"[%-20S] ", FunctionName);
99     va_list ArgList;
100     va_start(ArgList, Format);
101     StringCchVPrintfExW(Current, Length, &Current, &Length, STRSAFE_NULL_ON_FAILURE, Format, ArgList);
102     va_end(ArgList);
103     OutputDebugStringW(Buffer);
104 }
105 
106 #define ACDBG(fmt, ...)  ACDBG_FN(__FUNCTION__, fmt, ##__VA_ARGS__ )
107 
108 
109 
CLayerUIPropPage()110 CLayerUIPropPage::CLayerUIPropPage()
111 : m_IsSfcProtected(FALSE)
112 , m_AllowPermLayer(FALSE)
113 , m_LayerQueryFlags(GPLK_USER)  /* TODO: When do we read from HKLM? */
114 , m_RegistryOSMode(0)
115 , m_OSMode(0)
116 , m_RegistryEnabledLayers(0)
117 , m_EnabledLayers(0)
118 {
119     CComBSTR title;
120     title.LoadString(g_hModule, IDS_COMPAT_TITLE);
121     m_psp.pszTitle = title.Detach();
122     m_psp.dwFlags |= PSP_USETITLE;
123 }
124 
~CLayerUIPropPage()125 CLayerUIPropPage::~CLayerUIPropPage()
126 {
127     CComBSTR title;
128     title.Attach((BSTR)m_psp.pszTitle);
129 }
130 
InitFile(PCWSTR Filename)131 HRESULT CLayerUIPropPage::InitFile(PCWSTR Filename)
132 {
133     CString ExpandedFilename;
134     DWORD dwRequired = ExpandEnvironmentStringsW(Filename, NULL, 0);
135     if (dwRequired > 0)
136     {
137         LPWSTR Buffer = ExpandedFilename.GetBuffer(dwRequired);
138         DWORD dwReturned = ExpandEnvironmentStringsW(Filename, Buffer, dwRequired);
139         if (dwRequired == dwReturned)
140         {
141             ExpandedFilename.ReleaseBufferSetLength(dwReturned - 1);
142             ACDBG(L"Expanded '%s' => '%s'\r\n", Filename, (PCWSTR)ExpandedFilename);
143         }
144         else
145         {
146             ExpandedFilename.ReleaseBufferSetLength(0);
147             ExpandedFilename = Filename;
148             ACDBG(L"Failed during expansion '%s'\r\n", Filename);
149         }
150     }
151     else
152     {
153         ACDBG(L"Failed to expand '%s'\r\n", Filename);
154         ExpandedFilename = Filename;
155     }
156     PCWSTR pwszExt = PathFindExtensionW(ExpandedFilename);
157     if (!pwszExt)
158     {
159         ACDBG(L"Failed to find an extension: '%s'\r\n", (PCWSTR)ExpandedFilename);
160         return E_FAIL;
161     }
162     if (!_wcsicmp(pwszExt, L".lnk"))
163     {
164         WCHAR Buffer[MAX_PATH];
165         if (!GetExeFromLnk(ExpandedFilename, Buffer, _countof(Buffer)))
166         {
167             ACDBG(L"Failed to read link target from: '%s'\r\n", (PCWSTR)ExpandedFilename);
168             return E_FAIL;
169         }
170         if (!_wcsicmp(Buffer, ExpandedFilename))
171         {
172             ACDBG(L"Link redirects to itself: '%s'\r\n", (PCWSTR)ExpandedFilename);
173             return E_FAIL;
174         }
175         return InitFile(Buffer);
176     }
177 
178     CString tmp;
179     if (tmp.GetEnvironmentVariable(L"SystemRoot"))
180     {
181         tmp += L"\\System32";
182         if (ExpandedFilename.GetLength() >= tmp.GetLength() &&
183             ExpandedFilename.Left(tmp.GetLength()).MakeLower() == tmp.MakeLower())
184         {
185             ACDBG(L"Ignoring System32: %s\r\n", (PCWSTR)ExpandedFilename);
186             return E_FAIL;
187         }
188         tmp.GetEnvironmentVariable(L"SystemRoot");
189         tmp += L"\\WinSxs";
190         if (ExpandedFilename.GetLength() >= tmp.GetLength() &&
191             ExpandedFilename.Left(tmp.GetLength()).MakeLower() == tmp.MakeLower())
192         {
193             ACDBG(L"Ignoring WinSxs: %s\r\n", (PCWSTR)ExpandedFilename);
194             return E_FAIL;
195         }
196     }
197 
198     for (size_t n = 0; g_AllowedExtensions[n]; ++n)
199     {
200         if (!_wcsicmp(g_AllowedExtensions[n], pwszExt))
201         {
202             m_Filename = ExpandedFilename;
203             ACDBG(L"Got: %s\r\n", (PCWSTR)ExpandedFilename);
204             m_IsSfcProtected = SfcIsFileProtected(NULL, m_Filename);
205             m_AllowPermLayer = AllowPermLayer(ExpandedFilename);
206             return S_OK;
207         }
208     }
209     ACDBG(L"Extension not included: '%s'\r\n", pwszExt);
210     return E_FAIL;
211 }
212 
GetLayerInfo(PCWSTR Filename,DWORD QueryFlags,PDWORD OSMode,PDWORD Enabledlayers,CSimpleArray<CString> & customLayers)213 static BOOL GetLayerInfo(PCWSTR Filename, DWORD QueryFlags, PDWORD OSMode, PDWORD Enabledlayers, CSimpleArray<CString>& customLayers)
214 {
215     WCHAR wszLayers[MAX_LAYER_LENGTH] = { 0 };
216     DWORD dwBytes = sizeof(wszLayers);
217 
218     *OSMode = *Enabledlayers = 0;
219     customLayers.RemoveAll();
220     if (!SdbGetPermLayerKeys(Filename, wszLayers, &dwBytes, QueryFlags))
221         return FALSE;
222 
223     for (PWCHAR Layer = wcstok(wszLayers, L" "); Layer; Layer = wcstok(NULL, L" "))
224     {
225         size_t n;
226         for (n = 0; g_Layers[n].Name; ++n)
227         {
228             if (!_wcsicmp(g_Layers[n].Name, Layer))
229             {
230                 *Enabledlayers |= (1<<n);
231                 break;
232             }
233         }
234         /* Did we find it? */
235         if (g_Layers[n].Name)
236             continue;
237 
238         for (n = 0; g_CompatModes[n].Name; ++n)
239         {
240             if (!_wcsicmp(g_CompatModes[n].Name, Layer))
241             {
242                 *OSMode = n+1;
243                 break;
244             }
245         }
246         /* Did we find it? */
247         if (g_CompatModes[n].Name)
248             continue;
249 
250         /* Must be a 'custom' layer */
251         customLayers.Add(Layer);
252     }
253     return TRUE;
254 }
255 
OnSetActive()256 int CLayerUIPropPage::OnSetActive()
257 {
258     if (!GetLayerInfo(m_Filename, m_LayerQueryFlags, &m_RegistryOSMode, &m_RegistryEnabledLayers, m_RegistryCustomLayers))
259         m_RegistryOSMode = m_RegistryEnabledLayers = 0;
260 
261     for (size_t n = 0; g_Layers[n].Name; ++n)
262         CheckDlgButton(g_Layers[n].Id, (m_RegistryEnabledLayers & (1<<n)) ? BST_CHECKED : BST_UNCHECKED);
263 
264     CheckDlgButton(IDC_CHKRUNCOMPATIBILITY, m_RegistryOSMode ? BST_CHECKED : BST_UNCHECKED);
265 
266     if (m_RegistryOSMode)
267         ComboBox_SetCurSel(GetDlgItem(IDC_COMPATIBILITYMODE), m_RegistryOSMode-1);
268 
269     m_CustomLayers = m_RegistryCustomLayers;
270 
271     UpdateControls();
272 
273     return 0;
274 }
275 
276 
ArrayEquals(const CSimpleArray<CString> & lhs,const CSimpleArray<CString> & rhs)277 static BOOL ArrayEquals(const CSimpleArray<CString>& lhs, const CSimpleArray<CString>& rhs)
278 {
279     if (lhs.GetSize() != rhs.GetSize())
280         return FALSE;
281 
282     for (int n = 0; n < lhs.GetSize(); ++n)
283     {
284         if (lhs[n] != rhs[n])
285             return FALSE;
286     }
287     return TRUE;
288 }
289 
HasChanges() const290 BOOL CLayerUIPropPage::HasChanges() const
291 {
292     if (m_RegistryEnabledLayers != m_EnabledLayers)
293         return TRUE;
294 
295     if (m_RegistryOSMode != m_OSMode)
296         return TRUE;
297 
298     if (!ArrayEquals(m_RegistryCustomLayers, m_CustomLayers))
299         return TRUE;
300 
301     return FALSE;
302 }
303 
OnApply()304 int CLayerUIPropPage::OnApply()
305 {
306     if (HasChanges())
307     {
308         BOOL bMachine = m_LayerQueryFlags == GPLK_MACHINE;
309 
310         for (size_t n = 0; g_CompatModes[n].Name; ++n)
311             SetPermLayerState(m_Filename, g_CompatModes[n].Name, 0, bMachine, (n+1) == m_OSMode);
312 
313         for (size_t n = 0; g_Layers[n].Name; ++n)
314         {
315             SetPermLayerState(m_Filename, g_Layers[n].Name, 0, bMachine, ((1<<n) & m_EnabledLayers) != 0);
316         }
317 
318         /* Disable all old values */
319         for (int j = 0; j < m_RegistryCustomLayers.GetSize(); j++)
320         {
321             SetPermLayerState(m_Filename, m_RegistryCustomLayers[j].GetString(), 0, bMachine, FALSE);
322         }
323 
324         /* Enable all new values */
325         for (int j = 0; j < m_CustomLayers.GetSize(); j++)
326         {
327             SetPermLayerState(m_Filename, m_CustomLayers[j].GetString(), 0, bMachine, TRUE);
328         }
329 
330         SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATHW, (PCWSTR)m_Filename, NULL);
331     }
332 
333     return PSNRET_NOERROR;
334 }
335 
OnInitDialog(UINT uMsg,WPARAM wParam,LPARAM lParam,BOOL & bHandled)336 LRESULT CLayerUIPropPage::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled)
337 {
338     HWND cboMode = GetDlgItem(IDC_COMPATIBILITYMODE);
339     for (size_t n = 0; g_CompatModes[n].Display; ++n)
340         ComboBox_AddString(cboMode, g_CompatModes[n].Display);
341     ComboBox_SetCurSel(cboMode, 4);
342 
343     CStringW explanation;
344     if (!m_AllowPermLayer)
345     {
346         explanation.LoadString(g_hModule, IDS_FAILED_NETWORK);
347         DisableControls();
348         ACDBG(L"AllowPermLayer returned FALSE\r\n");
349     }
350     else if (m_IsSfcProtected)
351     {
352         explanation.LoadString(g_hModule, IDS_FAILED_PROTECTED);
353         DisableControls();
354         ACDBG(L"Protected OS file\r\n");
355     }
356     else
357     {
358         return TRUE;
359     }
360     SetDlgItemTextW(IDC_EXPLANATION, explanation);
361     return TRUE;
362 }
363 
DisableControls()364 INT_PTR CLayerUIPropPage::DisableControls()
365 {
366     ::EnableWindow(GetDlgItem(IDC_COMPATIBILITYMODE), 0);
367     ::EnableWindow(GetDlgItem(IDC_CHKRUNCOMPATIBILITY), 0);
368     for (size_t n = 0; g_Layers[n].Name; ++n)
369         ::EnableWindow(GetDlgItem(g_Layers[n].Id), 0);
370     ::EnableWindow(GetDlgItem(IDC_EDITCOMPATIBILITYMODES), 0);
371     return TRUE;
372 }
373 
UpdateControls()374 void CLayerUIPropPage::UpdateControls()
375 {
376     m_OSMode = 0, m_EnabledLayers = 0;
377     BOOL ModeEnabled = IsDlgButtonChecked(IDC_CHKRUNCOMPATIBILITY);
378     if (ModeEnabled)
379         m_OSMode = ComboBox_GetCurSel(GetDlgItem(IDC_COMPATIBILITYMODE))+1;
380     ::EnableWindow(GetDlgItem(IDC_COMPATIBILITYMODE), ModeEnabled);
381 
382     for (size_t n = 0; g_Layers[n].Name; ++n)
383     {
384         m_EnabledLayers |= IsDlgButtonChecked(g_Layers[n].Id) ? (1<<n) : 0;
385         ::ShowWindow(GetDlgItem(g_Layers[n].Id), SW_SHOW);
386     }
387 
388     CStringW customLayers;
389     for (int j = 0; j < m_CustomLayers.GetSize(); ++j)
390     {
391         if (j > 0)
392             customLayers += L", ";
393         customLayers += m_CustomLayers[j];
394     }
395     SetDlgItemTextW(IDC_ENABLED_LAYERS, customLayers);
396 
397     SetModified(HasChanges());
398 }
399 
OnCtrlCommand(WORD wNotifyCode,WORD wID,HWND hWndCtl,BOOL & bHandled)400 LRESULT CLayerUIPropPage::OnCtrlCommand(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
401 {
402     UpdateControls();
403     return 0;
404 }
405 
OnEditModes(WORD wNotifyCode,WORD wID,HWND hWndCtl,BOOL & bHandled)406 LRESULT CLayerUIPropPage::OnEditModes(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled)
407 {
408     if (ShowEditCompatModes(m_hWnd, this))
409         UpdateControls();
410     return 0;
411 }
412 
OnClickNotify(INT uCode,LPNMHDR hdr,BOOL & bHandled)413 LRESULT CLayerUIPropPage::OnClickNotify(INT uCode, LPNMHDR hdr, BOOL& bHandled)
414 {
415     if (hdr->idFrom == IDC_INFOLINK)
416         ShellExecute(NULL, L"open", L"https://reactos.org/forum/viewforum.php?f=4", NULL, NULL, SW_SHOW);
417     return 0;
418 }
419 
DisableShellext()420 static BOOL DisableShellext()
421 {
422     HKEY hkey;
423     LSTATUS ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Policies\\Microsoft\\Windows\\AppCompat", 0, KEY_QUERY_VALUE, &hkey);
424     BOOL Disable = FALSE;
425     if (ret == ERROR_SUCCESS)
426     {
427         DWORD dwValue = 0;
428         DWORD type, size = sizeof(dwValue);
429         ret = RegQueryValueExW(hkey, L"DisableEngine", NULL, &type, (PBYTE)&dwValue, &size);
430         if (ret == ERROR_SUCCESS && type == REG_DWORD)
431         {
432             Disable = !!dwValue;
433         }
434         if (!Disable)
435         {
436             size = sizeof(dwValue);
437             ret = RegQueryValueExW(hkey, L"DisablePropPage", NULL, &type, (PBYTE)&dwValue, &size);
438             if (ret == ERROR_SUCCESS && type == REG_DWORD)
439             {
440                 Disable = !!dwValue;
441             }
442         }
443 
444         RegCloseKey(hkey);
445     }
446     return Disable;
447 }
448 
Initialize(PCIDLIST_ABSOLUTE pidlFolder,LPDATAOBJECT pDataObj,HKEY hkeyProgID)449 STDMETHODIMP CLayerUIPropPage::Initialize(PCIDLIST_ABSOLUTE pidlFolder, LPDATAOBJECT pDataObj, HKEY hkeyProgID)
450 {
451     FORMATETC etc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
452     STGMEDIUM stg;
453 
454     if (DisableShellext())
455         return E_ACCESSDENIED;
456 
457     HRESULT hr = pDataObj->GetData(&etc, &stg);
458     if (FAILED(hr))
459     {
460         ACDBG(L"Failed to retrieve Data from pDataObj.\r\n");
461         return E_INVALIDARG;
462     }
463     hr = E_FAIL;
464     HDROP hdrop = (HDROP)GlobalLock(stg.hGlobal);
465     if (hdrop)
466     {
467         UINT uNumFiles = DragQueryFileW(hdrop, 0xFFFFFFFF, NULL, 0);
468         if (uNumFiles == 1)
469         {
470             WCHAR szFile[MAX_PATH * 2];
471             if (DragQueryFileW(hdrop, 0, szFile, _countof(szFile)))
472             {
473                 this->AddRef();
474                 hr = InitFile(szFile);
475             }
476             else
477             {
478                 ACDBG(L"Failed to query the file.\r\n");
479             }
480         }
481         else
482         {
483             ACDBG(L"Invalid number of files: %d\r\n", uNumFiles);
484         }
485         GlobalUnlock(stg.hGlobal);
486     }
487     else
488     {
489         ACDBG(L"Could not lock stg.hGlobal\r\n");
490     }
491     ReleaseStgMedium(&stg);
492     return hr;
493 }
494