Skip to content

Instantly share code, notes, and snippets.

@DJm00n
Created July 3, 2026 12:01
Show Gist options
  • Select an option

  • Save DJm00n/dc443ccde88c6bcc78050a1bfd0a8856 to your computer and use it in GitHub Desktop.

Select an option

Save DJm00n/dc443ccde88c6bcc78050a1bfd0a8856 to your computer and use it in GitHub Desktop.
Test CreateIconFromResource/LookupIconIdFromDirectory
// 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