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