From af3d986af771c8ce6401df5dd69504fb350c7de9 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 16 May 2017 19:06:37 +0100 Subject: Implement MsiGetFileVersion. This is pretty ugly code, but it works well enough to deliver the right versions for the files in my test MSI. I can polish it later. --- fake-lib.c | 12 ++++ fake-lib.h | 1 + fake-msi.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 188 insertions(+), 6 deletions(-) diff --git a/fake-lib.c b/fake-lib.c index 2207ce3..011d35d 100644 --- a/fake-lib.c +++ b/fake-lib.c @@ -128,3 +128,15 @@ char *dupcat(const char *str, ...) return out; } + +unsigned le(const unsigned char *buf, size_t len, size_t off, size_t nbytes) +{ + unsigned toret = 0; + off += nbytes; + while (nbytes-- > 0) { + toret <<= 8; + if (--off < len) + toret |= buf[off]; + } + return toret; +} diff --git a/fake-lib.h b/fake-lib.h index c95169a..9dd8049 100644 --- a/fake-lib.h +++ b/fake-lib.h @@ -5,6 +5,7 @@ void c16cpy(char16_t *out, uint32_t *outsize, char *s); void *smalloc(size_t size); void *srealloc(void *ptr, size_t size); char *dupcat(const char *str, ...); +unsigned le(const unsigned char *buf, size_t len, size_t off, size_t nbytes); #define snew(type) ((type *)smalloc(sizeof(type))) #define snewn(n,type) ((type *)smalloc((n)*sizeof(type))) diff --git a/fake-msi.c b/fake-msi.c index bbde0cb..2ceab99 100644 --- a/fake-msi.c +++ b/fake-msi.c @@ -9,6 +9,10 @@ #include #include +#include +#include +#include +#include #include "fake-lib.h" @@ -16,14 +20,179 @@ uint32_t MsiGetFileVersionW(const char16_t *filename, char16_t *version, uint32_t *version_size, char16_t *language, uint32_t *language_size) { - warnx("FIXME: MsiGetFileVersion(%s)", ascii(filename, true)); - if (version) { - c16cpy(version, version_size, "0.1.2.3"); + char *fname = ascii(filename, true); + uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ + int fd = -1; + void *mapv = MAP_FAILED; + + 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; + + if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) { + warnx("MsiGetFileInfo(%s) -> no MZ", fname); + goto cleanup; } - if (language) { - c16cpy(language, language_size, "2057"); + unsigned pe_pos = le(map, fsize, 0x3c, 4); + if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) { + warnx("MsiGetFileInfo(%s) -> no PE", fname); + goto cleanup; } - return 0; + pe_pos += 4; /* skip to the main header */ + unsigned nsections = le(map, fsize, pe_pos + 2, 2); + unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2); + unsigned opthdr_pos = pe_pos + 20; + /* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */ + unsigned secthdr_pos = opthdr_pos + opthdr_size; + while (nsections > 0) { + if (le(map, fsize, secthdr_pos+0, 1) == '.' && + le(map, fsize, secthdr_pos+1, 1) == 'r' && + le(map, fsize, secthdr_pos+2, 1) == 's' && + le(map, fsize, secthdr_pos+3, 1) == 'r' && + le(map, fsize, secthdr_pos+4, 1) == 'c' && + le(map, fsize, secthdr_pos+5, 1) == 0) + goto found_resource_section; + secthdr_pos += 0x28; + nsections--; + } + warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); + goto cleanup; + + found_resource_section:; + unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4); + unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4); + unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4); + + unsigned res_dir_offset = rsrc_offset; + unsigned nnamed, nid; + nnamed = le(map, fsize, res_dir_offset+12, 2); + nid = le(map, fsize, res_dir_offset+14, 2); + for (unsigned i = nnamed; i < nnamed+nid; i++) { + unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4); + unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 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 = le(map, fsize, res_dir_offset+12, 2); + nid = le(map, fsize, res_dir_offset+14, 2); + for (unsigned i = 0; i < nnamed+nid; i++) { + unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 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 = le(map, fsize, res_dir_offset+12, 2); + nid = le(map, fsize, res_dir_offset+14, 2); + for (unsigned i = 0; i < nnamed+nid; i++) { + unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 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 = le(map, fsize, res_dir_offset, 4); + unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 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 (le(map, fsize, name_offset, 2) != *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 (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) { + warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); + goto cleanup; + } + int four_part_version[4]; + four_part_version[0] = le(map, fsize, fixed_offset + 10, 2); + four_part_version[1] = le(map, fsize, fixed_offset + 8, 2); + four_part_version[2] = le(map, fsize, fixed_offset + 14, 2); + four_part_version[3] = le(map, fsize, fixed_offset + 12, 2); + unsigned child_offset = fixed_offset + + le(map, fsize, versioninfo_offset+2, 2); + unsigned lcid; + while (child_offset < versioninfo_offset + versioninfo_size) { + unsigned this_child_offset = child_offset; + child_offset += le(map, fsize, child_offset, 2); + 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 (le(map, fsize, name_offset, 2) != *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 (le(map, fsize, name_offset, 2) != *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 = le(map, fsize, subchild_offset, 2); + 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; +} } typedef struct MsiTypePrefix { -- cgit v1.2.3-55-g6feb