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