2 * versioninfo.c: Version information support
5 * Dick Porter (dick@ximian.com)
7 * (C) 2007 Novell, Inc.
14 #include <sys/types.h>
20 #include <mono/io-layer/wapi.h>
21 #include <mono/io-layer/wapi-private.h>
22 #include <mono/io-layer/versioninfo.h>
23 #include <mono/io-layer/io-portability.h>
24 #include <mono/io-layer/error.h>
25 #include <mono/utils/strenc.h>
26 #include <mono/utils/mono-mmap.h>
29 //define LOGDEBUG(...) g_message(__VA_ARGS__)
31 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
33 static WapiImageSectionHeader *
34 get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
36 WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
39 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
40 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
42 size = GUINT32_FROM_LE (section->SizeOfRawData);
45 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
46 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
54 /* This works for both 32bit and 64bit files, as the differences are
55 * all after the section header block
58 get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders, gpointer file_map)
60 WapiImageSectionHeader *section_header;
63 section_header = get_enclosing_section_header (rva, ntheaders);
64 if (section_header == NULL) {
68 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
69 GUINT32_FROM_LE (section_header->PointerToRawData));
71 return((guint8 *)file_map + rva - delta);
75 scan_resource_dir (WapiImageResourceDirectory *root,
76 WapiImageNTHeaders32 *nt_headers,
78 WapiImageResourceDirectoryEntry *entry,
79 int level, guint32 res_id, guint32 lang_id,
82 WapiImageResourceDirectoryEntry swapped_entry;
83 gboolean is_string, is_dir;
84 guint32 name_offset, dir_offset, data_offset;
86 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
87 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
89 is_string = swapped_entry.NameIsString;
90 is_dir = swapped_entry.DataIsDirectory;
91 name_offset = swapped_entry.NameOffset;
92 dir_offset = swapped_entry.OffsetToDirectory;
93 data_offset = swapped_entry.OffsetToData;
96 /* Normally holds a directory entry for each type of
99 if ((is_string == FALSE &&
100 name_offset != res_id) ||
101 (is_string == TRUE)) {
104 } else if (level == 1) {
105 /* Normally holds a directory entry for each resource
108 } else if (level == 2) {
109 /* Normally holds a directory entry for each language
111 if ((is_string == FALSE &&
112 name_offset != lang_id &&
114 (is_string == TRUE)) {
118 g_assert_not_reached ();
121 if (is_dir == TRUE) {
122 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
123 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
126 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
128 for (i = 0; i < entries; i++) {
129 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
132 ret = scan_resource_dir (root, nt_headers, file_map,
133 sub_entry, level + 1, res_id,
142 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
143 *size = GUINT32_FROM_LE (data_entry->Size);
145 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
150 find_pe_file_resources32 (gpointer file_map, guint32 map_size,
151 guint32 res_id, guint32 lang_id,
154 WapiImageDosHeader *dos_header;
155 WapiImageNTHeaders32 *nt_headers;
156 WapiImageResourceDirectory *resource_dir;
157 WapiImageResourceDirectoryEntry *resource_dir_entry;
158 guint32 resource_rva, entries, i;
161 dos_header = (WapiImageDosHeader *)file_map;
162 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
163 LOGDEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
165 SetLastError (ERROR_INVALID_DATA);
169 if (map_size < sizeof(WapiImageNTHeaders32) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
170 LOGDEBUG ("%s: File is too small: %d", __func__, map_size);
172 SetLastError (ERROR_BAD_LENGTH);
176 nt_headers = (WapiImageNTHeaders32 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
177 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
178 LOGDEBUG ("%s: Bad NT signature 0x%x", __func__, nt_headers->Signature);
180 SetLastError (ERROR_INVALID_DATA);
184 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
185 /* Do 64-bit stuff */
186 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
188 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
191 if (resource_rva == 0) {
192 LOGDEBUG ("%s: No resources in file!", __func__);
194 SetLastError (ERROR_INVALID_DATA);
198 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
199 if (resource_dir == NULL) {
200 LOGDEBUG ("%s: Can't find resource directory", __func__);
202 SetLastError (ERROR_INVALID_DATA);
206 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
207 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
209 for (i = 0; i < entries; i++) {
210 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
211 ret = scan_resource_dir (resource_dir,
212 (WapiImageNTHeaders32 *)nt_headers,
213 file_map, direntry, 0, res_id,
224 find_pe_file_resources64 (gpointer file_map, guint32 map_size,
225 guint32 res_id, guint32 lang_id,
228 WapiImageDosHeader *dos_header;
229 WapiImageNTHeaders64 *nt_headers;
230 WapiImageResourceDirectory *resource_dir;
231 WapiImageResourceDirectoryEntry *resource_dir_entry;
232 guint32 resource_rva, entries, i;
235 dos_header = (WapiImageDosHeader *)file_map;
236 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
237 LOGDEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
239 SetLastError (ERROR_INVALID_DATA);
243 if (map_size < sizeof(WapiImageNTHeaders64) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
244 LOGDEBUG ("%s: File is too small: %d", __func__, map_size);
246 SetLastError (ERROR_BAD_LENGTH);
250 nt_headers = (WapiImageNTHeaders64 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
251 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
252 LOGDEBUG ("%s: Bad NT signature 0x%x", __func__,
253 nt_headers->Signature);
255 SetLastError (ERROR_INVALID_DATA);
259 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
260 /* Do 64-bit stuff */
261 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
263 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
266 if (resource_rva == 0) {
267 LOGDEBUG ("%s: No resources in file!", __func__);
269 SetLastError (ERROR_INVALID_DATA);
273 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
274 if (resource_dir == NULL) {
275 LOGDEBUG ("%s: Can't find resource directory", __func__);
277 SetLastError (ERROR_INVALID_DATA);
281 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
282 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
284 for (i = 0; i < entries; i++) {
285 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
286 ret = scan_resource_dir (resource_dir,
287 (WapiImageNTHeaders32 *)nt_headers,
288 file_map, direntry, 0, res_id,
299 find_pe_file_resources (gpointer file_map, guint32 map_size,
300 guint32 res_id, guint32 lang_id,
303 /* Figure this out when we support 64bit PE files */
305 return find_pe_file_resources32 (file_map, map_size, res_id,
308 return find_pe_file_resources64 (file_map, map_size, res_id,
314 map_pe_file (gunichar2 *filename, gint32 *map_size, void **handle)
321 /* According to the MSDN docs, a search path is applied to
322 * filename. FIXME: implement this, for now just pass it
326 filename_ext = mono_unicode_to_external (filename);
327 if (filename_ext == NULL) {
328 LOGDEBUG ("%s: unicode conversion returned NULL", __func__);
330 SetLastError (ERROR_INVALID_NAME);
334 fd = _wapi_open (filename_ext, O_RDONLY, 0);
336 LOGDEBUG ("%s: Error opening file %s: %s", __func__, filename_ext, strerror (errno));
338 SetLastError (_wapi_get_win32_file_error (errno));
339 g_free (filename_ext);
344 if (fstat (fd, &statbuf) == -1) {
345 LOGDEBUG ("%s: Error stat()ing file %s: %s", __func__, filename_ext, strerror (errno));
347 SetLastError (_wapi_get_win32_file_error (errno));
348 g_free (filename_ext);
352 *map_size = statbuf.st_size;
354 /* Check basic file size */
355 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
356 LOGDEBUG ("%s: File %s is too small: %lld", __func__, filename_ext, statbuf.st_size);
358 SetLastError (ERROR_BAD_LENGTH);
359 g_free (filename_ext);
364 file_map = mono_file_map (statbuf.st_size, MONO_MMAP_READ | MONO_MMAP_PRIVATE, fd, 0, handle);
365 if (file_map == NULL) {
366 LOGDEBUG ("%s: Error mmap()int file %s: %s", __func__, filename_ext, strerror (errno));
368 SetLastError (_wapi_get_win32_file_error (errno));
369 g_free (filename_ext);
374 /* Don't need the fd any more */
376 g_free (filename_ext);
382 unmap_pe_file (gpointer file_map, void *handle)
384 mono_file_unmap (file_map, handle);
388 unicode_chars (const gunichar2 *str)
393 if (str[len] == '\0') {
401 unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
403 while (*str1 && *str2) {
404 if (*str1 != *str2) {
411 return(*str1 == *str2);
414 /* compare a little-endian null-terminated utf16 string and a normal string.
415 * Can be used only for ascii or latin1 chars.
418 unicode_string_equals (const gunichar2 *str1, const gchar *str2)
420 while (*str1 && *str2) {
421 if (GUINT16_TO_LE (*str1) != *str2) {
428 return(*str1 == *str2);
439 /* Returns a pointer to the value data, because there's no way to know
440 * how big that data is (value_len is set to zero for most blocks :-( )
443 get_versioninfo_block (gconstpointer data, version_data *block)
445 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
446 data = (char *)data + sizeof(guint16);
447 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
448 data = (char *)data + sizeof(guint16);
450 /* No idea what the type is supposed to indicate */
451 block->type = GUINT16_FROM_LE (*((guint16 *)data));
452 data = (char *)data + sizeof(guint16);
453 block->key = ((gunichar2 *)data);
455 /* Skip over the key (including the terminator) */
456 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
458 /* align on a 32-bit boundary */
465 get_fixedfileinfo_block (gconstpointer data, version_data *block)
467 gconstpointer data_ptr;
468 gint32 data_len; /* signed to guard against underflow */
469 WapiFixedFileInfo *ffi;
471 data_ptr = get_versioninfo_block (data, block);
472 data_len = block->data_len;
474 if (block->value_len != sizeof(WapiFixedFileInfo)) {
475 LOGDEBUG ("%s: FIXEDFILEINFO size mismatch", __func__);
479 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
480 LOGDEBUG ("%s: VS_VERSION_INFO mismatch", __func__);
485 ffi = ((WapiFixedFileInfo *)data_ptr);
486 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
487 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
488 LOGDEBUG ("%s: FIXEDFILEINFO bad signature", __func__);
497 get_varfileinfo_block (gconstpointer data_ptr, version_data *block)
499 /* data is pointing at a Var block
501 data_ptr = get_versioninfo_block (data_ptr, block);
507 get_string_block (gconstpointer data_ptr,
508 const gunichar2 *string_key,
509 gpointer *string_value,
510 guint32 *string_value_len,
513 guint16 data_len = block->data_len;
514 guint16 string_len = 28; /* Length of the StringTable block */
515 char *orig_data_ptr = (char *)data_ptr - 28;
517 /* data_ptr is pointing at an array of one or more String blocks
518 * with total length (not including alignment padding) of
521 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
522 /* align on a 32-bit boundary */
525 data_ptr = get_versioninfo_block (data_ptr, block);
526 if (block->data_len == 0) {
527 /* We must have hit padding, so give up
530 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
535 string_len = string_len + block->data_len;
537 if (string_key != NULL &&
538 string_value != NULL &&
539 string_value_len != NULL &&
540 unicode_compare (string_key, block->key) == TRUE) {
541 *string_value = (gpointer)data_ptr;
542 *string_value_len = block->value_len;
545 /* Skip over the value */
546 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
552 /* Returns a pointer to the byte following the Stringtable block, or
553 * NULL if the data read hits padding. We can't recover from this
554 * because the data length does not include padding bytes, so it's not
555 * possible to just return the start position + length
557 * If lang == NULL it means we're just stepping through this block
560 get_stringtable_block (gconstpointer data_ptr,
562 const gunichar2 *string_key,
563 gpointer *string_value,
564 guint32 *string_value_len,
567 guint16 data_len = block->data_len;
568 guint16 string_len = 36; /* length of the StringFileInfo block */
570 gchar *lowercase_lang;
572 /* data_ptr is pointing at an array of StringTable blocks,
573 * with total length (not including alignment padding) of
577 while(string_len < data_len) {
578 /* align on a 32-bit boundary */
581 data_ptr = get_versioninfo_block (data_ptr, block);
582 if (block->data_len == 0) {
583 /* We must have hit padding, so give up
586 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
590 string_len = string_len + block->data_len;
592 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
593 if (found_lang == NULL) {
594 LOGDEBUG ("%s: Didn't find a valid language key, giving up", __func__);
598 lowercase_lang = g_utf8_strdown (found_lang, -1);
600 found_lang = lowercase_lang;
601 lowercase_lang = NULL;
603 if (lang != NULL && !strcmp (found_lang, lang)) {
604 /* Got the one we're interested in */
605 data_ptr = get_string_block (data_ptr, string_key,
607 string_value_len, block);
609 data_ptr = get_string_block (data_ptr, NULL, NULL,
615 if (data_ptr == NULL) {
616 /* Child block hit padding */
617 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
625 #if G_BYTE_ORDER == G_BIG_ENDIAN
627 big_up_string_block (gconstpointer data_ptr, version_data *block)
629 guint16 data_len = block->data_len;
630 guint16 string_len = 28; /* Length of the StringTable block */
632 char *orig_data_ptr = (char *)data_ptr - 28;
634 /* data_ptr is pointing at an array of one or more String
635 * blocks with total length (not including alignment padding)
638 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
639 /* align on a 32-bit boundary */
642 data_ptr = get_versioninfo_block (data_ptr, block);
643 if (block->data_len == 0) {
644 /* We must have hit padding, so give up
647 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
651 string_len = string_len + block->data_len;
653 big_value = g_convert ((gchar *)block->key,
654 unicode_chars (block->key) * 2,
655 "UTF-16BE", "UTF-16LE", NULL, NULL,
657 if (big_value == NULL) {
658 LOGDEBUG ("%s: Didn't find a valid string, giving up", __func__);
662 /* The swapped string should be exactly the same
663 * length as the original little-endian one, but only
664 * copy the number of original chars just to be on the
667 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
670 big_value = g_convert ((gchar *)data_ptr,
671 unicode_chars (data_ptr) * 2,
672 "UTF-16BE", "UTF-16LE", NULL, NULL,
674 if (big_value == NULL) {
675 LOGDEBUG ("%s: Didn't find a valid data string, giving up", __func__);
678 memcpy ((gpointer)data_ptr, big_value,
679 unicode_chars (data_ptr) * 2);
682 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
688 /* Returns a pointer to the byte following the Stringtable block, or
689 * NULL if the data read hits padding. We can't recover from this
690 * because the data length does not include padding bytes, so it's not
691 * possible to just return the start position + length
694 big_up_stringtable_block (gconstpointer data_ptr, version_data *block)
696 guint16 data_len = block->data_len;
697 guint16 string_len = 36; /* length of the StringFileInfo block */
700 /* data_ptr is pointing at an array of StringTable blocks,
701 * with total length (not including alignment padding) of
705 while(string_len < data_len) {
706 /* align on a 32-bit boundary */
709 data_ptr = get_versioninfo_block (data_ptr, block);
710 if (block->data_len == 0) {
711 /* We must have hit padding, so give up
714 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
718 string_len = string_len + block->data_len;
720 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
721 "UTF-16LE", NULL, NULL, NULL);
722 if (big_value == NULL) {
723 LOGDEBUG ("%s: Didn't find a valid string, giving up", __func__);
727 memcpy (block->key, big_value, 16);
730 data_ptr = big_up_string_block (data_ptr, block);
732 if (data_ptr == NULL) {
733 /* Child block hit padding */
734 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
742 /* Follows the data structures and turns all UTF-16 strings from the
743 * LE found in the resource section into UTF-16BE
746 big_up (gconstpointer datablock, guint32 size)
748 gconstpointer data_ptr;
749 gint32 data_len; /* signed to guard against underflow */
752 data_ptr = get_fixedfileinfo_block (datablock, &block);
753 if (data_ptr != NULL) {
754 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
756 /* Byteswap all the fields */
757 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
758 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
759 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
760 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
761 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
762 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
763 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
764 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
765 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
766 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
767 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
769 /* The FFI and header occupies the first 92 bytes
771 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
772 data_len = block.data_len - 92;
774 /* There now follow zero or one StringFileInfo blocks
775 * and zero or one VarFileInfo blocks
777 while (data_len > 0) {
778 /* align on a 32-bit boundary */
781 data_ptr = get_versioninfo_block (data_ptr, &block);
782 if (block.data_len == 0) {
783 /* We must have hit padding, so give
786 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
790 data_len = data_len - block.data_len;
792 if (unicode_string_equals (block.key, "VarFileInfo")) {
793 data_ptr = get_varfileinfo_block (data_ptr,
795 data_ptr = ((guchar *)data_ptr) + block.value_len;
796 } else if (unicode_string_equals (block.key,
798 data_ptr = big_up_stringtable_block (data_ptr,
802 LOGDEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
806 if (data_ptr == NULL) {
807 /* Child block hit padding */
808 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
817 VerQueryValue (gconstpointer datablock, const gunichar2 *subblock, gpointer *buffer, guint32 *len)
819 gchar *subblock_utf8, *lang_utf8 = NULL;
820 gboolean ret = FALSE;
822 gconstpointer data_ptr;
823 gint32 data_len; /* signed to guard against underflow */
824 gboolean want_var = FALSE;
825 gboolean want_string = FALSE;
827 const gunichar2 *string_key = NULL;
828 gpointer string_value = NULL;
829 guint32 string_value_len = 0;
830 gchar *lowercase_lang;
832 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
833 if (subblock_utf8 == NULL) {
837 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
839 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
841 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
842 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
843 lowercase_lang = g_utf8_strdown (lang_utf8, -1);
845 lang_utf8 = lowercase_lang;
846 lowercase_lang = NULL;
847 string_key = subblock + 25;
850 if (!strcmp (subblock_utf8, "\\")) {
851 data_ptr = get_fixedfileinfo_block (datablock, &block);
852 if (data_ptr != NULL) {
853 *buffer = (gpointer)data_ptr;
854 *len = block.value_len;
858 } else if (want_var || want_string) {
859 data_ptr = get_fixedfileinfo_block (datablock, &block);
860 if (data_ptr != NULL) {
861 /* The FFI and header occupies the first 92
864 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
865 data_len = block.data_len - 92;
867 /* There now follow zero or one StringFileInfo
868 * blocks and zero or one VarFileInfo blocks
870 while (data_len > 0) {
871 /* align on a 32-bit boundary */
874 data_ptr = get_versioninfo_block (data_ptr,
876 if (block.data_len == 0) {
877 /* We must have hit padding,
878 * so give up processing now
880 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
884 data_len = data_len - block.data_len;
886 if (unicode_string_equals (block.key, "VarFileInfo")) {
887 data_ptr = get_varfileinfo_block (data_ptr, &block);
889 *buffer = (gpointer)data_ptr;
890 *len = block.value_len;
894 /* Skip over the Var block */
895 data_ptr = ((guchar *)data_ptr) + block.value_len;
897 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
898 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
900 string_value != NULL &&
901 string_value_len != 0) {
902 *buffer = string_value;
903 *len = unicode_chars (string_value) + 1; /* Include trailing null */
909 LOGDEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
913 if (data_ptr == NULL) {
914 /* Child block hit padding */
915 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
927 g_free (subblock_utf8);
932 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
935 gpointer versioninfo;
940 /* This value is unused, but set to zero */
943 file_map = map_pe_file (filename, &map_size, &map_handle);
944 if (file_map == NULL) {
948 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION, 0, &size);
949 if (versioninfo == NULL) {
950 /* Didn't find the resource, so set the return value
956 unmap_pe_file (file_map, map_handle);
962 GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED, guint32 len, gpointer data)
965 gpointer versioninfo;
969 gboolean ret = FALSE;
971 file_map = map_pe_file (filename, &map_size, &map_handle);
972 if (file_map == NULL) {
976 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
978 if (versioninfo != NULL) {
979 /* This could probably process the data so that
980 * VerQueryValue() doesn't have to follow the data
981 * blocks every time. But hey, these functions aren't
982 * likely to appear in many profiles.
984 memcpy (data, versioninfo, len < size?len:size);
987 #if G_BYTE_ORDER == G_BIG_ENDIAN
992 unmap_pe_file (file_map, map_handle);
998 copy_lang (gunichar2 *lang_out, guint32 lang_len, const gchar *text)
1001 int chars = strlen (text);
1004 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1005 g_assert (unitext != NULL);
1007 if (chars < (lang_len - 1)) {
1008 memcpy (lang_out, (gpointer)unitext, chars * 2);
1009 lang_out[chars] = '\0';
1012 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1013 lang_out[lang_len] = '\0';
1023 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1025 int primary, secondary;
1026 const char *name = NULL;
1028 primary = lang & 0x3FF;
1029 secondary = (lang >> 10) & 0x3F;
1035 name = "Process Default Language";
1043 name = "Arabic (Saudi Arabia)";
1046 name = "Arabic (Iraq)";
1049 name = "Arabic (Egypt)";
1052 name = "Arabic (Libya)";
1055 name = "Arabic (Algeria)";
1058 name = "Arabic (Morocco)";
1061 name = "Arabic (Tunisia)";
1064 name = "Arabic (Oman)";
1067 name = "Arabic (Yemen)";
1070 name = "Arabic (Syria)";
1073 name = "Arabic (Jordan)";
1076 name = "Arabic (Lebanon)";
1079 name = "Arabic (Kuwait)";
1082 name = "Arabic (U.A.E.)";
1085 name = "Arabic (Bahrain)";
1088 name = "Arabic (Qatar)";
1095 name = "Bulgarian (Bulgaria)";
1105 name = "Catalan (Spain)";
1116 name = "Chinese (Taiwan)";
1119 name = "Chinese (PRC)";
1122 name = "Chinese (Hong Kong S.A.R.)";
1125 name = "Chinese (Singapore)";
1128 name = "Chinese (Macau S.A.R.)";
1135 name = "Czech (Czech Republic)";
1145 name = "Danish (Denmark)";
1156 name = "German (Germany)";
1159 name = "German (Switzerland)";
1162 name = "German (Austria)";
1165 name = "German (Luxembourg)";
1168 name = "German (Liechtenstein)";
1175 name = "Greek (Greece)";
1186 name = "English (United States)";
1189 name = "English (United Kingdom)";
1192 name = "English (Australia)";
1195 name = "English (Canada)";
1198 name = "English (New Zealand)";
1201 name = "English (Ireland)";
1204 name = "English (South Africa)";
1207 name = "English (Jamaica)";
1210 name = "English (Caribbean)";
1213 name = "English (Belize)";
1216 name = "English (Trinidad and Tobago)";
1219 name = "English (Zimbabwe)";
1222 name = "English (Philippines)";
1225 name = "English (India)";
1228 name = "English (Malaysia)";
1231 name = "English (Singapore)";
1238 name = "Spanish (Spain)";
1241 name = "Spanish (Traditional Sort)";
1244 name = "Spanish (Mexico)";
1247 name = "Spanish (International Sort)";
1250 name = "Spanish (Guatemala)";
1253 name = "Spanish (Costa Rica)";
1256 name = "Spanish (Panama)";
1259 name = "Spanish (Dominican Republic)";
1262 name = "Spanish (Venezuela)";
1265 name = "Spanish (Colombia)";
1268 name = "Spanish (Peru)";
1271 name = "Spanish (Argentina)";
1274 name = "Spanish (Ecuador)";
1277 name = "Spanish (Chile)";
1280 name = "Spanish (Uruguay)";
1283 name = "Spanish (Paraguay)";
1286 name = "Spanish (Bolivia)";
1289 name = "Spanish (El Salvador)";
1292 name = "Spanish (Honduras)";
1295 name = "Spanish (Nicaragua)";
1298 name = "Spanish (Puerto Rico)";
1301 name = "Spanish (United States)";
1308 name = "Finnish (Finland)";
1319 name = "French (France)";
1322 name = "French (Belgium)";
1325 name = "French (Canada)";
1328 name = "French (Switzerland)";
1331 name = "French (Luxembourg)";
1334 name = "French (Monaco)";
1341 name = "Hebrew (Israel)";
1351 name = "Hungarian (Hungary)";
1361 name = "Icelandic (Iceland)";
1372 name = "Italian (Italy)";
1375 name = "Italian (Switzerland)";
1382 name = "Japanese (Japan)";
1392 name = "Korean (Korea)";
1403 name = "Dutch (Netherlands)";
1406 name = "Dutch (Belgium)";
1414 name = "Norwegian (Bokmal)";
1417 name = "Norwegian (Nynorsk)";
1424 name = "Polish (Poland)";
1435 name = "Portuguese (Brazil)";
1438 name = "Portuguese (Portugal)";
1445 name = "Romansh (Switzerland)";
1452 name = "Romanian (Romania)";
1462 name = "Russian (Russia)";
1472 name = "Croatian (Croatia)";
1478 name = "Serbian (Latin)";
1481 name = "Serbian (Cyrillic)";
1484 name = "Croatian (Bosnia and Herzegovina)";
1487 name = "Bosnian (Latin, Bosnia and Herzegovina)";
1490 name = "Serbian (Latin, Bosnia and Herzegovina)";
1493 name = "Serbian (Cyrillic, Bosnia and Herzegovina)";
1496 name = "Bosnian (Cyrillic, Bosnia and Herzegovina)";
1503 name = "Slovak (Slovakia)";
1513 name = "Albanian (Albania)";
1523 name = "Swedish (Sweden)";
1529 name = "Swedish (Finland)";
1536 name = "Thai (Thailand)";
1546 name = "Turkish (Turkey)";
1556 name = "Urdu (Islamic Republic of Pakistan)";
1566 name = "Indonesian (Indonesia)";
1569 name = "Indonesian";
1576 name = "Ukrainian (Ukraine)";
1586 name = "Belarusian (Belarus)";
1589 name = "Belarusian";
1596 name = "Slovenian (Slovenia)";
1606 name = "Estonian (Estonia)";
1616 name = "Latvian (Latvia)";
1626 name = "Lithuanian (Lithuania)";
1629 name = "Lithuanian";
1636 name = "Tajik (Tajikistan)";
1643 name = "Farsi (Iran)";
1653 name = "Vietnamese (Viet Nam)";
1656 name = "Vietnamese";
1663 name = "Armenian (Armenia)";
1673 name = "Azeri (Latin) (Azerbaijan)";
1676 name = "Azeri (Latin)";
1679 name = "Azeri (Cyrillic)";
1686 name = "Basque (Spain)";
1696 name = "Upper Sorbian (Germany)";
1699 name = "Lower Sorbian (Germany)";
1706 name = "FYRO Macedonian (Former Yugoslav Republic of Macedonia)";
1709 name = "FYRO Macedonian";
1716 name = "Tswana (South Africa)";
1726 name = "Xhosa (South Africa)";
1736 name = "Zulu (South Africa)";
1746 name = "Afrikaans (South Africa)";
1756 name = "Georgian (Georgia)";
1766 name = "Faroese (Faroe Islands)";
1776 name = "Hindi (India)";
1786 name = "Maltese (Malta)";
1796 name = "Sami (Northern) (Norway)";
1799 name = "Sami, Northern (Norway)";
1802 name = "Sami, Northern (Sweden)";
1805 name = "Sami, Northern (Finland)";
1808 name = "Sami, Lule (Norway)";
1811 name = "Sami, Lule (Sweden)";
1814 name = "Sami, Southern (Norway)";
1817 name = "Sami, Southern (Sweden)";
1820 name = "Sami, Skolt (Finland)";
1823 name = "Sami, Inari (Finland)";
1830 name = "Irish (Ireland)";
1838 name = "Malay (Malaysia)";
1841 name = "Malay (Brunei Darussalam)";
1848 name = "Kazakh (Kazakhstan)";
1858 name = "Kyrgyz (Kyrgyzstan)";
1861 name = "Kyrgyz (Cyrillic)";
1868 name = "Swahili (Kenya)";
1878 name = "Turkmen (Turkmenistan)";
1885 name = "Uzbek (Latin) (Uzbekistan)";
1888 name = "Uzbek (Latin)";
1891 name = "Uzbek (Cyrillic)";
1898 name = "Tatar (Russia)";
1909 name = "Bengali (India)";
1916 name = "Punjabi (India)";
1926 name = "Gujarati (India)";
1936 name = "Tamil (India)";
1946 name = "Telugu (India)";
1956 name = "Kannada (India)";
1967 name = "Malayalam (India)";
1974 name = "Assamese (India)";
1981 name = "Marathi (India)";
1991 name = "Sanskrit (India)";
2001 name = "Mongolian (Mongolia)";
2004 name = "Mongolian (Cyrillic)";
2007 name = "Mongolian (PRC)";
2014 name = "Tibetan (PRC)";
2017 name = "Tibetan (Bhutan)";
2024 name = "Welsh (United Kingdom)";
2034 name = "Khmer (Cambodia)";
2041 name = "Lao (Lao PDR)";
2048 name = "Galician (Spain)";
2058 name = "Konkani (India)";
2068 name = "Syriac (Syria)";
2078 name = "Sinhala (Sri Lanka)";
2085 name = "Inuktitut (Syllabics, Canada)";
2088 name = "Inuktitut (Latin, Canada)";
2095 name = "Amharic (Ethiopia)";
2102 name = "Tamazight (Algeria, Latin)";
2109 name = "Nepali (Nepal)";
2116 name = "Frisian (Netherlands)";
2123 name = "Pashto (Afghanistan)";
2130 name = "Filipino (Philippines)";
2137 name = "Divehi (Maldives)";
2147 name = "Hausa (Nigeria, Latin)";
2154 name = "Yoruba (Nigeria)";
2162 name = "Quechua (Bolivia)";
2165 name = "Quechua (Ecuador)";
2168 name = "Quechua (Peru)";
2175 name = "Northern Sotho (South Africa)";
2178 name = "Northern Sotho";
2185 name = "Bashkir (Russia)";
2192 name = "Luxembourgish (Luxembourg)";
2199 name = "Greenlandic (Greenland)";
2213 name = "Mapudungun (Chile)";
2220 name = "Mohawk (Mohawk)";
2227 name = "Breton (France)";
2234 name = "Invariant Language (Invariant Country)";
2241 name = "Uighur (PRC)";
2248 name = "Maori (New Zealand)";
2258 name = "Corsican (France)";
2265 name = "Alsatian (France)";
2272 name = "Yakut (Russia)";
2279 name = "K'iche (Guatemala)";
2286 name = "Kinyarwanda (Rwanda)";
2293 name = "Wolof (Senegal)";
2300 name = "Dari (Afghanistan)";
2306 name = "Language Neutral";
2311 name = "Language Neutral";
2313 return copy_lang (lang_out, lang_len, name);