#include #include #include #include #include #include #include #include #include #include "memory.h" #include "uchars.h" #include "api.h" uint32_t MsiGetFileVersionW(const char16_t *filename, char16_t *version, uint32_t *version_size, char16_t *language, uint32_t *language_size) { char *fname = ascii(filename, true); uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ int fd = -1; void *mapv = MAP_FAILED; if (version && *version_size) *version = 0; if (language && *language_size) *language = 0; fd = open(fname, O_RDONLY); if (fd < 0) err(1, "%s: open", fname); struct stat st; if (fstat(fd, &st) < 0) err(1, "%s: fstat", fname); size_t fsize = st.st_size; mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); if (mapv == MAP_FAILED) err(1, "%s: mmap", fname); unsigned char *map = (unsigned char *)mapv; #define BYTE(offset) ({ \ size_t _boffset = (offset); \ if (_boffset >= fsize) goto cleanup; /* outside file bounds */ \ map[_boffset]; \ }) #define WORD16(offset) ({ \ size_t _woffset = (offset); \ uint16_t toret = BYTE(_woffset+1); \ toret = (toret << 8) | BYTE(_woffset+0); \ toret; \ }) #define WORD32(offset) ({ \ size_t _doffset = (offset); \ uint32_t toret = BYTE(_doffset+3); \ toret = (toret << 8) | BYTE(_doffset+2); \ toret = (toret << 8) | BYTE(_doffset+1); \ toret = (toret << 8) | BYTE(_doffset+0); \ toret; \ }) if (WORD16(0) != (('Z'<<8) | 'M')) { warnx("MsiGetFileInfo(%s) -> no MZ", fname); goto cleanup; } unsigned pe_pos = WORD32(0x3c); if (WORD32(pe_pos) != (('E'<<8) | 'P')) { warnx("MsiGetFileInfo(%s) -> no PE", fname); goto cleanup; } pe_pos += 4; /* skip to the main header */ unsigned nsections = WORD16(pe_pos + 2); unsigned opthdr_size = WORD16(pe_pos + 16); unsigned opthdr_pos = pe_pos + 20; /* bool sixtyfourbit = WORD16(opthdr_pos) == 0x020B; */ unsigned secthdr_pos = opthdr_pos + opthdr_size; while (nsections > 0) { if (BYTE(secthdr_pos+0) == '.' && BYTE(secthdr_pos+1) == 'r' && BYTE(secthdr_pos+2) == 's' && BYTE(secthdr_pos+3) == 'r' && BYTE(secthdr_pos+4) == 'c' && BYTE(secthdr_pos+5) == 0) goto found_resource_section; secthdr_pos += 0x28; nsections--; } warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); goto cleanup; found_resource_section:; unsigned rsrc_size = WORD32(secthdr_pos+8); unsigned rsrc_offset = WORD32(secthdr_pos+20); unsigned rsrc_vaddr = WORD32(secthdr_pos+12); unsigned res_dir_offset = rsrc_offset; unsigned nnamed, nid; nnamed = WORD16(res_dir_offset+12); nid = WORD16(res_dir_offset+14); for (unsigned i = nnamed; i < nnamed+nid; i++) { unsigned id = WORD32(res_dir_offset + 16 + 8*i); unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4); if (id == 16 && (entry & 0x80000000)) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_toplevel; } } warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname); goto cleanup; found_versioninfo_toplevel: nnamed = WORD16(res_dir_offset+12); nid = WORD16(res_dir_offset+14); for (unsigned i = 0; i < nnamed+nid; i++) { unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4); if (entry & 0x80000000) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_2ndlevel; } } warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname); goto cleanup; found_versioninfo_2ndlevel: nnamed = WORD16(res_dir_offset+12); nid = WORD16(res_dir_offset+14); for (unsigned i = 0; i < nnamed+nid; i++) { unsigned entry = WORD32(res_dir_offset + 16 + 8*i + 4); if (!(entry & 0x80000000)) { res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); goto found_versioninfo_3rdlevel; } } warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname); goto cleanup; found_versioninfo_3rdlevel:; unsigned versioninfo_offset = WORD32(res_dir_offset); unsigned versioninfo_size = WORD32(res_dir_offset+4); versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr; unsigned name_offset = versioninfo_offset + 6; const char *next_name_chr = "VS_VERSION_INFO"; do { if (WORD16(name_offset) != *next_name_chr) goto cleanup; /* identifying string didn't match */ name_offset += 2; } while (*next_name_chr++); unsigned fixed_offset = (name_offset + 3) & ~3; if (WORD32(fixed_offset) != 0xFEEF04BDU) { warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); goto cleanup; } int four_part_version[4]; four_part_version[0] = WORD16(fixed_offset + 10); four_part_version[1] = WORD16(fixed_offset + 8); four_part_version[2] = WORD16(fixed_offset + 14); four_part_version[3] = WORD16(fixed_offset + 12); unsigned child_offset = fixed_offset + WORD16(versioninfo_offset+2); unsigned lcid; while (child_offset < versioninfo_offset + versioninfo_size) { unsigned this_child_offset = child_offset; child_offset += WORD16(child_offset); child_offset = (child_offset + 3) &~ 3; if (child_offset <= this_child_offset) { warnx("MsiGetFileInfo(%s) -> bad length field", fname); goto cleanup; } const char *next_name_chr = "VarFileInfo"; name_offset = this_child_offset + 6; do { if (WORD16(name_offset) != *next_name_chr) goto this_is_not_a_varfileinfo; name_offset += 2; } while (*next_name_chr++); unsigned subchild_offset = (name_offset + 3) & ~3; next_name_chr = "Translation"; name_offset = subchild_offset + 6; do { if (WORD16(name_offset) != *next_name_chr) { warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname); goto cleanup; } name_offset += 2; } while (*next_name_chr++); subchild_offset = (name_offset + 3) & ~3; lcid = WORD16(subchild_offset); goto success; this_is_not_a_varfileinfo: continue; } warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname); goto cleanup; success:; char verbuf[256], langbuf[256]; snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d", four_part_version[0], four_part_version[1], four_part_version[2], four_part_version[3]); snprintf(langbuf, sizeof(langbuf), "%u", lcid); warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf); if (version) c16cpy(version, version_size, verbuf); if (language) c16cpy(language, language_size, langbuf); toret = 0; cleanup: if (mapv != MAP_FAILED) munmap(mapv, fsize); if (fd != -1) close(fd); sfree(fname); return toret; }