"""
替换 exe 程序图标（保留 PyInstaller overlay 数据）
用法: python change_icon.py
"""
import struct
import io
import shutil
import os

EXE_PATH = r"e:\reverseApp\金罗盘_全自动PDD采集解析_V2.7\金罗盘_全自动PDD采集解析_V2.7_patched.exe"
JPG_PATH = r"e:\reverseApp\金罗盘_全自动PDD采集解析_V2.7\imag.jpg"
ICO_PATH = r"e:\reverseApp\金罗盘_全自动PDD采集解析_V2.7\temp_icon.ico"


def jpg_to_ico(jpg_path, ico_path, sizes=[(256,256),(128,128),(64,64),(48,48),(32,32),(16,16)]):
    from PIL import Image
    img = Image.open(jpg_path).convert("RGBA")
    icons = []
    for w, h in sizes:
        resized = img.resize((w, h), Image.LANCZOS)
        buf = io.BytesIO()
        resized.save(buf, format='PNG')
        png_data = buf.getvalue()
        icons.append((w, h, png_data))

    with open(ico_path, 'wb') as f:
        f.write(struct.pack('<HHH', 0, 1, len(icons)))
        offset = 6 + len(icons) * 16
        for w, h, data in icons:
            f.write(struct.pack('<BBBBHHII',
                w if w < 256 else 0, h if h < 256 else 0,
                0, 0, 1, 32, len(data), offset))
            offset += len(data)
        for _, _, data in icons:
            f.write(data)
    print(f'[+] ICO 已生成: {ico_path}')
    return ico_path


def replace_icon_preserve_overlay(exe_path, ico_path):
    """替换 PE 图标资源，保留 overlay 数据"""
    import ctypes
    from ctypes import wintypes

    # 读取原始文件
    with open(exe_path, 'rb') as f:
        full_data = bytearray(f.read())

    # 解析 PE 找到 overlay 起始位置
    if full_data[:2] != b'MZ':
        print('[!] 不是有效的 PE 文件')
        return False

    pe_offset = struct.unpack_from('<I', full_data, 0x3C)[0]
    if full_data[pe_offset:pe_offset+4] != b'PE\x00\x00':
        print('[!] PE 签名无效')
        return False

    # 读取 PE 头
    # IMAGE_FILE_HEADER
    num_sections = struct.unpack_from('<H', full_data, pe_offset + 6)[0]
    # SizeOfOptionalHeader
    opt_header_size = struct.unpack_from('<H', full_data, pe_offset + 20)[0]
    
    # IMAGE_OPTIONAL_HEADER64
    opt_header_start = pe_offset + 24
    # SizeOfImage
    size_of_image = struct.unpack_from('<I', full_data, opt_header_start + 56)[0]

    # 找最后一个节的结束位置作为 overlay 起始
    section_table_start = opt_header_start + opt_header_size
    last_section_end = 0
    for i in range(num_sections):
        sec_offset = section_table_start + i * 40
        virtual_size = struct.unpack_from('<I', full_data, sec_offset + 8)[0]
        virtual_addr = struct.unpack_from('<I', full_data, sec_offset + 12)[0]
        raw_size = struct.unpack_from('<I', full_data, sec_offset + 16)[0]
        raw_offset = struct.unpack_from('<I', full_data, sec_offset + 20)[0]
        sec_end = raw_offset + max(raw_size, virtual_size)
        if sec_end > last_section_end:
            last_section_end = sec_end

    overlay_data = bytes(full_data[last_section_end:])
    pe_data = bytes(full_data[:last_section_end])
    
    print(f'[+] PE 大小: {last_section_end} bytes, Overlay: {len(overlay_data)} bytes')

    # 写入临时 PE 文件
    tmp_pe = exe_path + '.tmp_pe'
    with open(tmp_pe, 'wb') as f:
        f.write(pe_data)

    # 使用 UpdateResource API 修改图标
    kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
    kernel32.BeginUpdateResourceW.argtypes = [wintypes.LPCWSTR, wintypes.BOOL]
    kernel32.BeginUpdateResourceW.restype = wintypes.HANDLE
    kernel32.UpdateResourceW.argtypes = [wintypes.HANDLE, ctypes.c_void_p, ctypes.c_void_p,
                                          wintypes.WORD, wintypes.LPVOID, wintypes.DWORD]
    kernel32.UpdateResourceW.restype = wintypes.BOOL
    kernel32.EndUpdateResourceW.argtypes = [wintypes.HANDLE, wintypes.BOOL]
    kernel32.EndUpdateResourceW.restype = wintypes.BOOL

    handle = kernel32.BeginUpdateResourceW(tmp_pe, True)
    if not handle:
        print(f'[!] BeginUpdateResource 失败: {ctypes.get_last_error()}')
        os.remove(tmp_pe)
        return False

    # 读取新 ICO
    with open(ico_path, 'rb') as f:
        ico_data = f.read()

    num_icons = struct.unpack_from('<HHH', ico_data, 0)[2]
    RT_ICON = ctypes.c_void_p(3)
    RT_GROUP_ICON = ctypes.c_void_p(14)

    # 解析 ICO 条目
    dir_entries = []
    for i in range(num_icons):
        offset = 6 + i * 16
        dir_entries.append(struct.unpack_from('<BBBBHHII', ico_data, offset))

    # 构建 RT_GROUP_ICON 数据
    # GRPICONDIRENTRY: bWidth(B), bHeight(B), bColorCount(B), bReserved(B),
    #                   wPlanes(H), wBitCount(H), dwBytesInRes(I), nID(H) = 14 bytes
    group_data = struct.pack('<HHH', 0, 1, num_icons)
    for i, entry in enumerate(dir_entries):
        w, h, colors, reserved, plane, bpp, size, _ = entry
        group_data += struct.pack('<BBBBHHIH',
            w if w != 0 else 0, h if h != 0 else 0,
            colors, 0, plane, bpp, size, i + 1)

    # 写入图标
    for i, entry in enumerate(dir_entries):
        _, _, _, _, _, _, img_size, img_offset = entry
        icon_img_data = ico_data[img_offset:img_offset + img_size]
        ok = kernel32.UpdateResourceW(handle, RT_ICON, ctypes.c_void_p(i + 1),
                                       0, icon_img_data, len(icon_img_data))
        if not ok:
            print(f'  [!] 写入 RT_ICON id={i+1} 失败: {ctypes.get_last_error()}')
            kernel32.EndUpdateResourceW(handle, True)
            os.remove(tmp_pe)
            return False
        print(f'  [+] 写入 RT_ICON id={i + 1} ({len(icon_img_data)} bytes)')

    ok = kernel32.UpdateResourceW(handle, RT_GROUP_ICON, ctypes.c_void_p(1),
                                   0, group_data, len(group_data))
    if not ok:
        print(f'  [!] 写入 RT_GROUP_ICON 失败: {ctypes.get_last_error()}')
        kernel32.EndUpdateResourceW(handle, True)
        os.remove(tmp_pe)
        return False

    print(f'  [+] 写入 RT_GROUP_ICON id=1 ({len(group_data)} bytes)')

    if not kernel32.EndUpdateResourceW(handle, False):
        print(f'[!] EndUpdateResource 失败: {ctypes.get_last_error()}')
        os.remove(tmp_pe)
        return False

    print(f'[+] PE 资源部分已更新')

    # 读取更新后的 PE 数据
    with open(tmp_pe, 'rb') as f:
        new_pe_data = bytearray(f.read())
    os.remove(tmp_pe)

    # 拼接 PE + overlay（EndUpdateResource 已正确设置 SizeOfImage）
    final_data = new_pe_data + overlay_data

    with open(exe_path, 'wb') as f:
        f.write(final_data)

    print(f'[+] 图标替换完成: {len(final_data)} bytes')
    return True


if __name__ == '__main__':
    print('=' * 60)
    print('  程序图标替换工具')
    print('=' * 60)

    print(f'\n--- 转换 JPG → ICO ---')
    jpg_to_ico(JPG_PATH, ICO_PATH)

    print(f'\n--- 替换 EXE 图标 ---')
    if replace_icon_preserve_overlay(EXE_PATH, ICO_PATH):
        print(f'\n[+] 图标已成功替换!')
    else:
        print(f'\n[!] 图标替换失败')

    if os.path.exists(ICO_PATH):
        os.remove(ICO_PATH)
