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