1 /* 2 * PROJECT: NT Object Namespace shell extension 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: System Registry folder class implementation 5 * COPYRIGHT: Copyright 2015-2017 David Quintana <gigaherz@gmail.com> 6 */ 7 8 #include "precomp.h" 9 10 // {1C6D6E08-2332-4A7B-A94D-6432DB2B5AE6} 11 const GUID CLSID_RegistryFolder = { 0x1c6d6e08, 0x2332, 0x4a7b, { 0xa9, 0x4d, 0x64, 0x32, 0xdb, 0x2b, 0x5a, 0xe6 } }; 12 13 // {18A4B504-F6D8-4D8A-8661-6296514C2CF0} 14 //static const GUID GUID_RegistryColumns = { 0x18a4b504, 0xf6d8, 0x4d8a, { 0x86, 0x61, 0x62, 0x96, 0x51, 0x4c, 0x2c, 0xf0 } }; 15 16 enum RegistryColumns 17 { 18 REGISTRY_COLUMN_NAME = 0, 19 REGISTRY_COLUMN_TYPE, 20 REGISTRY_COLUMN_VALUE, 21 REGISTRY_COLUMN_END 22 }; 23 24 // ------------------------------- 25 // CRegistryFolderExtractIcon 26 CRegistryFolderExtractIcon::CRegistryFolderExtractIcon() : 27 m_pcidlFolder(NULL), 28 m_pcidlChild(NULL) 29 { 30 31 } 32 33 CRegistryFolderExtractIcon::~CRegistryFolderExtractIcon() 34 { 35 if (m_pcidlFolder) 36 ILFree((LPITEMIDLIST)m_pcidlFolder); 37 if (m_pcidlChild) 38 ILFree((LPITEMIDLIST)m_pcidlChild); 39 } 40 41 HRESULT CRegistryFolderExtractIcon::Initialize(LPCWSTR ntPath, PCIDLIST_ABSOLUTE parent, UINT cidl, PCUITEMID_CHILD_ARRAY apidl) 42 { 43 m_pcidlFolder = ILClone(parent); 44 if (cidl != 1) 45 return E_INVALIDARG; 46 m_pcidlChild = ILClone(apidl[0]); 47 return S_OK; 48 } 49 50 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::GetIconLocation( 51 UINT uFlags, 52 LPWSTR szIconFile, 53 UINT cchMax, 54 INT *piIndex, 55 UINT *pwFlags) 56 { 57 const RegPidlEntry * entry = (RegPidlEntry *)m_pcidlChild; 58 59 if ((entry->cb < sizeof(RegPidlEntry)) || (entry->magic != REGISTRY_PIDL_MAGIC)) 60 return E_INVALIDARG; 61 62 UINT flags = 0; 63 64 switch (entry->entryType) 65 { 66 case REG_ENTRY_KEY: 67 case REG_ENTRY_ROOT: 68 GetModuleFileNameW(g_hInstance, szIconFile, cchMax); 69 *piIndex = -IDI_REGISTRYKEY; 70 *pwFlags = flags; 71 return S_OK; 72 73 case REG_ENTRY_VALUE: 74 case REG_ENTRY_VALUE_WITH_CONTENT: 75 GetModuleFileNameW(g_hInstance, szIconFile, cchMax); 76 *piIndex = -IDI_REGISTRYVALUE; 77 *pwFlags = flags; 78 return S_OK; 79 80 default: 81 GetModuleFileNameW(g_hInstance, szIconFile, cchMax); 82 *piIndex = -IDI_NTOBJECTITEM; 83 *pwFlags = flags; 84 return S_OK; 85 } 86 } 87 88 HRESULT STDMETHODCALLTYPE CRegistryFolderExtractIcon::Extract( 89 LPCWSTR pszFile, 90 UINT nIconIndex, 91 HICON *phiconLarge, 92 HICON *phiconSmall, 93 UINT nIconSize) 94 { 95 return SHDefExtractIconW(pszFile, nIconIndex, 0, phiconLarge, phiconSmall, nIconSize); 96 } 97 98 // CRegistryFolder 99 100 CRegistryFolder::CRegistryFolder() 101 { 102 } 103 104 CRegistryFolder::~CRegistryFolder() 105 { 106 } 107 108 // IShellFolder 109 HRESULT STDMETHODCALLTYPE CRegistryFolder::EnumObjects( 110 HWND hwndOwner, 111 SHCONTF grfFlags, 112 IEnumIDList **ppenumIDList) 113 { 114 if (m_NtPath[0] == 0 && m_hRoot == NULL) 115 { 116 return GetEnumRegistryRoot(ppenumIDList); 117 } 118 else 119 { 120 return GetEnumRegistryKey(m_NtPath, m_hRoot, ppenumIDList); 121 } 122 } 123 124 HRESULT STDMETHODCALLTYPE CRegistryFolder::InternalBindToObject( 125 PWSTR path, 126 const RegPidlEntry * info, 127 LPITEMIDLIST first, 128 LPCITEMIDLIST rest, 129 LPITEMIDLIST fullPidl, 130 LPBC pbcReserved, 131 IShellFolder** ppsfChild) 132 { 133 if (wcslen(m_NtPath) == 0 && m_hRoot == NULL) 134 { 135 return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, L"", info->rootKey, IID_PPV_ARG(IShellFolder, ppsfChild)); 136 } 137 138 return ShellObjectCreatorInit<CRegistryFolder>(fullPidl, path, m_hRoot, IID_PPV_ARG(IShellFolder, ppsfChild)); 139 } 140 141 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(PCIDLIST_ABSOLUTE pidl) 142 { 143 m_shellPidl = ILClone(pidl); 144 m_hRoot = NULL; 145 146 StringCbCopyW(m_NtPath, sizeof(m_NtPath), L""); 147 return S_OK; 148 } 149 150 HRESULT STDMETHODCALLTYPE CRegistryFolder::Initialize(PCIDLIST_ABSOLUTE pidl, PCWSTR ntPath, HKEY hRoot) 151 { 152 m_shellPidl = ILClone(pidl); 153 m_hRoot = hRoot; 154 155 StringCbCopyW(m_NtPath, sizeof(m_NtPath), ntPath); 156 return S_OK; 157 } 158 159 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDefaultColumnState( 160 UINT iColumn, 161 SHCOLSTATEF *pcsFlags) 162 { 163 switch (iColumn) 164 { 165 case REGISTRY_COLUMN_NAME: 166 *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT; 167 return S_OK; 168 169 case REGISTRY_COLUMN_TYPE: 170 *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT; 171 return S_OK; 172 173 case REGISTRY_COLUMN_VALUE: 174 *pcsFlags = SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT | SHCOLSTATE_SLOW; 175 return S_OK; 176 } 177 178 return E_INVALIDARG; 179 } 180 181 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsEx( 182 LPCITEMIDLIST pidl, 183 const SHCOLUMNID *pscid, 184 VARIANT *pv) 185 { 186 const RegPidlEntry * info; 187 188 TRACE("GetDetailsEx\n"); 189 190 if (pidl) 191 { 192 HRESULT hr = GetInfoFromPidl(pidl, &info); 193 if (FAILED_UNEXPECTEDLY(hr)) 194 return hr; 195 196 static const GUID storage = PSGUID_STORAGE; 197 if (IsEqualGUID(pscid->fmtid, storage)) 198 { 199 if (pscid->pid == PID_STG_NAME) 200 { 201 if (info->entryNameLength > 0) 202 { 203 return MakeVariantString(pv, info->entryName); 204 } 205 return MakeVariantString(pv, L"(Default)"); 206 } 207 else if (pscid->pid == PID_STG_STORAGETYPE) 208 { 209 if (info->entryType == REG_ENTRY_ROOT) 210 { 211 return MakeVariantString(pv, L"Key"); 212 } 213 214 if (info->entryType == REG_ENTRY_KEY) 215 { 216 if (info->contentsLength > 0) 217 { 218 PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR)); 219 220 return MakeVariantString(pv, td); 221 } 222 return MakeVariantString(pv, L"Key"); 223 } 224 225 return MakeVariantString(pv, RegistryTypeNames[info->contentType]); 226 } 227 else if (pscid->pid == PID_STG_CONTENTS) 228 { 229 PCWSTR strValueContents; 230 231 hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents); 232 if (FAILED_UNEXPECTEDLY(hr)) 233 return hr; 234 235 if (hr == S_FALSE) 236 { 237 V_VT(pv) = VT_EMPTY; 238 return S_OK; 239 } 240 241 hr = MakeVariantString(pv, strValueContents); 242 243 CoTaskMemFree((PVOID)strValueContents); 244 245 return hr; 246 247 } 248 } 249 } 250 251 return E_INVALIDARG; 252 } 253 254 HRESULT STDMETHODCALLTYPE CRegistryFolder::GetDetailsOf( 255 LPCITEMIDLIST pidl, 256 UINT iColumn, 257 SHELLDETAILS *psd) 258 { 259 const RegPidlEntry * info; 260 261 TRACE("GetDetailsOf\n"); 262 263 if (pidl) 264 { 265 HRESULT hr = GetInfoFromPidl(pidl, &info); 266 if (FAILED_UNEXPECTEDLY(hr)) 267 return hr; 268 269 switch (iColumn) 270 { 271 case REGISTRY_COLUMN_NAME: 272 { 273 psd->fmt = LVCFMT_LEFT; 274 275 if (info->entryNameLength > 0) 276 { 277 return MakeStrRetFromString(info->entryName, info->entryNameLength, &(psd->str)); 278 } 279 return MakeStrRetFromString(L"(Default)", &(psd->str)); 280 } 281 282 case REGISTRY_COLUMN_TYPE: 283 { 284 psd->fmt = LVCFMT_LEFT; 285 286 if (info->entryType == REG_ENTRY_ROOT) 287 { 288 return MakeStrRetFromString(L"Key", &(psd->str)); 289 } 290 291 if (info->entryType == REG_ENTRY_KEY) 292 { 293 if (info->contentsLength > 0) 294 { 295 PWSTR td = (PWSTR)(((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR)); 296 297 return MakeStrRetFromString(td, info->contentsLength, &(psd->str)); 298 } 299 300 return MakeStrRetFromString(L"Key", &(psd->str)); 301 } 302 303 return MakeStrRetFromString(RegistryTypeNames[info->contentType], &(psd->str)); 304 } 305 306 case REGISTRY_COLUMN_VALUE: 307 { 308 psd->fmt = LVCFMT_LEFT; 309 310 PCWSTR strValueContents; 311 312 hr = FormatContentsForDisplay(info, m_hRoot, m_NtPath, &strValueContents); 313 if (FAILED_UNEXPECTEDLY(hr)) 314 return hr; 315 316 if (hr == S_FALSE) 317 { 318 return MakeStrRetFromString(L"(Empty)", &(psd->str)); 319 } 320 321 hr = MakeStrRetFromString(strValueContents, &(psd->str)); 322 323 CoTaskMemFree((PVOID)strValueContents); 324 325 return hr; 326 } 327 } 328 } 329 else 330 { 331 switch (iColumn) 332 { 333 case REGISTRY_COLUMN_NAME: 334 psd->fmt = LVCFMT_LEFT; 335 psd->cxChar = 30; 336 337 // TODO: Make localizable 338 MakeStrRetFromString(L"Object Name", &(psd->str)); 339 return S_OK; 340 341 case REGISTRY_COLUMN_TYPE: 342 psd->fmt = LVCFMT_LEFT; 343 psd->cxChar = 20; 344 345 // TODO: Make localizable 346 MakeStrRetFromString(L"Content Type", &(psd->str)); 347 return S_OK; 348 349 case REGISTRY_COLUMN_VALUE: 350 psd->fmt = LVCFMT_LEFT; 351 psd->cxChar = 20; 352 353 // TODO: Make localizable 354 MakeStrRetFromString(L"Value", &(psd->str)); 355 return S_OK; 356 } 357 } 358 359 return E_INVALIDARG; 360 } 361 362 HRESULT STDMETHODCALLTYPE CRegistryFolder::MapColumnToSCID( 363 UINT iColumn, 364 SHCOLUMNID *pscid) 365 { 366 static const GUID storage = PSGUID_STORAGE; 367 switch (iColumn) 368 { 369 case REGISTRY_COLUMN_NAME: 370 pscid->fmtid = storage; 371 pscid->pid = PID_STG_NAME; 372 return S_OK; 373 374 case REGISTRY_COLUMN_TYPE: 375 pscid->fmtid = storage; 376 pscid->pid = PID_STG_STORAGETYPE; 377 return S_OK; 378 379 case REGISTRY_COLUMN_VALUE: 380 pscid->fmtid = storage; 381 pscid->pid = PID_STG_CONTENTS; 382 return S_OK; 383 } 384 return E_INVALIDARG; 385 } 386 387 HRESULT CRegistryFolder::CompareIDs(LPARAM lParam, const RegPidlEntry * first, const RegPidlEntry * second) 388 { 389 HRESULT hr; 390 391 DWORD sortMode = lParam & 0xFFFF0000; 392 DWORD column = lParam & 0x0000FFFF; 393 394 if (sortMode == SHCIDS_ALLFIELDS) 395 { 396 if (column != 0) 397 return E_INVALIDARG; 398 399 int minsize = min(first->cb, second->cb); 400 hr = MAKE_COMPARE_HRESULT(memcmp(second, first, minsize)); 401 if (hr != S_EQUAL) 402 return hr; 403 404 return MAKE_COMPARE_HRESULT(second->cb - first->cb); 405 } 406 407 switch (column) 408 { 409 case REGISTRY_COLUMN_NAME: 410 return CompareName(lParam, first, second); 411 412 case REGISTRY_COLUMN_TYPE: 413 { 414 if (first->entryType != second->entryType) 415 return MAKE_COMPARE_HRESULT(second->entryType - first->entryType); 416 417 if (first->entryType == REG_ENTRY_KEY) 418 { 419 if (first->contentsLength == 0 || second->contentsLength == 0) 420 return (first->contentsLength == 0) ? S_GREATERTHAN : S_LESSTHAN; 421 422 PWSTR firstKey = (PWSTR)(((PBYTE)first) + FIELD_OFFSET(RegPidlEntry, entryName) + first->entryNameLength + sizeof(WCHAR)); 423 PWSTR secondKey = (PWSTR)(((PBYTE)second) + FIELD_OFFSET(RegPidlEntry, entryName) + second->entryNameLength + sizeof(WCHAR)); 424 return MAKE_COMPARE_HRESULT(lstrcmpW(firstKey, secondKey)); 425 } 426 427 return CompareName(lParam, first, second); 428 } 429 430 case REGISTRY_COLUMN_VALUE: 431 { 432 PCWSTR firstContent, secondContent; 433 434 if (FAILED_UNEXPECTEDLY(FormatContentsForDisplay(first, m_hRoot, m_NtPath, &firstContent))) 435 return E_INVALIDARG; 436 437 if (FAILED_UNEXPECTEDLY(FormatContentsForDisplay(second, m_hRoot, m_NtPath, &secondContent))) 438 return E_INVALIDARG; 439 440 hr = MAKE_COMPARE_HRESULT(lstrcmpW(firstContent, secondContent)); 441 442 CoTaskMemFree((LPVOID)firstContent); 443 CoTaskMemFree((LPVOID)secondContent); 444 445 return hr; 446 } 447 } 448 449 DbgPrint("Unsupported sorting mode.\n"); 450 return E_INVALIDARG; 451 } 452 453 ULONG CRegistryFolder::ConvertAttributes(const RegPidlEntry * entry, PULONG inMask) 454 { 455 ULONG mask = inMask ? *inMask : 0xFFFFFFFF; 456 ULONG flags = 0; 457 458 if ((entry->entryType == REG_ENTRY_KEY) || 459 (entry->entryType == REG_ENTRY_ROOT)) 460 flags |= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE; 461 462 return flags & mask; 463 } 464 465 BOOL CRegistryFolder::IsFolder(const RegPidlEntry * info) 466 { 467 return (info->entryType == REG_ENTRY_KEY) ||(info->entryType == REG_ENTRY_ROOT); 468 } 469 470 HRESULT CRegistryFolder::GetInfoFromPidl(LPCITEMIDLIST pcidl, const RegPidlEntry ** pentry) 471 { 472 if (!pcidl) 473 { 474 DbgPrint("PCIDL is NULL\n"); 475 return E_INVALIDARG; 476 } 477 478 RegPidlEntry * entry = (RegPidlEntry*) &(pcidl->mkid); 479 if (entry->cb < sizeof(RegPidlEntry)) 480 { 481 DbgPrint("PCIDL too small %l (required %l)\n", entry->cb, sizeof(RegPidlEntry)); 482 return E_INVALIDARG; 483 } 484 485 if (entry->magic != REGISTRY_PIDL_MAGIC) 486 { 487 DbgPrint("PCIDL magic mismatch %04x (expected %04x)\n", entry->magic, REGISTRY_PIDL_MAGIC); 488 return E_INVALIDARG; 489 } 490 491 *pentry = entry; 492 return S_OK; 493 } 494 495 HRESULT CRegistryFolder::FormatValueData(DWORD contentType, PVOID td, DWORD contentsLength, PCWSTR * strContents) 496 { 497 switch (contentType) 498 { 499 case REG_NONE: 500 { 501 PCWSTR strTodo = L""; 502 DWORD bufferLength = (wcslen(strTodo) + 1) * sizeof(WCHAR); 503 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 504 StringCbCopyW(strValue, bufferLength, strTodo); 505 *strContents = strValue; 506 return S_OK; 507 } 508 509 case REG_SZ: 510 case REG_EXPAND_SZ: 511 { 512 PWSTR strValue = (PWSTR)CoTaskMemAlloc(contentsLength + sizeof(WCHAR)); 513 StringCbCopyNW(strValue, contentsLength + sizeof(WCHAR), (LPCWSTR)td, contentsLength); 514 *strContents = strValue; 515 return S_OK; 516 } 517 518 case REG_MULTI_SZ: 519 { 520 PCWSTR separator = L" "; // To match regedit 521 size_t sepChars = wcslen(separator); 522 int strings = 0; 523 int stringChars = 0; 524 525 PCWSTR strData = (PCWSTR)td; 526 while (*strData) 527 { 528 size_t len = wcslen(strData); 529 stringChars += len; 530 strData += len + 1; // Skips null-terminator 531 strings++; 532 } 533 534 int cch = stringChars + (strings - 1) * sepChars + 1; 535 536 PWSTR strValue = (PWSTR)CoTaskMemAlloc(cch * sizeof(WCHAR)); 537 538 strValue[0] = 0; 539 540 strData = (PCWSTR)td; 541 while (*strData) 542 { 543 StrCatW(strValue, strData); 544 strData += wcslen(strData) + 1; 545 if (*strData) 546 StrCatW(strValue, separator); 547 } 548 549 *strContents = strValue; 550 return S_OK; 551 } 552 553 case REG_DWORD: 554 { 555 DWORD bufferLength = 64 * sizeof(WCHAR); 556 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 557 StringCbPrintfW(strValue, bufferLength, L"0x%08x (%d)", 558 *(DWORD*)td, *(DWORD*)td); 559 *strContents = strValue; 560 return S_OK; 561 } 562 563 case REG_QWORD: 564 { 565 DWORD bufferLength = 64 * sizeof(WCHAR); 566 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 567 StringCbPrintfW(strValue, bufferLength, L"0x%016llx (%lld)", 568 *(LARGE_INTEGER*)td, ((LARGE_INTEGER*)td)->QuadPart); 569 *strContents = strValue; 570 return S_OK; 571 } 572 573 case REG_BINARY: 574 { 575 DWORD bufferLength = (contentsLength * 3 + 1) * sizeof(WCHAR); 576 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 577 PWSTR strTemp = strValue; 578 PBYTE data = (PBYTE)td; 579 for (DWORD i = 0; i < contentsLength; i++) 580 { 581 StringCbPrintfW(strTemp, bufferLength, L"%02x ", data[i]); 582 strTemp += 3; 583 bufferLength -= 3; 584 } 585 *strContents = strValue; 586 return S_OK; 587 } 588 589 default: 590 { 591 PCWSTR strFormat = L"<Unimplemented value type %d>"; 592 DWORD bufferLength = (wcslen(strFormat) + 15) * sizeof(WCHAR); 593 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 594 StringCbPrintfW(strValue, bufferLength, strFormat, contentType); 595 *strContents = strValue; 596 return S_OK; 597 } 598 } 599 } 600 601 HRESULT CRegistryFolder::FormatContentsForDisplay(const RegPidlEntry * info, HKEY rootKey, LPCWSTR ntPath, PCWSTR * strContents) 602 { 603 PVOID td = (((PBYTE)info) + FIELD_OFFSET(RegPidlEntry, entryName) + info->entryNameLength + sizeof(WCHAR)); 604 605 if (info->entryType == REG_ENTRY_VALUE_WITH_CONTENT) 606 { 607 if (info->contentsLength > 0) 608 { 609 return FormatValueData(info->contentType, td, info->contentsLength, strContents); 610 } 611 } 612 else if (info->entryType == REG_ENTRY_VALUE) 613 { 614 PVOID valueData; 615 DWORD valueLength; 616 HRESULT hr = ReadRegistryValue(rootKey, ntPath, info->entryName, &valueData, &valueLength); 617 if (FAILED_UNEXPECTEDLY(hr)) 618 { 619 PCWSTR strEmpty = L"(Error reading value)"; 620 DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR); 621 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 622 StringCbCopyW(strValue, bufferLength, strEmpty); 623 *strContents = strValue; 624 return S_OK; 625 } 626 627 if (valueLength > 0) 628 { 629 hr = FormatValueData(info->contentType, valueData, valueLength, strContents); 630 631 CoTaskMemFree(valueData); 632 633 return hr; 634 } 635 } 636 else 637 { 638 PCWSTR strEmpty = L""; 639 DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR); 640 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 641 StringCbCopyW(strValue, bufferLength, strEmpty); 642 *strContents = strValue; 643 return S_OK; 644 } 645 646 PCWSTR strEmpty = L"(Empty)"; 647 DWORD bufferLength = (wcslen(strEmpty) + 1) * sizeof(WCHAR); 648 PWSTR strValue = (PWSTR)CoTaskMemAlloc(bufferLength); 649 StringCbCopyW(strValue, bufferLength, strEmpty); 650 *strContents = strValue; 651 return S_OK; 652 } 653