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>(¶m), 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 }