Created
July 3, 2026 12:01
-
-
Save DJm00n/dc443ccde88c6bcc78050a1bfd0a8856 to your computer and use it in GitHub Desktop.
Test CreateIconFromResource/LookupIconIdFromDirectory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // full_align_test.cpp | |
| // | |
| // Сравнивает поведение старых (non-Ex) и новых (Ex) версий функций работы | |
| // с иконками на предмет чувствительности к выравниванию входного буфера: | |
| // - CreateIconFromResource vs CreateIconFromResourceEx | |
| // - LookupIconIdFromDirectory vs LookupIconIdFromDirectoryEx | |
| // | |
| // Гипотеза: возможно, именно старые (non-Ex) версии реально требуют | |
| // DWORD-aligned буфер, а Ex-версии — просто унаследовали формулировку | |
| // в документации по инерции, хотя внутри используют более терпимый код. | |
| // | |
| // Сборка (MSVC, x64 Native Tools Command Prompt): | |
| // cl /EHsc /std:c++17 full_align_test.cpp user32.lib | |
| // | |
| // Запуск: | |
| // full_align_test.exe path\to\icon.ico | |
| #include <windows.h> | |
| #include <cstdio> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <vector> | |
| #pragma pack(push, 1) | |
| struct IconDirHeader | |
| { | |
| WORD reserved; | |
| WORD type; | |
| WORD count; | |
| }; | |
| struct IconDirEntry | |
| { | |
| BYTE width; | |
| BYTE height; | |
| BYTE colorCount; | |
| BYTE reserved; | |
| WORD planes; | |
| WORD bitCount; | |
| DWORD bytesInRes; | |
| DWORD imageOffset; | |
| }; | |
| struct GrpIconDirEntry | |
| { | |
| BYTE width; | |
| BYTE height; | |
| BYTE colorCount; | |
| BYTE reserved; | |
| WORD planes; | |
| WORD bitCount; | |
| DWORD bytesInRes; | |
| WORD id; | |
| }; | |
| #pragma pack(pop) | |
| namespace | |
| { | |
| std::vector<BYTE> ReadWholeFile(const wchar_t* fileName) | |
| { | |
| HANDLE file = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, | |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); | |
| if (file == INVALID_HANDLE_VALUE) | |
| { | |
| printf("CreateFileW(%ls) failed, GetLastError=%lu\n", fileName, GetLastError()); | |
| exit(1); | |
| } | |
| LARGE_INTEGER size{}; | |
| if (!GetFileSizeEx(file, &size) || size.HighPart != 0) | |
| { | |
| printf("GetFileSizeEx failed or file too large\n"); | |
| CloseHandle(file); | |
| exit(1); | |
| } | |
| std::vector<BYTE> bits(size.LowPart); | |
| DWORD actual = 0; | |
| BOOL ok = ReadFile(file, bits.data(), size.LowPart, &actual, nullptr); | |
| CloseHandle(file); | |
| if (!ok || actual != size.LowPart) | |
| { | |
| printf("ReadFile failed, GetLastError=%lu\n", GetLastError()); | |
| exit(1); | |
| } | |
| return bits; | |
| } | |
| bool IsPngSignature(const BYTE* p, size_t len) | |
| { | |
| static const BYTE sig[8] = { 0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A }; | |
| return len >= 8 && memcmp(p, sig, 8) == 0; | |
| } | |
| struct IcoData | |
| { | |
| IconDirHeader header; | |
| std::vector<IconDirEntry> entries; | |
| std::vector<BYTE> file; | |
| }; | |
| IcoData ParseIco(const std::vector<BYTE>& file) | |
| { | |
| IcoData result; | |
| result.file = file; | |
| if (file.size() < sizeof(IconDirHeader)) | |
| { | |
| printf("File too small to be an .ico\n"); | |
| exit(1); | |
| } | |
| memcpy(&result.header, file.data(), sizeof(result.header)); | |
| if (result.header.reserved != 0 || result.header.type != 1) | |
| { | |
| printf("Not a valid ICO file (bad ICONDIR header)\n"); | |
| exit(1); | |
| } | |
| printf("ICO contains %u image(s)\n", result.header.count); | |
| size_t entriesOffset = sizeof(IconDirHeader); | |
| for (WORD i = 0; i < result.header.count; ++i) | |
| { | |
| size_t off = entriesOffset + i * sizeof(IconDirEntry); | |
| if (off + sizeof(IconDirEntry) > file.size()) | |
| break; | |
| IconDirEntry entry; | |
| memcpy(&entry, file.data() + off, sizeof(entry)); | |
| result.entries.push_back(entry); | |
| bool isPng = entry.imageOffset + entry.bytesInRes <= file.size() && | |
| IsPngSignature(file.data() + entry.imageOffset, entry.bytesInRes); | |
| printf(" [%u] %ux%u, %u bpp, %lu bytes -> %s\n", | |
| i, entry.width, entry.height, entry.bitCount, | |
| entry.bytesInRes, isPng ? "PNG-based" : "BMP-based"); | |
| } | |
| return result; | |
| } | |
| std::vector<BYTE> ExtractFirstBmpEntry(const IcoData& ico) | |
| { | |
| for (auto& e : ico.entries) | |
| { | |
| if (e.imageOffset + e.bytesInRes > ico.file.size()) | |
| continue; | |
| const BYTE* p = ico.file.data() + e.imageOffset; | |
| if (!IsPngSignature(p, e.bytesInRes)) | |
| return std::vector<BYTE>(p, p + e.bytesInRes); | |
| } | |
| return {}; | |
| } | |
| std::vector<BYTE> BuildGroupIconDirectory(const IcoData& ico) | |
| { | |
| std::vector<BYTE> result(sizeof(IconDirHeader) + | |
| ico.header.count * sizeof(GrpIconDirEntry)); | |
| memcpy(result.data(), &ico.header, sizeof(ico.header)); | |
| for (size_t i = 0; i < ico.entries.size(); ++i) | |
| { | |
| const IconDirEntry& src = ico.entries[i]; | |
| GrpIconDirEntry dst{}; | |
| dst.width = src.width; | |
| dst.height = src.height; | |
| dst.colorCount = src.colorCount; | |
| dst.reserved = src.reserved; | |
| dst.planes = src.planes; | |
| dst.bitCount = src.bitCount; | |
| dst.bytesInRes = src.bytesInRes; | |
| dst.id = static_cast<WORD>(i + 1); | |
| size_t off = sizeof(IconDirHeader) + i * sizeof(GrpIconDirEntry); | |
| memcpy(result.data() + off, &dst, sizeof(dst)); | |
| } | |
| return result; | |
| } | |
| // ---- generic sweep runner ---- | |
| // testFn: принимает (BYTE* p, size_t len) и возвращает pair<bool ok, int info> | |
| template <typename TestFn> | |
| void RunSweep(const char* label, const std::vector<BYTE>& data, TestFn testFn) | |
| { | |
| printf("\n=== %s (%zu bytes) ===\n", label, data.size()); | |
| int successCount = 0, failCount = 0; | |
| for (size_t offset = 0; offset < 16; ++offset) | |
| { | |
| std::vector<BYTE> scratch(data.size() + 16, 0xCC); | |
| BYTE* p = scratch.data() + offset; | |
| memcpy(p, data.data(), data.size()); | |
| uintptr_t addr = reinterpret_cast<uintptr_t>(p); | |
| SetLastError(0); | |
| auto [ok, info] = testFn(p, data.size()); | |
| DWORD err = GetLastError(); | |
| printf(" offset=%2zu addr=0x%p %%4=%zu %%8=%zu -> %-7s info=%-6d GetLastError=%lu\n", | |
| offset, p, addr % 4, addr % 8, | |
| ok ? "OK" : "FAIL", info, err); | |
| ok ? ++successCount : ++failCount; | |
| } | |
| printf("--- %s summary: %d succeeded, %d failed (out of 16 offsets) ---\n", | |
| label, successCount, failCount); | |
| } | |
| } | |
| int wmain(int argc, wchar_t* argv[]) | |
| { | |
| if (argc < 2) | |
| { | |
| printf("Usage: full_align_test.exe <path-to-ico>\n"); | |
| return 1; | |
| } | |
| std::vector<BYTE> file = ReadWholeFile(argv[1]); | |
| printf("Loaded %zu bytes from %ls\n", file.size(), argv[1]); | |
| IcoData ico = ParseIco(file); | |
| std::vector<BYTE> bmpEntry = ExtractFirstBmpEntry(ico); | |
| std::vector<BYTE> groupDir = BuildGroupIconDirectory(ico); | |
| if (bmpEntry.empty()) | |
| { | |
| printf("\nNo legacy BMP-based RT_ICON entry found in this .ico — " | |
| "CreateIconFromResource(Ex) tests skipped.\n"); | |
| } | |
| else | |
| { | |
| // --- CreateIconFromResource (non-Ex, старая версия) --- | |
| RunSweep("CreateIconFromResource (legacy, non-Ex)", bmpEntry, | |
| [](BYTE* p, size_t len) -> std::pair<bool, int> | |
| { | |
| HICON icon = CreateIconFromResource( | |
| p, static_cast<DWORD>(len), /*fIcon*/ TRUE, 0x00030000); | |
| bool ok = icon != nullptr; | |
| if (icon) DestroyIcon(icon); | |
| return { ok, ok ? 1 : 0 }; | |
| }); | |
| // --- CreateIconFromResourceEx (Ex версия) --- | |
| RunSweep("CreateIconFromResourceEx", bmpEntry, | |
| [](BYTE* p, size_t len) -> std::pair<bool, int> | |
| { | |
| HICON icon = CreateIconFromResourceEx( | |
| p, static_cast<DWORD>(len), /*fIcon*/ TRUE, 0x00030000, | |
| 0, 0, LR_DEFAULTCOLOR); | |
| bool ok = icon != nullptr; | |
| if (icon) DestroyIcon(icon); | |
| return { ok, ok ? 1 : 0 }; | |
| }); | |
| } | |
| // --- LookupIconIdFromDirectory (non-Ex, старая версия) --- | |
| RunSweep("LookupIconIdFromDirectory (legacy, non-Ex)", groupDir, | |
| [](BYTE* p, size_t /*len*/) -> std::pair<bool, int> | |
| { | |
| int id = LookupIconIdFromDirectory(p, /*fIcon*/ TRUE); | |
| return { id != 0, id }; | |
| }); | |
| // --- LookupIconIdFromDirectoryEx (Ex версия) --- | |
| RunSweep("LookupIconIdFromDirectoryEx", groupDir, | |
| [](BYTE* p, size_t /*len*/) -> std::pair<bool, int> | |
| { | |
| int id = LookupIconIdFromDirectoryEx(p, /*fIcon*/ TRUE, 0, 0, LR_DEFAULTCOLOR); | |
| return { id != 0, id }; | |
| }); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment