#!/usr/bin/env python3 """Checks that a Windows exe carries the Igneum resources: a .rsrc section with an icon group and a version block whose CompanyName is Igneum and ProductName is Igneum Miner. Prints what it finds. Small PE parser, no dependencies, runs on the Mac where rcedit and Explorer are not available. python3 verify-exe.py [--version 0.3.0] exe [exe ...] exit 1 if any exe fails""" import struct, sys RT_ICON, RT_GROUP_ICON, RT_VERSION = 3, 14, 16 REQUIRED = ("CompanyName", "ProductName", "FileDescription", "FileVersion", "ProductVersion", "LegalCopyright") def sections_of(data): pe = struct.unpack_from("= 52 and struct.unpack_from("> 16, ms & 0xFFFF, ls >> 16, ls & 0xFFFF, pms >> 16, pms & 0xFFFF, pls >> 16, pls & 0xFFFF) pos = (val + vlen + 3) & ~3 while pos < off + length: clen, _, _, ckey, cval = block(pos) if ckey == "StringFileInfo": tpos = cval while tpos < pos + clen: tlen, _, _, _, tval = block(tpos) spos = tval while spos < tpos + tlen: slen, svlen, _, skey, sval = block(spos) if slen == 0: break raw = data[sval:sval + 2 * svlen] strings[skey] = raw.decode("utf-16-le").rstrip("\0") spos = (spos + slen + 3) & ~3 tpos = (tpos + tlen + 3) & ~3 if clen == 0: break pos = (pos + clen + 3) & ~3 return fixed, strings def check(path, want_version): data = open(path, "rb").read() secs, res_rva, res_size = sections_of(data) names = [s[0] for s in secs] problems = [] if ".rsrc" not in names: problems.append("no .rsrc section") if not res_rva: problems.append("resource table empty in the PE data directory") icons = groups = 0 fixed, strings = None, {} if res_rva: base = rva_to_off(secs, res_rva) found = [] walk(data, base, 0, 0, found) for p, rva, size in found: if p[0] == RT_ICON: icons += 1 elif p[0] == RT_GROUP_ICON: groups += 1 elif p[0] == RT_VERSION and fixed is None: fixed, strings = version_strings(data, rva_to_off(secs, rva), size) if not groups: problems.append("no icon group (RT_GROUP_ICON)") if fixed is None: problems.append("no version block (RT_VERSION)") for k in REQUIRED: if not strings.get(k): problems.append("missing " + k) if strings.get("CompanyName") not in (None, "Igneum"): problems.append("CompanyName is %r, not Igneum" % strings["CompanyName"]) if strings.get("ProductName") not in (None, "Igneum Miner"): problems.append("ProductName is %r, not Igneum Miner" % strings["ProductName"]) if want_version: for k in ("FileVersion", "ProductVersion"): if strings.get(k) and strings[k] != want_version: problems.append("%s is %s, not %s" % (k, strings[k], want_version)) if fixed and ".".join(map(str, fixed[:3])) != want_version: problems.append("FILEVERSION is %s, not %s" % (".".join(map(str, fixed[:4])), want_version)) print("%s: %d bytes, sections %s" % (path, len(data), " ".join(names))) print(" icon images %d, icon groups %d, FILEVERSION %s" % (icons, groups, ".".join(map(str, fixed[:4])) if fixed else "none")) for k in REQUIRED + ("InternalName", "OriginalFilename"): if k in strings: print(" %-17s %s" % (k, strings[k])) for p in problems: print(" FAIL: " + p) return not problems if __name__ == "__main__": args = sys.argv[1:] want = None if args[:1] == ["--version"]: want, args = args[1], args[2:] if not args: sys.exit(__doc__) ok = all([check(p, want) for p in args]) print("all %d exe(s) carry the Igneum resources" % len(args) if ok else "FAILED") sys.exit(0 if ok else 1)