Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python : Extract icon from an .exe file

Tags:

python

icons

exe

I am making a Launcher, I want to get the icon of the exe file to show it in my programs. But I don't know how to do that. I have been searching for several days for a solution.

I tried icoextract : https://github.com/jlu5/icoextract but i don't understand how it works.

like image 401
Boubou Avatar asked Sep 01 '26 06:09

Boubou


1 Answers

This is a bit complex so I can't give you an example of a direction to help you figure this out and leave you to write your own code, so...


This will extract the first icon from an exe, and dll file if it contains one and give you the necessary hIcon for RegisterClassW and WNDCLASSW.

import ctypes
from collections.abc import Callable
from ctypes import wintypes
from typing import Any

# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
LOAD_LIBRARY_AS_DATAFILE = 0x00000002

# https://learn.microsoft.com/en-us/windows/win32/menurc/resource-types
RT_ICON = 3
RT_GROUP_ICON = RT_ICON + 11

# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconfromresourceex
LR_DEFAULTCOLOR = 0x00000000
LR_DEFAULTSIZE = 0x00000040

kernel32 = ctypes.WinDLL('kernel32', use_last_error = True)
user32 = ctypes.WinDLL('user32', use_last_error = True)

# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nc-libloaderapi-enumresnameprocw
# ENUMRESNAMEPROCW Enumresnameprocw;
#
# BOOL Enumresnameprocw(
#   [in, optional] HMODULE hModule,
#                  LPCWSTR lpType,
#                  LPWSTR lpName,
#   [in]           LONG_PTR lParam
# )
# {...}
ENUMRESNAMEPROC = ctypes.WINFUNCTYPE(
    wintypes.BOOL,
    wintypes.HMODULE,
    wintypes.LPVOID,
    wintypes.LPVOID,
    wintypes.LPARAM,
    use_last_error = True
)


def enum_res_name_proc(func: Callable[[int, int, int, int], bool]) -> ENUMRESNAMEPROC:
    return ENUMRESNAMEPROC(func)


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-enumresourcenamesw
# BOOL EnumResourceNamesW(
#   [in, optional] HMODULE          hModule,
#   [in]           LPCWSTR          lpType,
#   [in]           ENUMRESNAMEPROCW lpEnumFunc,
#   [in]           LONG_PTR         lParam
# );
EnumResourceNames = kernel32.EnumResourceNamesW
EnumResourceNames.argtypes = [wintypes.HMODULE, wintypes.LPVOID, ENUMRESNAMEPROC, wintypes.LPARAM]
EnumResourceNames.restype = wintypes.BOOL


def enum_resource_names(hModule: int, lpType: str, lpEnumFunc: Any, lParam: int) -> bool:
    return EnumResourceNames(hModule, lpType, lpEnumFunc, lParam)


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-findresourcew
# HRSRC FindResourceW(
#   [in, optional] HMODULE hModule,
#   [in]           LPCWSTR lpName,
#   [in]           LPCWSTR lpType
# );
FindResource = kernel32.FindResourceW
FindResource.argtypes = [wintypes.HMODULE, wintypes.LPVOID, wintypes.LPVOID]
FindResource.restype = wintypes.HRSRC


def find_resource(hModule: int, lpName: int | str, lpType: int | str) -> int:
    def prep_resource_arg(val):
        if isinstance(val, str):
            return ctypes.cast(ctypes.c_wchar_p(val), wintypes.LPVOID)

        elif isinstance(val, int):
            return wintypes.LPVOID(val)

        else:
            raise TypeError("Resource identifiers must be an int or str")

    h_res_info = FindResource(
        hModule,
        prep_resource_arg(lpName),
        prep_resource_arg(lpType)
    )

    if h_res_info is None or h_res_info == 0:
        raise ctypes.WinError(ctypes.get_last_error())

    return h_res_info


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-freelibrary
# BOOL FreeLibrary(
#   [in] HMODULE hLibModule
# );
FreeLibrary = kernel32.FreeLibrary
FreeLibrary.argtypes = [wintypes.HMODULE]
FreeLibrary.restype = wintypes.BOOL


def free_library(hLibModule: int) -> bool:
    return FreeLibrary(hLibModule)


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw
# HMODULE LoadLibraryExW(
#   [in] LPCWSTR lpLibFileName,
#        HANDLE  hFile,
#   [in] DWORD   dwFlags
# );
LoadLibraryEx = kernel32.LoadLibraryExW
LoadLibraryEx.argtypes = [wintypes.LPCWSTR, wintypes.HANDLE, wintypes.DWORD]
LoadLibraryEx.restype = wintypes.HMODULE


def load_library(lpLibFileName: str, hFile: int, dwFlags: int) -> int:
    h_module = LoadLibraryEx(lpLibFileName, hFile, dwFlags)
    if h_module is None: raise ctypes.WinError(ctypes.get_last_error())

    return h_module


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadresource
# HGLOBAL LoadResource(
#   [in, optional] HMODULE hModule,
#   [in]           HRSRC   hResInfo
# );
LoadResource = kernel32.LoadResource
LoadResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC]
LoadResource.restype = wintypes.HGLOBAL


def load_resource(hModule: int, hResInfo: int) -> int:
    h_res_data = LoadResource(hModule, hResInfo)
    if h_res_data is None: raise ctypes.WinError(ctypes.get_last_error())

    return h_res_data


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-lockresource
# LPVOID LockResource(
#   [in] HGLOBAL hResData
# );
LockResource = kernel32.LockResource
LockResource.argtypes = [wintypes.HGLOBAL]
LockResource.restype = ctypes.c_void_p


def lock_resource(hResData: int) -> int:
    p_res_data = LockResource(hResData)
    if p_res_data is None: raise ctypes.WinError(ctypes.get_last_error())

    return p_res_data


# https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-sizeofresource
# DWORD SizeofResource(
#   [in, optional] HMODULE hModule,
#   [in]           HRSRC   hResInfo
# );
SizeofResource = kernel32.SizeofResource
SizeofResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC]
SizeofResource.restype = wintypes.DWORD


def size_of_resource(hModule: int, hResInfo: Any) -> int:
    size = SizeofResource(hModule, hResInfo)
    if size == 0: raise ctypes.WinError(ctypes.get_last_error())

    return size


# https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createiconfromresourceex
# HICON CreateIconFromResourceEx(
#   [in] PBYTE presbits,
#   [in] DWORD dwResSize,
#   [in] BOOL  fIcon,
#   [in] DWORD dwVer,
#   [in] int   cxDesired,
#   [in] int   cyDesired,
#   [in] UINT  Flags
# );
CreateIconFromResourceEx = user32.CreateIconFromResourceEx
CreateIconFromResourceEx.argtypes = [
    wintypes.LPVOID,
    wintypes.DWORD,
    wintypes.BOOL,
    wintypes.DWORD,
    ctypes.c_int,
    ctypes.c_int,
    wintypes.UINT
]
CreateIconFromResourceEx.restype = wintypes.HICON


def create_icon_from_resource(
    presbits: bytes,
    dwResSize: int,
    fIcon: bool,
    cxDesired: int,
    cyDesired: int,
    Flags: int
) -> int | None:
    hIcon = CreateIconFromResourceEx(
        presbits,
        dwResSize,
        fIcon,
        0x00030000,  # dwVersion (always 3.0)
        cxDesired,
        cyDesired,
        Flags
    )

    if hIcon is None:
        raise ctypes.WinError(ctypes.get_last_error())

    return hIcon


class IconExtractor:

    @classmethod
    def extract_from_library(cls, filepath: str, target_size: int = 16) -> int:
        h_module = 0
        try:
            h_module = load_library(filepath, 0, LOAD_LIBRARY_AS_DATAFILE)

            target_group_id = None

            @enum_res_name_proc
            def enumerator_callback(_hModule, _lpszType, lpszName, _lParam):
                nonlocal target_group_id
                target_group_id = lpszName
                return False

            enum_resource_names(h_module, RT_GROUP_ICON, enumerator_callback, 0)

            if target_group_id is None:
                raise ValueError("No icon groups found in the executable.")

            dir_data = cls._get_data_from_resource(h_module, RT_GROUP_ICON, target_group_id)
            count = int.from_bytes(dir_data[4:6], byteorder = 'little')

            best_id = 0
            min_diff = float('inf')

            for i in range(count):
                offset = 6 + (14 * i)
                width = dir_data[offset]
                width = 256 if width == 0 else width

                nID = int.from_bytes(dir_data[offset + 12: offset + 14], byteorder = 'little')

                # Calculate how close this icon is to our target (16x16 or 32x32)
                diff = abs(target_size - width)
                if diff < min_diff:
                    min_diff = diff
                    best_id = nID

            raw_icon_bytes = cls._get_data_from_resource(h_module, RT_ICON, best_id)
            hIcon = cls._create_hicon_from_bytearray(raw_icon_bytes, target_size, target_size)

            return hIcon

        finally:
            if h_module:
                free_library(h_module)

    @staticmethod
    def _create_hicon_from_bytearray(icon_data: bytearray, width: int = 0, height: int = 0) -> int:
        raw_bytes = bytes(icon_data)
        buffer = ctypes.create_string_buffer(raw_bytes)

        flags = LR_DEFAULTCOLOR
        if width == 0 and height == 0:
            flags = LR_DEFAULTSIZE

        hIcon = create_icon_from_resource(
            buffer,
            len(raw_bytes),
            True,  # True means we want an Icon, not a Cursor
            width,
            height,
            flags
        )

        if hIcon == 0 or hIcon is None:
            raise ctypes.WinError(ctypes.get_last_error())

        return hIcon

    @staticmethod
    def _get_data_from_resource(h_module, res_type, name) -> bytearray:
        h_res_info = find_resource(h_module, name, res_type)
        h_res_data = load_resource(h_module, h_res_info)
        p_res_data = lock_resource(h_res_data)
        size = size_of_resource(h_module, h_res_info)
        raw_bytes = ctypes.string_at(p_res_data, size)
        return bytearray(raw_bytes)

Usage...

hicon = IconExtractor.extract_from_library(
    filepath = r'C:\path\to\executable file.exe'
)
like image 112
phpjunkie Avatar answered Sep 03 '26 19:09

phpjunkie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!