[SHELL] IShellExtInit::Initialize uses PCIDLIST_ABSOLUTE. CORE-16385
[reactos.git] / dll / shellext / acppage / CLayerUIPropPage.cpp
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(PCIDLIST_ABSOLUTE 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 }