winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

IconExtraction.cpp (13479B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "IconDefs.h"
      5 #include "winget/IconExtraction.h"
      6 #include "Microsoft/ARPHelper.h"
      7 #include <AppInstallerSHA256.h>
      8 #include <winget/Filesystem.h>
      9 
     10 namespace AppInstaller::Repository
     11 {
     12     using namespace AppInstaller::Repository::Microsoft;
     13 
     14     namespace
     15     {
     16         // Struct used as data object passed to Enumerate callback function of EnumResourceNamesEx
     17         struct EnumGroupIconProcParameter
     18         {
     19             // Input to specify icon index
     20             int IconIndex = 0;
     21             // The result Resource handle of the group icon
     22             HRSRC ResourceHandle = nullptr;
     23             // How many icons were already found
     24             int IconsFound = 0;
     25         };
     26 
     27         BOOL CALLBACK EnumGroupIconProc(HMODULE hModule, LPCWSTR lpType, LPWSTR lpName, LONG_PTR lParam)
     28         {
     29             EnumGroupIconProcParameter* parameter = reinterpret_cast<EnumGroupIconProcParameter*>(lParam);
     30             bool foundRequestedIcon = false;
     31 
     32             // Find icon by resource name
     33             if (parameter->IconIndex < 0)
     34             {
     35                 if (IS_INTRESOURCE(lpName))
     36                 {
     37                     if (-parameter->IconIndex == LOWORD(lpName))
     38                     {
     39                         // Found icon by MAKEINTRESOURCE name
     40                         foundRequestedIcon = true;
     41                     }
     42                 }
     43                 else if (lpName[0] == TEXT('#'))
     44                 {
     45                     std::wstring resourceIdString = lpName + 1; // skip the #
     46                     try
     47                     {
     48                         auto resourceId = std::stoi(resourceIdString.c_str(), nullptr, 0);
     49                         if (-parameter->IconIndex == resourceId)
     50                         {
     51                             // Found icon by number as string #12
     52                             foundRequestedIcon = true;
     53                         }
     54                     }
     55                     catch (...)
     56                     {
     57                         // Error occurred, stop enumerating
     58                         return FALSE;
     59                     }
     60                 }
     61             }
     62             else if (parameter->IconIndex == parameter->IconsFound)
     63             {
     64                 // Found icon by index
     65                 foundRequestedIcon = TRUE;
     66             }
     67 
     68             if (foundRequestedIcon)
     69             {
     70                 parameter->ResourceHandle = FindResourceExW(hModule, lpType, lpName, 0);
     71                 return FALSE;
     72             }
     73 
     74             // Continue enumerating
     75             parameter->IconsFound++;
     76             return TRUE;
     77         };
     78 
     79         void WriteIconDirHeaderToByteArray(std::vector<BYTE>& data, const ICONDIR& iconDir)
     80         {
     81             data.clear();
     82             BYTE const* toBeWritten = reinterpret_cast<BYTE const*>(&(iconDir.idReserved));
     83             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDir.idReserved));
     84             toBeWritten = reinterpret_cast<BYTE const*>(&(iconDir.idType));
     85             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDir.idType));
     86             toBeWritten = reinterpret_cast<BYTE const*>(&(iconDir.idCount));
     87             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDir.idCount));
     88         }
     89 
     90         void AppendIconDirEntryToByteArray(std::vector<BYTE>& data, const ICONDIRENTRY& iconDirEntry)
     91         {
     92             data.insert(data.end(), iconDirEntry.bWidth);
     93             data.insert(data.end(), iconDirEntry.bHeight);
     94             data.insert(data.end(), iconDirEntry.bColorCount);
     95             data.insert(data.end(), iconDirEntry.bReserved);
     96             BYTE const* toBeWritten = reinterpret_cast<BYTE const*>(&(iconDirEntry.wPlanes));
     97             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDirEntry.wPlanes));
     98             toBeWritten = reinterpret_cast<BYTE const*>(&(iconDirEntry.wBitCount));
     99             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDirEntry.wBitCount));
    100             toBeWritten = reinterpret_cast<BYTE const*>(&(iconDirEntry.dwBytesInRes));
    101             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDirEntry.dwBytesInRes));
    102             toBeWritten = reinterpret_cast<BYTE const*>(&(iconDirEntry.dwImageOffset));
    103             data.insert(data.end(), toBeWritten, toBeWritten + sizeof(iconDirEntry.dwImageOffset));
    104         }
    105     }
    106 
    107     std::vector<BYTE> ExtractIconFromBinaryFile(const std::filesystem::path binaryPath, int iconIndex)
    108     {
    109         try
    110         {
    111             wil::unique_hmodule module;
    112             module.reset(LoadLibraryEx(binaryPath.c_str(), nullptr, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE));
    113             THROW_LAST_ERROR_IF_NULL(module);
    114 
    115             EnumGroupIconProcParameter param;
    116             param.IconIndex = iconIndex;
    117 
    118 #pragma warning( push )
    119 #pragma warning ( disable : 4302 )
    120             // First find the requested group icon
    121             EnumResourceNamesExW(
    122                 module.get(),
    123                 MAKEINTRESOURCE(RT_GROUP_ICON),
    124                 EnumGroupIconProc,
    125                 reinterpret_cast<LONG_PTR>(&param),
    126                 (RESOURCE_ENUM_MUI | RESOURCE_ENUM_LN | RESOURCE_ENUM_VALIDATE),
    127                 0);
    128 #pragma warning( pop )
    129 
    130             if (param.ResourceHandle)
    131             {
    132                 // Load and Lock to get a pointer to a GRPICONDIR
    133                 HGLOBAL groupIconResourceHandle = LoadResource(module.get(), param.ResourceHandle);
    134                 THROW_LAST_ERROR_IF_NULL(groupIconResourceHandle);
    135                 LPGRPICONDIR groupIconDir = reinterpret_cast<LPGRPICONDIR>(LockResource(groupIconResourceHandle));
    136                 THROW_LAST_ERROR_IF_NULL(groupIconDir);
    137 
    138                 // Basic validation
    139                 if (groupIconDir->idReserved != 0 || groupIconDir->idType != 1 || groupIconDir->idCount == 0)
    140                 {
    141                     return {};
    142                 }
    143 
    144                 struct SingleIconImage
    145                 {
    146                     ICONDIRENTRY DirEntry = { 0 };
    147                     // pointer to byte contents with size
    148                     std::pair<const BYTE*, DWORD> Content;
    149                 };
    150 
    151                 // Read all individual icon image contents
    152                 std::vector<SingleIconImage> iconContents;
    153                 // The first image's offset.
    154                 DWORD imageOffset = 6 /* ICONDIR size */ + groupIconDir->idCount * 16 /* each ICONDIRENTRY size */;
    155 
    156                 for (int i = 0; i < groupIconDir->idCount; i++)
    157                 {
    158                     SingleIconImage iconEntry;
    159 
    160                     // Populate ICONDIRENTRY
    161                     iconEntry.DirEntry.bWidth = groupIconDir->idEntries[i].bWidth;
    162                     iconEntry.DirEntry.bHeight = groupIconDir->idEntries[i].bHeight;
    163                     iconEntry.DirEntry.bColorCount = groupIconDir->idEntries[i].bColorCount;
    164                     iconEntry.DirEntry.bReserved = groupIconDir->idEntries[i].bReserved;
    165                     iconEntry.DirEntry.wPlanes = groupIconDir->idEntries[i].wPlanes;
    166                     iconEntry.DirEntry.wBitCount = groupIconDir->idEntries[i].wBitCount;
    167                     iconEntry.DirEntry.dwBytesInRes = groupIconDir->idEntries[i].dwBytesInRes;
    168                     iconEntry.DirEntry.dwImageOffset = imageOffset;
    169 
    170                     // Load individual icon content
    171                     HRSRC iconResourceHandle = FindResourceExW(module.get(), RT_ICON, MAKEINTRESOURCE(groupIconDir->idEntries[i].nID), 0);
    172                     THROW_LAST_ERROR_IF_NULL(iconResourceHandle);
    173                     HGLOBAL iconResourceContentHandle = LoadResource(module.get(), iconResourceHandle);
    174                     THROW_LAST_ERROR_IF_NULL(iconResourceContentHandle);
    175                     iconEntry.Content.second = SizeofResource(module.get(), iconResourceHandle);
    176                     THROW_LAST_ERROR_IF(iconEntry.Content.second == 0);
    177                     iconEntry.Content.first = reinterpret_cast<BYTE*>(LockResource(iconResourceContentHandle));
    178                     THROW_LAST_ERROR_IF_NULL(iconEntry.Content.first);
    179 
    180                     // This will be the next image offset.
    181                     imageOffset += iconEntry.Content.second;
    182 
    183                     iconContents.emplace_back(std::move(iconEntry));
    184                 }
    185 
    186                 // Construct ico file icon dir header
    187                 ICONDIR iconDir;
    188                 iconDir.idReserved = groupIconDir->idReserved;
    189                 iconDir.idType = groupIconDir->idType;
    190                 iconDir.idCount = groupIconDir->idCount;
    191 
    192                 std::vector<BYTE> result;
    193 
    194                 // Write Icon Dir header
    195                 WriteIconDirHeaderToByteArray(result, iconDir);
    196 
    197                 // Write Icon Dir entries
    198                 for (auto const& singleIconEntry : iconContents)
    199                 {
    200                     AppendIconDirEntryToByteArray(result, singleIconEntry.DirEntry);
    201                 }
    202 
    203                 // Write Icon contents
    204                 for (auto const& singleIconEntry : iconContents)
    205                 {
    206                     result.insert(result.end(), singleIconEntry.Content.first, singleIconEntry.Content.first + singleIconEntry.Content.second);
    207                 }
    208 
    209                 return result;
    210             }
    211         }
    212         CATCH_LOG();
    213 
    214         return {};
    215     }
    216 
    217 #ifndef AICLI_DISABLE_TEST_HOOKS
    218     static std::vector<ExtractedIconInfo>* s_ExtractIconFromArpEntry_TestHook_Override = nullptr;
    219 
    220     void TestHook_SetExtractIconFromArpEntryResult_Override(std::vector<ExtractedIconInfo>* result)
    221     {
    222         s_ExtractIconFromArpEntry_TestHook_Override = result;
    223     }
    224 #endif
    225 
    226     std::vector<ExtractedIconInfo> ExtractIconFromArpEntry(const std::string& productCode, Manifest::ScopeEnum scope)
    227     {
    228 #ifndef AICLI_DISABLE_TEST_HOOKS
    229         if (s_ExtractIconFromArpEntry_TestHook_Override)
    230         {
    231             return *s_ExtractIconFromArpEntry_TestHook_Override;
    232         }
    233 #endif
    234 
    235         ARPHelper arpHelper;
    236         Registry::Key arpEntry = arpHelper.FindARPEntry(productCode, scope);
    237 
    238         if (arpEntry)
    239         {
    240             std::wstring iconPathRaw;
    241             if (arpHelper.GetBoolValue(arpEntry, arpHelper.WindowsInstaller))
    242             {
    243                 // For msi, get icon from ProductInfo
    244                 auto productCodeWide = Utility::ConvertToUTF16(productCode);
    245                 DWORD iconPathSize = 0;
    246                 if (ERROR_MORE_DATA == MsiGetProductInfoW(productCodeWide.c_str(), INSTALLPROPERTY_PRODUCTICON, nullptr, &iconPathSize))
    247                 {
    248                     std::wstring iconPathBuffer;
    249                     // The iconPathSize returned in previous call does not count the null terminator.
    250                     iconPathSize++;
    251                     iconPathBuffer.resize(iconPathSize);
    252                     if (ERROR_SUCCESS == MsiGetProductInfoW(productCodeWide.c_str(), INSTALLPROPERTY_PRODUCTICON, iconPathBuffer.data(), &iconPathSize))
    253                     {
    254                         iconPathBuffer.resize(iconPathSize);
    255                         iconPathRaw = iconPathBuffer;
    256                     }
    257                 }
    258             }
    259             else
    260             {
    261                 // For other win32 apps, try DisplayIcon.
    262                 iconPathRaw = Utility::ConvertToUTF16(arpHelper.GetStringValue(arpEntry, arpHelper.DisplayIcon));
    263             }
    264 
    265             if (!iconPathRaw.empty())
    266             {
    267                 PathUnquoteSpacesW(iconPathRaw.data());
    268                 // For paths like C:\test\test.exe,-3
    269                 int iconIndex = 0;
    270                 iconIndex = PathParseIconLocationW(iconPathRaw.data());
    271                 // Above operations will modify the input string with null terminator in the middle.
    272                 iconPathRaw = iconPathRaw.c_str();
    273                 auto iconPath = Filesystem::GetExpandedPath(Utility::ConvertToUTF8(iconPathRaw));
    274 
    275                 if (std::filesystem::exists(iconPath))
    276                 {
    277                     auto extension = iconPath.extension().u8string();
    278                     std::vector<BYTE> iconContent;
    279                     if (Utility::CaseInsensitiveEquals(extension, ".ico"))
    280                     {
    281                         std::ifstream iconFile{ iconPath, std::ios::in | std::ios::binary };
    282                         iconContent = Utility::ReadEntireStreamAsByteArray(iconFile);
    283                     }
    284                     else if (Utility::CaseInsensitiveEquals(extension, ".exe") || Utility::CaseInsensitiveEquals(extension, ".dll"))
    285                     {
    286                         iconContent = ExtractIconFromBinaryFile(iconPath, iconIndex);
    287                     }
    288 
    289                     // Construct ExtractedIconInfo return result
    290                     if (!iconContent.empty())
    291                     {
    292                         ExtractedIconInfo iconInfo;
    293                         iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico;
    294                         iconInfo.IconTheme = Manifest::IconThemeEnum::Default;
    295                         iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom;
    296                         iconInfo.IconSha256 = Utility::SHA256::ComputeHash(iconContent.data(), static_cast<uint32_t>(iconContent.size()));
    297                         iconInfo.IconContent = std::move(iconContent);
    298 
    299                         return { std::move(iconInfo) };
    300                     }
    301                 }
    302             }
    303         }
    304 
    305         return {};
    306     }
    307 }