diff options
| author | Simon Tatham <anakin@pobox.com> | 2017-05-16 19:06:37 +0100 |
|---|---|---|
| committer | Simon Tatham <anakin@pobox.com> | 2017-05-16 19:06:37 +0100 |
| commit | af3d986af771c8ce6401df5dd69504fb350c7de9 (patch) | |
| tree | e1ab5557a1308d82fafaec1f925d89549a686c0d | |
| parent | f0cdcb370ec7023428bc421e3a8b5f5cbf1de502 (diff) | |
| download | wix-on-linux-af3d986af771c8ce6401df5dd69504fb350c7de9.tar.gz wix-on-linux-af3d986af771c8ce6401df5dd69504fb350c7de9.tar.bz2 wix-on-linux-af3d986af771c8ce6401df5dd69504fb350c7de9.zip | |
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.
| -rw-r--r-- | fake-lib.c | 12 | ||||
| -rw-r--r-- | fake-lib.h | 1 | ||||
| -rw-r--r-- | fake-msi.c | 181 |
3 files changed, 188 insertions, 6 deletions
| @@ -128,3 +128,15 @@ char *dupcat(const char *str, ...) | |||
| 128 | 128 | ||
| 129 | return out; | 129 | return out; |
| 130 | } | 130 | } |
| 131 | |||
| 132 | unsigned le(const unsigned char *buf, size_t len, size_t off, size_t nbytes) | ||
| 133 | { | ||
| 134 | unsigned toret = 0; | ||
| 135 | off += nbytes; | ||
| 136 | while (nbytes-- > 0) { | ||
| 137 | toret <<= 8; | ||
| 138 | if (--off < len) | ||
| 139 | toret |= buf[off]; | ||
| 140 | } | ||
| 141 | return toret; | ||
| 142 | } | ||
| @@ -5,6 +5,7 @@ void c16cpy(char16_t *out, uint32_t *outsize, char *s); | |||
| 5 | void *smalloc(size_t size); | 5 | void *smalloc(size_t size); |
| 6 | void *srealloc(void *ptr, size_t size); | 6 | void *srealloc(void *ptr, size_t size); |
| 7 | char *dupcat(const char *str, ...); | 7 | char *dupcat(const char *str, ...); |
| 8 | unsigned le(const unsigned char *buf, size_t len, size_t off, size_t nbytes); | ||
| 8 | 9 | ||
| 9 | #define snew(type) ((type *)smalloc(sizeof(type))) | 10 | #define snew(type) ((type *)smalloc(sizeof(type))) |
| 10 | #define snewn(n,type) ((type *)smalloc((n)*sizeof(type))) | 11 | #define snewn(n,type) ((type *)smalloc((n)*sizeof(type))) |
| @@ -9,6 +9,10 @@ | |||
| 9 | #include <err.h> | 9 | #include <err.h> |
| 10 | 10 | ||
| 11 | #include <fcntl.h> | 11 | #include <fcntl.h> |
| 12 | #include <sys/mman.h> | ||
| 13 | #include <sys/types.h> | ||
| 14 | #include <sys/stat.h> | ||
| 15 | #include <unistd.h> | ||
| 12 | 16 | ||
| 13 | #include "fake-lib.h" | 17 | #include "fake-lib.h" |
| 14 | 18 | ||
| @@ -16,14 +20,179 @@ uint32_t MsiGetFileVersionW(const char16_t *filename, | |||
| 16 | char16_t *version, uint32_t *version_size, | 20 | char16_t *version, uint32_t *version_size, |
| 17 | char16_t *language, uint32_t *language_size) | 21 | char16_t *language, uint32_t *language_size) |
| 18 | { | 22 | { |
| 19 | warnx("FIXME: MsiGetFileVersion(%s)", ascii(filename, true)); | 23 | char *fname = ascii(filename, true); |
| 20 | if (version) { | 24 | uint32_t toret = 1006; /* ERROR_FILE_INVALID == 'no version info found' */ |
| 21 | c16cpy(version, version_size, "0.1.2.3"); | 25 | int fd = -1; |
| 26 | void *mapv = MAP_FAILED; | ||
| 27 | |||
| 28 | fd = open(fname, O_RDONLY); | ||
| 29 | if (fd < 0) | ||
| 30 | err(1, "%s: open", fname); | ||
| 31 | struct stat st; | ||
| 32 | if (fstat(fd, &st) < 0) | ||
| 33 | err(1, "%s: fstat", fname); | ||
| 34 | size_t fsize = st.st_size; | ||
| 35 | mapv = mmap(NULL, fsize, PROT_READ, MAP_PRIVATE, fd, 0); | ||
| 36 | if (mapv == MAP_FAILED) | ||
| 37 | err(1, "%s: mmap", fname); | ||
| 38 | unsigned char *map = (unsigned char *)mapv; | ||
| 39 | |||
| 40 | if (le(map, fsize, 0, 2) != (('Z'<<8) | 'M')) { | ||
| 41 | warnx("MsiGetFileInfo(%s) -> no MZ", fname); | ||
| 42 | goto cleanup; | ||
| 22 | } | 43 | } |
| 23 | if (language) { | 44 | unsigned pe_pos = le(map, fsize, 0x3c, 4); |
| 24 | c16cpy(language, language_size, "2057"); | 45 | if (le(map, fsize, pe_pos, 4) != (('E'<<8) | 'P')) { |
| 46 | warnx("MsiGetFileInfo(%s) -> no PE", fname); | ||
| 47 | goto cleanup; | ||
| 25 | } | 48 | } |
| 26 | return 0; | 49 | pe_pos += 4; /* skip to the main header */ |
| 50 | unsigned nsections = le(map, fsize, pe_pos + 2, 2); | ||
| 51 | unsigned opthdr_size = le(map, fsize, pe_pos + 16, 2); | ||
| 52 | unsigned opthdr_pos = pe_pos + 20; | ||
| 53 | /* bool sixtyfourbit = le(map, fsize, opthdr_pos, 2) == 0x020B; */ | ||
| 54 | unsigned secthdr_pos = opthdr_pos + opthdr_size; | ||
| 55 | while (nsections > 0) { | ||
| 56 | if (le(map, fsize, secthdr_pos+0, 1) == '.' && | ||
| 57 | le(map, fsize, secthdr_pos+1, 1) == 'r' && | ||
| 58 | le(map, fsize, secthdr_pos+2, 1) == 's' && | ||
| 59 | le(map, fsize, secthdr_pos+3, 1) == 'r' && | ||
| 60 | le(map, fsize, secthdr_pos+4, 1) == 'c' && | ||
| 61 | le(map, fsize, secthdr_pos+5, 1) == 0) | ||
| 62 | goto found_resource_section; | ||
| 63 | secthdr_pos += 0x28; | ||
| 64 | nsections--; | ||
| 65 | } | ||
| 66 | warnx("MsiGetFileInfo(%s) -> no .rsrc", fname); | ||
| 67 | goto cleanup; | ||
| 68 | |||
| 69 | found_resource_section:; | ||
| 70 | unsigned rsrc_size = le(map, fsize, secthdr_pos+8, 4); | ||
| 71 | unsigned rsrc_offset = le(map, fsize, secthdr_pos+20, 4); | ||
| 72 | unsigned rsrc_vaddr = le(map, fsize, secthdr_pos+12, 4); | ||
| 73 | |||
| 74 | unsigned res_dir_offset = rsrc_offset; | ||
| 75 | unsigned nnamed, nid; | ||
| 76 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 77 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 78 | for (unsigned i = nnamed; i < nnamed+nid; i++) { | ||
| 79 | unsigned id = le(map, fsize, res_dir_offset + 16 + 8*i, 4); | ||
| 80 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 81 | if (id == 16 && (entry & 0x80000000)) { | ||
| 82 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 83 | goto found_versioninfo_toplevel; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | warnx("MsiGetFileInfo(%s) -> no top-level numeric key 16 for versioninfo", fname); | ||
| 87 | goto cleanup; | ||
| 88 | |||
| 89 | found_versioninfo_toplevel: | ||
| 90 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 91 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 92 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 93 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 94 | if (entry & 0x80000000) { | ||
| 95 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 96 | goto found_versioninfo_2ndlevel; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | warnx("MsiGetFileInfo(%s) -> no 2nd-level subdir", fname); | ||
| 100 | goto cleanup; | ||
| 101 | |||
| 102 | found_versioninfo_2ndlevel: | ||
| 103 | nnamed = le(map, fsize, res_dir_offset+12, 2); | ||
| 104 | nid = le(map, fsize, res_dir_offset+14, 2); | ||
| 105 | for (unsigned i = 0; i < nnamed+nid; i++) { | ||
| 106 | unsigned entry = le(map, fsize, res_dir_offset + 16 + 8*i + 4, 4); | ||
| 107 | if (!(entry & 0x80000000)) { | ||
| 108 | res_dir_offset = rsrc_offset + (entry & 0x7FFFFFFF); | ||
| 109 | goto found_versioninfo_3rdlevel; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | warnx("MsiGetFileInfo(%s) -> no 3rd-level resource data", fname); | ||
| 113 | goto cleanup; | ||
| 114 | |||
| 115 | found_versioninfo_3rdlevel:; | ||
| 116 | unsigned versioninfo_offset = le(map, fsize, res_dir_offset, 4); | ||
| 117 | unsigned versioninfo_size = le(map, fsize, res_dir_offset+4, 4); | ||
| 118 | versioninfo_offset = rsrc_offset + versioninfo_offset - rsrc_vaddr; | ||
| 119 | |||
| 120 | unsigned name_offset = versioninfo_offset + 6; | ||
| 121 | const char *next_name_chr = "VS_VERSION_INFO"; | ||
| 122 | do { | ||
| 123 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 124 | goto cleanup; /* identifying string didn't match */ | ||
| 125 | name_offset += 2; | ||
| 126 | } while (*next_name_chr++); | ||
| 127 | unsigned fixed_offset = (name_offset + 3) & ~3; | ||
| 128 | if (le(map, fsize, fixed_offset, 4) != 0xFEEF04BDU) { | ||
| 129 | warnx("MsiGetFileInfo(%s) -> no VS_FIXEDFILEINFO magic number", fname); | ||
| 130 | goto cleanup; | ||
| 131 | } | ||
| 132 | int four_part_version[4]; | ||
| 133 | four_part_version[0] = le(map, fsize, fixed_offset + 10, 2); | ||
| 134 | four_part_version[1] = le(map, fsize, fixed_offset + 8, 2); | ||
| 135 | four_part_version[2] = le(map, fsize, fixed_offset + 14, 2); | ||
| 136 | four_part_version[3] = le(map, fsize, fixed_offset + 12, 2); | ||
| 137 | unsigned child_offset = fixed_offset + | ||
| 138 | le(map, fsize, versioninfo_offset+2, 2); | ||
| 139 | unsigned lcid; | ||
| 140 | while (child_offset < versioninfo_offset + versioninfo_size) { | ||
| 141 | unsigned this_child_offset = child_offset; | ||
| 142 | child_offset += le(map, fsize, child_offset, 2); | ||
| 143 | child_offset = (child_offset + 3) &~ 3; | ||
| 144 | if (child_offset <= this_child_offset) { | ||
| 145 | warnx("MsiGetFileInfo(%s) -> bad length field", fname); | ||
| 146 | goto cleanup; | ||
| 147 | } | ||
| 148 | const char *next_name_chr = "VarFileInfo"; | ||
| 149 | name_offset = this_child_offset + 6; | ||
| 150 | do { | ||
| 151 | if (le(map, fsize, name_offset, 2) != *next_name_chr) | ||
| 152 | goto this_is_not_a_varfileinfo; | ||
| 153 | name_offset += 2; | ||
| 154 | } while (*next_name_chr++); | ||
| 155 | unsigned subchild_offset = (name_offset + 3) & ~3; | ||
| 156 | |||
| 157 | next_name_chr = "Translation"; | ||
| 158 | name_offset = subchild_offset + 6; | ||
| 159 | do { | ||
| 160 | if (le(map, fsize, name_offset, 2) != *next_name_chr) { | ||
| 161 | warnx("MsiGetFileInfo(%s) -> child not called 'Translation'", fname); | ||
| 162 | goto cleanup; | ||
| 163 | } | ||
| 164 | name_offset += 2; | ||
| 165 | } while (*next_name_chr++); | ||
| 166 | subchild_offset = (name_offset + 3) & ~3; | ||
| 167 | lcid = le(map, fsize, subchild_offset, 2); | ||
| 168 | goto success; | ||
| 169 | this_is_not_a_varfileinfo: | ||
| 170 | continue; | ||
| 171 | } | ||
| 172 | warnx("MsiGetFileInfo(%s) -> no VarFileInfo found", fname); | ||
| 173 | goto cleanup; | ||
| 174 | |||
| 175 | success:; | ||
| 176 | char verbuf[256], langbuf[256]; | ||
| 177 | snprintf(verbuf, sizeof(verbuf), "%d.%d.%d.%d", | ||
| 178 | four_part_version[0], four_part_version[1], | ||
| 179 | four_part_version[2], four_part_version[3]); | ||
| 180 | snprintf(langbuf, sizeof(langbuf), "%u", lcid); | ||
| 181 | warnx("MsiGetFileInfo(%s) -> version %s lang %s", fname, verbuf, langbuf); | ||
| 182 | if (version) | ||
| 183 | c16cpy(version, version_size, verbuf); | ||
| 184 | if (language) | ||
| 185 | c16cpy(language, language_size, langbuf); | ||
| 186 | toret = 0; | ||
| 187 | |||
| 188 | cleanup: | ||
| 189 | if (mapv != MAP_FAILED) | ||
| 190 | munmap(mapv, fsize); | ||
| 191 | if (fd != -1) | ||
| 192 | close(fd); | ||
| 193 | sfree(fname); | ||
| 194 | return toret; | ||
| 195 | } | ||
| 27 | } | 196 | } |
| 28 | 197 | ||
| 29 | typedef struct MsiTypePrefix { | 198 | typedef struct MsiTypePrefix { |
