BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / scripts / package / pe_has_icon.py

pe_has_icon.py

pe_has_icon.py 2.7 KiB Download Raw
1#!/usr/bin/env python3
2"""Require RT_ICON and RT_GROUP_ICON inside a PE, not a sibling .ico file."""
3
4import struct
5import sys
6import zipfile
7
8RT_ICON = 3
9RT_GROUP_ICON = 14
10
11
12def pe_has_icon(data: bytes) -> None:
13 if data[:2] != b"MZ":
14 raise SystemExit("executable does not start with MZ")
15 e_lfanew = struct.unpack_from("<I", data, 0x3C)[0]
16 if data[e_lfanew : e_lfanew + 4] != b"PE\0\0":
17 raise SystemExit("executable has no PE signature")
18 coff = e_lfanew + 4
19 nsections = struct.unpack_from("<H", data, coff + 2)[0]
20 optsize = struct.unpack_from("<H", data, coff + 16)[0]
21 opt = coff + 20
22 magic = struct.unpack_from("<H", data, opt)[0]
23 if magic == 0x20B:
24 dd_off = opt + 112
25 elif magic == 0x10B:
26 dd_off = opt + 96
27 else:
28 raise SystemExit(f"unknown PE magic {magic:#x}")
29 res_rva, res_size = struct.unpack_from("<II", data, dd_off + 8 * 2)
30 if res_rva == 0 or res_size == 0:
31 raise SystemExit("PE has no resource directory")
32 sections = []
33 sec = opt + optsize
34 for index in range(nsections):
35 off = sec + index * 40
36 vsize, va, rawsize, rawptr = struct.unpack_from("<IIII", data, off + 8)
37 sections.append((va, rawptr, max(rawsize, vsize)))
38
39 def rva_to_off(rva: int) -> int:
40 for va, rawptr, span in sections:
41 if va <= rva < va + span:
42 return rawptr + (rva - va)
43 raise SystemExit(f"resource RVA {rva:#x} is outside the sections")
44
45 off = rva_to_off(res_rva)
46 nnames, nids = struct.unpack_from("<HH", data, off + 12)
47 found = set()
48 base = off + 16
49 for index in range(nnames + nids):
50 name_or_id, nextv = struct.unpack_from("<II", data, base + index * 8)
51 if name_or_id & 0x80000000:
52 continue
53 if nextv & 0x80000000 and name_or_id in (RT_ICON, RT_GROUP_ICON):
54 found.add(name_or_id)
55 missing = {RT_ICON, RT_GROUP_ICON} - found
56 if missing:
57 raise SystemExit(f"PE is missing icon resource types {sorted(missing)}")
58
59
60def main() -> None:
61 path = sys.argv[1]
62 if path.endswith(".zip"):
63 with zipfile.ZipFile(path) as archive:
64 names = archive.namelist()
65 loose = [name for name in names if name.lower().endswith(".ico")]
66 if loose:
67 raise SystemExit(f"zip carries a loose icon file: {loose}")
68 exe_name = "paperdoll-renderer.exe"
69 if exe_name not in names:
70 raise SystemExit("zip has no paperdoll-renderer.exe")
71 pe_has_icon(archive.read(exe_name))
72 else:
73 with open(path, "rb") as handle:
74 pe_has_icon(handle.read())
75 print(f"embedded icon ok: {path}")
76
77
78if __name__ == "__main__":
79 main()