BsIconUtility.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. #include "BsIconUtility.h"
  2. #include "BsPixelData.h"
  3. #include "BsColor.h"
  4. #include "BsException.h"
  5. #define MSDOS_SIGNATURE 0x5A4D
  6. #define PE_SIGNATURE 0x00004550
  7. #define PE_32BIT_SIGNATURE 0x10B
  8. #define PE_64BIT_SIGNATURE 0x20B
  9. #define PE_NUM_DIRECTORY_ENTRIES 16
  10. #define PE_SECTION_UNINITIALIZED_DATA 0x00000080
  11. #define PE_IMAGE_DIRECTORY_ENTRY_RESOURCE 2
  12. #define PE_IMAGE_RT_ICON 3
  13. namespace BansheeEngine
  14. {
  15. /**
  16. * @brief MS-DOS header found at the beggining in a PE format file.
  17. */
  18. struct MSDOSHeader
  19. {
  20. UINT16 signature;
  21. UINT16 lastSize;
  22. UINT16 numBlocks;
  23. UINT16 numReloc;
  24. UINT16 hdrSize;
  25. UINT16 minAlloc;
  26. UINT16 maxAlloc;
  27. UINT16 ss;
  28. UINT16 sp;
  29. UINT16 checksum;
  30. UINT16 ip;
  31. UINT16 cs;
  32. UINT16 relocPos;
  33. UINT16 numOverlay;
  34. UINT16 reserved1[4];
  35. UINT16 oemId;
  36. UINT16 oemInfo;
  37. UINT16 reserved2[10];
  38. UINT32 lfanew;
  39. };
  40. /**
  41. * @brief COFF header found in a PE format file.
  42. */
  43. struct COFFHeader
  44. {
  45. UINT16 machine;
  46. UINT16 numSections;
  47. UINT32 timeDateStamp;
  48. UINT32 ptrSymbolTable;
  49. UINT32 numSymbols;
  50. UINT16 sizeOptHeader;
  51. UINT16 characteristics;
  52. };
  53. /**
  54. * @brief Contains address and size of data areas in a PE image.
  55. */
  56. struct PEDataDirectory
  57. {
  58. UINT32 virtualAddress;
  59. UINT32 size;
  60. };
  61. /**
  62. * @brief Optional header in a 32-bit PE format file.
  63. */
  64. struct PEOptionalHeader32
  65. {
  66. UINT16 signature;
  67. UINT8 majorLinkerVersion;
  68. UINT8 minorLinkerVersion;
  69. UINT32 sizeCode;
  70. UINT32 sizeInitializedData;
  71. UINT32 sizeUninitializedData;
  72. UINT32 addressEntryPoint;
  73. UINT32 baseCode;
  74. UINT32 baseData;
  75. UINT32 baseImage;
  76. UINT32 alignmentSection;
  77. UINT32 alignmentFile;
  78. UINT16 majorOSVersion;
  79. UINT16 minorOSVersion;
  80. UINT16 majorImageVersion;
  81. UINT16 minorImageVersion;
  82. UINT16 majorSubsystemVersion;
  83. UINT16 minorSubsystemVersion;
  84. UINT32 reserved;
  85. UINT32 sizeImage;
  86. UINT32 sizeHeaders;
  87. UINT32 checksum;
  88. UINT16 subsystem;
  89. UINT16 characteristics;
  90. UINT32 sizeStackReserve;
  91. UINT32 sizeStackCommit;
  92. UINT32 sizeHeapReserve;
  93. UINT32 sizeHeapCommit;
  94. UINT32 loaderFlags;
  95. UINT32 NumRvaAndSizes;
  96. PEDataDirectory dataDirectory[16];
  97. };
  98. /**
  99. * @brief Optional header in a 64-bit PE format file.
  100. */
  101. struct PEOptionalHeader64
  102. {
  103. UINT16 signature;
  104. UINT8 majorLinkerVersion;
  105. UINT8 minorLinkerVersion;
  106. UINT32 sizeCode;
  107. UINT32 sizeInitializedData;
  108. UINT32 sizeUninitializedData;
  109. UINT32 addressEntryPoint;
  110. UINT32 baseCode;
  111. UINT64 baseImage;
  112. UINT32 alignmentSection;
  113. UINT32 alignmentFile;
  114. UINT16 majorOSVersion;
  115. UINT16 minorOSVersion;
  116. UINT16 majorImageVersion;
  117. UINT16 minorImageVersion;
  118. UINT16 majorSubsystemVersion;
  119. UINT16 minorSubsystemVersion;
  120. UINT32 reserved;
  121. UINT32 sizeImage;
  122. UINT32 sizeHeaders;
  123. UINT32 checksum;
  124. UINT16 subsystem;
  125. UINT16 characteristics;
  126. UINT64 sizeStackReserve;
  127. UINT64 sizeStackCommit;
  128. UINT64 sizeHeapReserve;
  129. UINT64 sizeHeapCommit;
  130. UINT32 loaderFlags;
  131. UINT32 NumRvaAndSizes;
  132. PEDataDirectory dataDirectory[16];
  133. };
  134. /**
  135. * @brief A section header in a PE format file.
  136. */
  137. struct PESectionHeader
  138. {
  139. char name[8];
  140. UINT32 virtualSize;
  141. UINT32 relativeVirtualAddress;
  142. UINT32 physicalSize;
  143. UINT32 physicalAddress;
  144. UINT8 deprecated[12];
  145. UINT32 flags;
  146. };
  147. /**
  148. * @brief A resource table header within a .rsrc section in a PE format file.
  149. */
  150. struct PEImageResourceDirectory
  151. {
  152. UINT32 flags;
  153. UINT32 timeDateStamp;
  154. UINT16 majorVersion;
  155. UINT16 minorVersion;
  156. UINT16 numNamedEntries;
  157. UINT16 numIdEntries;
  158. };
  159. /**
  160. * @brief A single entry in a resource table within a .rsrc section in a PE format file.
  161. */
  162. struct PEImageResourceEntry
  163. {
  164. UINT32 type;
  165. UINT32 offsetDirectory : 31;
  166. UINT32 isDirectory : 1;
  167. };
  168. /**
  169. * @brief An entry in a resource table referencing resource data. Found within a
  170. * .rsrc section in a PE format file.
  171. */
  172. struct PEImageResourceEntryData
  173. {
  174. UINT32 offsetData;
  175. UINT32 size;
  176. UINT32 codePage;
  177. UINT32 resourceHandle;
  178. };
  179. /**
  180. * @brief Header used in icon file format.
  181. */
  182. struct IconHeader
  183. {
  184. UINT32 size;
  185. INT32 width;
  186. INT32 height;
  187. UINT16 planes;
  188. UINT16 bitCount;
  189. UINT32 compression;
  190. UINT32 sizeImage;
  191. INT32 xPelsPerMeter;
  192. INT32 yPelsPerMeter;
  193. UINT32 clrUsed;
  194. UINT32 clrImportant;
  195. };
  196. void IconUtility::updateIconExe(const Path& path, const Map<UINT32, PixelDataPtr>& pixelsPerSize)
  197. {
  198. // A PE file is structured as such:
  199. // - MSDOS Header
  200. // - PE Signature
  201. // - COFF Header
  202. // - PE Optional Header
  203. // - One or multiple sections
  204. // - .code
  205. // - .data
  206. // - ...
  207. // - .rsrc
  208. // - icon/cursor/etc data
  209. std::fstream stream;
  210. stream.open(path.toString().c_str(), std::ios::in | std::ios::out | std::ios::binary);
  211. // First check magic number to ensure file is even an executable
  212. UINT16 magicNum;
  213. stream.read((char*)&magicNum, sizeof(magicNum));
  214. if (magicNum != MSDOS_SIGNATURE)
  215. BS_EXCEPT(InvalidStateException, "Provided file is not a valid executable.");
  216. // Read the MSDOS header and skip over it
  217. stream.seekg(0);
  218. MSDOSHeader msdosHeader;
  219. stream.read((char*)&msdosHeader, sizeof(MSDOSHeader));
  220. // Read PE signature
  221. stream.seekg(msdosHeader.lfanew);
  222. UINT32 peSignature;
  223. stream.read((char*)&peSignature, sizeof(peSignature));
  224. if (peSignature != PE_SIGNATURE)
  225. BS_EXCEPT(InvalidStateException, "Provided file is not in PE format.");
  226. // Read COFF header
  227. COFFHeader coffHeader;
  228. stream.read((char*)&coffHeader, sizeof(COFFHeader));
  229. if (coffHeader.sizeOptHeader == 0) // .exe files always have an optional header
  230. BS_EXCEPT(InvalidStateException, "Provided file is not a valid executable.");
  231. UINT32 numSectionHeaders = coffHeader.numSections;
  232. // Read optional header
  233. auto optionalHeaderPos = stream.tellg();
  234. UINT16 optionalHeaderSignature;
  235. stream.read((char*)&optionalHeaderSignature, sizeof(optionalHeaderSignature));
  236. PEDataDirectory* dataDirectory = nullptr;
  237. stream.seekg(optionalHeaderPos);
  238. if (optionalHeaderSignature == PE_32BIT_SIGNATURE)
  239. {
  240. PEOptionalHeader32 optionalHeader;
  241. stream.read((char*)&optionalHeader, sizeof(optionalHeader));
  242. dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
  243. }
  244. else if (optionalHeaderSignature == PE_64BIT_SIGNATURE)
  245. {
  246. PEOptionalHeader64 optionalHeader;
  247. stream.read((char*)&optionalHeader, sizeof(optionalHeader));
  248. dataDirectory = optionalHeader.dataDirectory + PE_IMAGE_DIRECTORY_ENTRY_RESOURCE;
  249. }
  250. else
  251. BS_EXCEPT(InvalidStateException, "Unrecognized PE format.");
  252. // Read section headers
  253. auto sectionHeaderPos = optionalHeaderPos + (std::ifstream::pos_type)coffHeader.sizeOptHeader;
  254. stream.seekg(sectionHeaderPos);
  255. PESectionHeader* sectionHeaders = bs_stack_alloc<PESectionHeader>(numSectionHeaders);
  256. stream.read((char*)sectionHeaders, sizeof(PESectionHeader) * numSectionHeaders);
  257. // Look for .rsrc section header
  258. std::function<void(PEImageResourceDirectory*, PEImageResourceDirectory*, UINT8*, UINT32)> setIconData =
  259. [&](PEImageResourceDirectory* base, PEImageResourceDirectory* current, UINT8* imageData, UINT32 sectionAddress)
  260. {
  261. UINT32 numEntries = current->numIdEntries; // Not supporting name entries
  262. PEImageResourceEntry* entries = (PEImageResourceEntry*)(current + 1);
  263. for (UINT32 i = 0; i < numEntries; i++)
  264. {
  265. // Only at root does the type identify resource type
  266. if (base == current && entries[i].type != PE_IMAGE_RT_ICON)
  267. continue;
  268. if (entries[i].isDirectory)
  269. {
  270. PEImageResourceDirectory* child = (PEImageResourceDirectory*)(((UINT8*)base) + entries[i].offsetDirectory);
  271. setIconData(base, child, imageData, sectionAddress);
  272. }
  273. else
  274. {
  275. PEImageResourceEntryData* data = (PEImageResourceEntryData*)(((UINT8*)base) + entries[i].offsetDirectory);
  276. UINT8* iconData = imageData + (data->offsetData - sectionAddress);
  277. updateIconData(iconData, pixelsPerSize);
  278. }
  279. }
  280. };
  281. for (UINT32 i = 0; i < numSectionHeaders; i++)
  282. {
  283. if (sectionHeaders[i].flags & PE_SECTION_UNINITIALIZED_DATA)
  284. continue;
  285. if (strcmp(sectionHeaders[i].name, ".rsrc") == 0)
  286. {
  287. UINT32 imageSize = sectionHeaders[i].physicalSize;
  288. UINT8* imageData = (UINT8*)bs_stack_alloc(imageSize);
  289. stream.seekg(sectionHeaders[i].physicalAddress);
  290. stream.read((char*)imageData, imageSize);
  291. UINT32 resourceDirOffset = dataDirectory->virtualAddress - sectionHeaders[i].relativeVirtualAddress;
  292. PEImageResourceDirectory* resourceDirectory = (PEImageResourceDirectory*)&imageData[resourceDirOffset];
  293. setIconData(resourceDirectory, resourceDirectory, imageData, sectionHeaders[i].relativeVirtualAddress);
  294. stream.seekp(sectionHeaders[i].physicalAddress);
  295. stream.write((char*)imageData, imageSize);
  296. bs_stack_free(imageData);
  297. }
  298. }
  299. bs_stack_free(sectionHeaders);
  300. stream.close();
  301. }
  302. void IconUtility::updateIconData(UINT8* iconData, const Map<UINT32, PixelDataPtr>& pixelsPerSize)
  303. {
  304. IconHeader* iconHeader = (IconHeader*)iconData;
  305. if (iconHeader->size != sizeof(IconHeader) || iconHeader->compression != 0
  306. || iconHeader->planes != 1 || iconHeader->bitCount != 32)
  307. {
  308. // Unsupported format
  309. return;
  310. }
  311. UINT8* iconPixels = iconData + sizeof(IconHeader);
  312. UINT32 width = iconHeader->width;
  313. UINT32 height = iconHeader->height / 2;
  314. auto iterFind = pixelsPerSize.find(width);
  315. if (iterFind == pixelsPerSize.end() || iterFind->second->getWidth() != width
  316. || iterFind->second->getHeight() != height)
  317. {
  318. // No icon of this size provided
  319. return;
  320. }
  321. // Write colors
  322. PixelDataPtr srcPixels = iterFind->second;
  323. UINT32* colorData = (UINT32*)iconPixels;
  324. UINT32 idx = 0;
  325. for (INT32 y = (INT32)height - 1; y >= 0; y--)
  326. {
  327. for (UINT32 x = 0; x < width; x++)
  328. colorData[idx++] = srcPixels->getColorAt(x, y).getAsBGRA();
  329. }
  330. // Write AND mask
  331. UINT32 colorDataSize = width * height * sizeof(UINT32);
  332. UINT8* maskData = iconPixels + colorDataSize;
  333. UINT32 numPackedPixels = width / 8; // One per bit in byte
  334. for (INT32 y = (INT32)height - 1; y >= 0; y--)
  335. {
  336. UINT8 mask = 0;
  337. for (UINT32 packedX = 0; packedX < numPackedPixels; packedX++)
  338. {
  339. for (UINT32 pixelIdx = 0; pixelIdx < 8; pixelIdx++)
  340. {
  341. UINT32 x = packedX * 8 + pixelIdx;
  342. Color color = srcPixels->getColorAt(x, y);
  343. if (color.a < 0.25f)
  344. mask |= 1 << (7 - pixelIdx);
  345. }
  346. *maskData = mask;
  347. maskData++;
  348. }
  349. }
  350. }
  351. }