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 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 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 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 125 CLayerUIPropPage::~CLayerUIPropPage() 126 { 127 CComBSTR title; 128 title.Attach((BSTR)m_psp.pszTitle); 129 } 130 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 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 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 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 290 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 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 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 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 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 400 LRESULT CLayerUIPropPage::OnCtrlCommand(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL &bHandled) 401 { 402 UpdateControls(); 403 return 0; 404 } 405 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 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 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 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