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