2 * versioninfo.c: Version information support
5 * Dick Porter (dick@ximian.com)
7 * (C) 2007 Novell, Inc.
15 #include <sys/types.h>
21 #include <mono/io-layer/wapi.h>
22 #include <mono/io-layer/wapi-private.h>
23 #include <mono/io-layer/versioninfo.h>
24 #include <mono/io-layer/io-portability.h>
25 #include <mono/io-layer/error.h>
26 #include <mono/utils/strenc.h>
30 static WapiImageSectionHeader *get_enclosing_section_header (guint32 rva, WapiImageNTHeaders *nt_headers)
32 WapiImageSectionHeader *section = IMAGE_FIRST_SECTION (nt_headers);
35 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
36 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
38 size = GUINT32_FROM_LE (section->SizeOfRawData);
41 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
42 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
50 static gpointer get_ptr_from_rva (guint32 rva, WapiImageNTHeaders *ntheaders,
53 WapiImageSectionHeader *section_header;
56 section_header = get_enclosing_section_header (rva, ntheaders);
57 if (section_header == NULL) {
61 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
62 GUINT32_FROM_LE (section_header->PointerToRawData));
64 return((guint8 *)file_map + rva - delta);
67 static gpointer scan_resource_dir (WapiImageResourceDirectory *root,
68 WapiImageNTHeaders *nt_headers,
70 WapiImageResourceDirectoryEntry *entry,
71 int level, guint32 res_id, guint32 lang_id,
74 WapiImageResourceDirectoryEntry swapped_entry;
75 gboolean is_string, is_dir;
76 guint32 name_offset, dir_offset, data_offset;
78 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
79 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
81 is_string = swapped_entry.NameIsString;
82 is_dir = swapped_entry.DataIsDirectory;
83 name_offset = swapped_entry.NameOffset;
84 dir_offset = swapped_entry.OffsetToDirectory;
85 data_offset = swapped_entry.OffsetToData;
88 /* Normally holds a directory entry for each type of
91 if ((is_string == FALSE &&
92 name_offset != res_id) ||
93 (is_string == TRUE)) {
96 } else if (level == 1) {
97 /* Normally holds a directory entry for each resource
100 } else if (level == 2) {
101 /* Normally holds a directory entry for each language
103 if ((is_string == FALSE &&
104 name_offset != lang_id &&
106 (is_string == TRUE)) {
110 g_assert_not_reached ();
113 if (is_dir == TRUE) {
114 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
115 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
118 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
120 for (i = 0; i < entries; i++) {
121 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
124 ret = scan_resource_dir (root, nt_headers, file_map,
125 sub_entry, level + 1, res_id,
134 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
135 *size = GUINT32_FROM_LE (data_entry->Size);
137 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
141 static gpointer find_pe_file_resources (gpointer file_map, guint32 map_size,
142 guint32 res_id, guint32 lang_id,
145 WapiImageDosHeader *dos_header;
146 WapiImageNTHeaders *nt_headers;
147 WapiImageResourceDirectory *resource_dir;
148 WapiImageResourceDirectoryEntry *resource_dir_entry;
149 guint32 resource_rva, entries, i;
152 dos_header = (WapiImageDosHeader *)file_map;
153 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
155 g_message ("%s: Bad dos signature 0x%x", __func__,
156 dos_header->e_magic);
159 SetLastError (ERROR_INVALID_DATA);
163 if (map_size < sizeof(WapiImageNTHeaders) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
165 g_message ("%s: File is too small: %d", __func__, map_size);
168 SetLastError (ERROR_BAD_LENGTH);
172 nt_headers = (WapiImageNTHeaders *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
173 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
175 g_message ("%s: Bad NT signature 0x%x", __func__,
176 nt_headers->Signature);
179 SetLastError (ERROR_INVALID_DATA);
183 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
184 /* Do 64-bit stuff */
185 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
187 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
190 if (resource_rva == 0) {
192 g_message ("%s: No resources in file!", __func__);
194 SetLastError (ERROR_INVALID_DATA);
198 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, nt_headers, file_map);
199 if (resource_dir == NULL) {
201 g_message ("%s: Can't find resource directory", __func__);
203 SetLastError (ERROR_INVALID_DATA);
207 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
208 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
210 for (i = 0; i < entries; i++) {
211 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
212 ret = scan_resource_dir (resource_dir, nt_headers, file_map,
213 direntry, 0, res_id, lang_id, size);
222 static gpointer map_pe_file (gunichar2 *filename, guint32 *map_size)
229 /* According to the MSDN docs, a search path is applied to
230 * filename. FIXME: implement this, for now just pass it
234 filename_ext = mono_unicode_to_external (filename);
235 if (filename_ext == NULL) {
237 g_message ("%s: unicode conversion returned NULL", __func__);
240 SetLastError (ERROR_INVALID_NAME);
244 fd = _wapi_open (filename_ext, O_RDONLY, 0);
247 g_message ("%s: Error opening file %s: %s", __func__,
248 filename_ext, strerror (errno));
251 SetLastError (_wapi_get_win32_file_error (errno));
252 g_free (filename_ext);
257 if (fstat (fd, &statbuf) == -1) {
259 g_message ("%s: Error stat()ing file %s: %s", __func__,
260 filename_ext, strerror (errno));
263 SetLastError (_wapi_get_win32_file_error (errno));
264 g_free (filename_ext);
268 *map_size = statbuf.st_size;
270 /* Check basic file size */
271 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
273 g_message ("%s: File %s is too small: %ld", __func__,
274 filename_ext, statbuf.st_size);
277 SetLastError (ERROR_BAD_LENGTH);
278 g_free (filename_ext);
283 file_map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
284 if (file_map == MAP_FAILED) {
286 g_message ("%s: Error mmap()int file %s: %s", __func__,
287 filename_ext, strerror (errno));
290 SetLastError (_wapi_get_win32_file_error (errno));
291 g_free (filename_ext);
296 /* Don't need the fd any more */
302 static void unmap_pe_file (gpointer file_map, guint32 map_size)
304 munmap (file_map, map_size);
307 static guint32 unicode_chars (const gunichar2 *str)
312 if (str[len] == '\0') {
319 static gboolean unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
321 while (*str1 && *str2) {
322 if (*str1 != *str2) {
329 return(*str1 == *str2);
332 /* compare a little-endian null-terminated utf16 string and a normal string.
333 * Can be used only for ascii or latin1 chars.
335 static gboolean unicode_string_equals (const gunichar2 *str1, const gchar *str2)
337 while (*str1 && *str2) {
338 if (GUINT16_TO_LE (*str1) != *str2) {
345 return(*str1 == *str2);
356 /* Returns a pointer to the value data, because there's no way to know
357 * how big that data is (value_len is set to zero for most blocks :-( )
359 static gconstpointer get_versioninfo_block (gconstpointer data,
362 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
363 data = (char *)data + sizeof(guint16);
364 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
365 data = (char *)data + sizeof(guint16);
367 /* No idea what the type is supposed to indicate */
368 block->type = GUINT16_FROM_LE (*((guint16 *)data));
369 data = (char *)data + sizeof(guint16);
370 block->key = ((gunichar2 *)data);
372 /* Skip over the key (including the terminator) */
373 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
375 /* align on a 32-bit boundary */
376 data = (gpointer)((char *)data + 3);
377 data = (gpointer)((char *)data - (GPOINTER_TO_INT (data) & 3));
382 static gconstpointer get_fixedfileinfo_block (gconstpointer data,
385 gconstpointer data_ptr;
386 gint32 data_len; /* signed to guard against underflow */
387 WapiFixedFileInfo *ffi;
389 data_ptr = get_versioninfo_block (data, block);
390 data_len = block->data_len;
392 if (block->value_len != sizeof(WapiFixedFileInfo)) {
394 g_message ("%s: FIXEDFILEINFO size mismatch", __func__);
399 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
401 g_message ("%s: VS_VERSION_INFO mismatch", __func__);
406 ffi = ((WapiFixedFileInfo *)data_ptr);
407 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
408 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
410 g_message ("%s: FIXEDFILEINFO bad signature", __func__);
418 static gconstpointer get_varfileinfo_block (gconstpointer data_ptr,
421 /* data is pointing at a Var block
423 data_ptr = get_versioninfo_block (data_ptr, block);
428 static gconstpointer get_string_block (gconstpointer data_ptr,
429 const gunichar2 *string_key,
430 gpointer *string_value,
431 guint32 *string_value_len,
434 guint16 data_len = block->data_len;
435 guint16 string_len = 28; /* Length of the StringTable block */
436 char *orig_data_ptr = (char *)data_ptr - 28;
438 /* data_ptr is pointing at an array of one or more String blocks
439 * with total length (not including alignment padding) of
442 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
443 /* align on a 32-bit boundary */
444 data_ptr = (gpointer)((char *)data_ptr + 3);
445 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
447 data_ptr = get_versioninfo_block (data_ptr, block);
448 if (block->data_len == 0) {
449 /* We must have hit padding, so give up
453 g_message ("%s: Hit 0-length block, giving up",
459 string_len = string_len + block->data_len;
461 if (string_key != NULL &&
462 string_value != NULL &&
463 string_value_len != NULL &&
464 unicode_compare (string_key, block->key) == TRUE) {
465 *string_value = (gpointer)data_ptr;
466 *string_value_len = block->value_len;
469 /* Skip over the value */
470 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
476 /* Returns a pointer to the byte following the Stringtable block, or
477 * NULL if the data read hits padding. We can't recover from this
478 * because the data length does not include padding bytes, so it's not
479 * possible to just return the start position + length
481 * If lang == NULL it means we're just stepping through this block
483 static gconstpointer get_stringtable_block (gconstpointer data_ptr,
485 const gunichar2 *string_key,
486 gpointer *string_value,
487 guint32 *string_value_len,
490 guint16 data_len = block->data_len;
491 guint16 string_len = 36; /* length of the StringFileInfo block */
494 /* data_ptr is pointing at an array of StringTable blocks,
495 * with total length (not including alignment padding) of
499 while(string_len < data_len) {
500 /* align on a 32-bit boundary */
501 data_ptr = (gpointer)((char *)data_ptr + 3);
502 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
504 data_ptr = get_versioninfo_block (data_ptr, block);
505 if (block->data_len == 0) {
506 /* We must have hit padding, so give up
510 g_message ("%s: Hit 0-length block, giving up",
516 string_len = string_len + block->data_len;
518 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
519 if (found_lang == NULL) {
521 g_message ("%s: Didn't find a valid language key, giving up", __func__);
526 g_strdown (found_lang);
528 if (lang != NULL && !strcmp (found_lang, lang)) {
529 /* Got the one we're interested in */
530 data_ptr = get_string_block (data_ptr, string_key,
532 string_value_len, block);
534 data_ptr = get_string_block (data_ptr, NULL, NULL,
540 if (data_ptr == NULL) {
541 /* Child block hit padding */
543 g_message ("%s: Child block hit 0-length block, giving up", __func__);
552 #if G_BYTE_ORDER == G_BIG_ENDIAN
553 static gconstpointer big_up_string_block (gconstpointer data_ptr,
556 guint16 data_len = block->data_len;
557 guint16 string_len = 28; /* Length of the StringTable block */
559 char *orig_data_ptr = (char *)data_ptr - 28;
561 /* data_ptr is pointing at an array of one or more String
562 * blocks with total length (not including alignment padding)
565 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
566 /* align on a 32-bit boundary */
567 data_ptr = (gpointer)((char *)data_ptr + 3);
568 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
570 data_ptr = get_versioninfo_block (data_ptr, block);
571 if (block->data_len == 0) {
572 /* We must have hit padding, so give up
576 g_message ("%s: Hit 0-length block, giving up",
582 string_len = string_len + block->data_len;
584 big_value = g_convert ((gchar *)block->key,
585 unicode_chars (block->key) * 2,
586 "UTF-16BE", "UTF-16LE", NULL, NULL,
588 if (big_value == NULL) {
590 g_message ("%s: Didn't find a valid string, giving up",
596 /* The swapped string should be exactly the same
597 * length as the original little-endian one, but only
598 * copy the number of original chars just to be on the
601 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
604 big_value = g_convert ((gchar *)data_ptr,
605 unicode_chars (data_ptr) * 2,
606 "UTF-16BE", "UTF-16LE", NULL, NULL,
608 if (big_value == NULL) {
610 g_message ("%s: Didn't find a valid data string, giving up", __func__);
614 memcpy ((gpointer)data_ptr, big_value,
615 unicode_chars (data_ptr) * 2);
618 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
624 /* Returns a pointer to the byte following the Stringtable block, or
625 * NULL if the data read hits padding. We can't recover from this
626 * because the data length does not include padding bytes, so it's not
627 * possible to just return the start position + length
629 static gconstpointer big_up_stringtable_block (gconstpointer data_ptr,
632 guint16 data_len = block->data_len;
633 guint16 string_len = 36; /* length of the StringFileInfo block */
636 /* data_ptr is pointing at an array of StringTable blocks,
637 * with total length (not including alignment padding) of
641 while(string_len < data_len) {
642 /* align on a 32-bit boundary */
643 data_ptr = (gpointer)((char *)data_ptr + 3);
644 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
646 data_ptr = get_versioninfo_block (data_ptr, block);
647 if (block->data_len == 0) {
648 /* We must have hit padding, so give up
652 g_message ("%s: Hit 0-length block, giving up",
658 string_len = string_len + block->data_len;
660 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
661 "UTF-16LE", NULL, NULL, NULL);
662 if (big_value == NULL) {
664 g_message ("%s: Didn't find a valid string, giving up",
670 memcpy (block->key, big_value, 16);
673 data_ptr = big_up_string_block (data_ptr, block);
675 if (data_ptr == NULL) {
676 /* Child block hit padding */
678 g_message ("%s: Child block hit 0-length block, giving up", __func__);
687 /* Follows the data structures and turns all UTF-16 strings from the
688 * LE found in the resource section into UTF-16BE
690 static void big_up (gconstpointer datablock, guint32 size)
692 gconstpointer data_ptr;
693 gint32 data_len; /* signed to guard against underflow */
696 data_ptr = get_fixedfileinfo_block (datablock, &block);
697 if (data_ptr != NULL) {
698 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
700 /* Byteswap all the fields */
701 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
702 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
703 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
704 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
705 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
706 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
707 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
708 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
709 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
710 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
711 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
713 /* The FFI and header occupies the first 92 bytes
715 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
716 data_len = block.data_len - 92;
718 /* There now follow zero or one StringFileInfo blocks
719 * and zero or one VarFileInfo blocks
721 while (data_len > 0) {
722 /* align on a 32-bit boundary */
723 data_ptr = (gpointer)((char *)data_ptr + 3);
724 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
726 data_ptr = get_versioninfo_block (data_ptr, &block);
727 if (block.data_len == 0) {
728 /* We must have hit padding, so give
732 g_message ("%s: Hit 0-length block, giving up",
738 data_len = data_len - block.data_len;
740 if (unicode_string_equals (block.key, "VarFileInfo")) {
741 data_ptr = get_varfileinfo_block (data_ptr,
743 data_ptr = ((guchar *)data_ptr) + block.value_len;
744 } else if (unicode_string_equals (block.key,
746 data_ptr = big_up_stringtable_block (data_ptr,
751 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
756 if (data_ptr == NULL) {
757 /* Child block hit padding */
759 g_message ("%s: Child block hit 0-length block, giving up", __func__);
768 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
769 gpointer *buffer, guint32 *len)
771 gchar *subblock_utf8, *lang_utf8 = NULL;
772 gboolean ret = FALSE;
774 gconstpointer data_ptr;
775 gint32 data_len; /* signed to guard against underflow */
776 gboolean want_var = FALSE;
777 gboolean want_string = FALSE;
779 const gunichar2 *string_key = NULL;
780 gpointer string_value = NULL;
781 guint32 string_value_len = 0;
783 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
784 if (subblock_utf8 == NULL) {
788 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
790 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
792 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
793 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
794 g_strdown (lang_utf8);
795 string_key = subblock + 25;
798 if (!strcmp (subblock_utf8, "\\")) {
799 data_ptr = get_fixedfileinfo_block (datablock, &block);
800 if (data_ptr != NULL) {
801 *buffer = (gpointer)data_ptr;
802 *len = block.value_len;
806 } else if (want_var || want_string) {
807 data_ptr = get_fixedfileinfo_block (datablock, &block);
808 if (data_ptr != NULL) {
809 /* The FFI and header occupies the first 92
812 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
813 data_len = block.data_len - 92;
815 /* There now follow zero or one StringFileInfo
816 * blocks and zero or one VarFileInfo blocks
818 while (data_len > 0) {
819 /* align on a 32-bit boundary */
820 data_ptr = (gpointer)((char *)data_ptr + 3);
821 data_ptr = (gpointer)((char *)data_ptr - (GPOINTER_TO_INT (data_ptr) & 3));
823 data_ptr = get_versioninfo_block (data_ptr,
825 if (block.data_len == 0) {
826 /* We must have hit padding,
827 * so give up processing now
830 g_message ("%s: Hit 0-length block, giving up", __func__);
835 data_len = data_len - block.data_len;
837 if (unicode_string_equals (block.key, "VarFileInfo")) {
838 data_ptr = get_varfileinfo_block (data_ptr, &block);
840 *buffer = (gpointer)data_ptr;
841 *len = block.value_len;
845 /* Skip over the Var block */
846 data_ptr = ((guchar *)data_ptr) + block.value_len;
848 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
849 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
851 string_value != NULL &&
852 string_value_len != 0) {
853 *buffer = string_value;
854 *len = unicode_chars (string_value) + 1; /* Include trailing null */
861 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
866 if (data_ptr == NULL) {
867 /* Child block hit padding */
869 g_message ("%s: Child block hit 0-length block, giving up", __func__);
882 g_free (subblock_utf8);
886 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
889 gpointer versioninfo;
893 /* This value is unused, but set to zero */
896 file_map = map_pe_file (filename, &map_size);
897 if (file_map == NULL) {
901 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
903 if (versioninfo == NULL) {
904 /* Didn't find the resource, so set the return value
910 unmap_pe_file (file_map, map_size);
915 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
916 guint32 len, gpointer data)
919 gpointer versioninfo;
922 gboolean ret = FALSE;
924 file_map = map_pe_file (filename, &map_size);
925 if (file_map == NULL) {
929 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
931 if (versioninfo != NULL) {
932 /* This could probably process the data so that
933 * VerQueryValue() doesn't have to follow the data
934 * blocks every time. But hey, these functions aren't
935 * likely to appear in many profiles.
937 memcpy (data, versioninfo, len < size?len:size);
940 #if G_BYTE_ORDER == G_BIG_ENDIAN
945 unmap_pe_file (file_map, map_size);
950 static guint32 copy_lang (gunichar2 *lang_out, guint32 lang_len,
954 int chars = strlen (text);
957 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
958 g_assert (unitext != NULL);
960 if (chars < (lang_len - 1)) {
961 memcpy (lang_out, (gpointer)unitext, chars * 2);
962 lang_out[chars] = '\0';
965 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
966 lang_out[lang_len] = '\0';
975 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
977 int primary, secondary;
979 primary = lang & 0x3FF;
980 secondary = (lang >> 10) & 0x3F;
986 return(copy_lang (lang_out, lang_len, "Process Default Language"));
994 return(copy_lang (lang_out, lang_len, "Arabic (Saudi Arabia)"));
997 return(copy_lang (lang_out, lang_len, "Arabic (Iraq)"));
1000 return(copy_lang (lang_out, lang_len, "Arabic (Egypt)"));
1003 return(copy_lang (lang_out, lang_len, "Arabic (Libya)"));
1006 return(copy_lang (lang_out, lang_len, "Arabic (Algeria)"));
1009 return(copy_lang (lang_out, lang_len, "Arabic (Morocco)"));
1012 return(copy_lang (lang_out, lang_len, "Arabic (Tunisia)"));
1015 return(copy_lang (lang_out, lang_len, "Arabic (Oman)"));
1018 return(copy_lang (lang_out, lang_len, "Arabic (Yemen)"));
1021 return(copy_lang (lang_out, lang_len, "Arabic (Syria)"));
1024 return(copy_lang (lang_out, lang_len, "Arabic (Jordan)"));
1027 return(copy_lang (lang_out, lang_len, "Arabic (Lebanon)"));
1030 return(copy_lang (lang_out, lang_len, "Arabic (Kuwait)"));
1033 return(copy_lang (lang_out, lang_len, "Arabic (U.A.E.)"));
1036 return(copy_lang (lang_out, lang_len, "Arabic (Bahrain)"));
1039 return(copy_lang (lang_out, lang_len, "Arabic (Qatar)"));
1046 return(copy_lang (lang_out, lang_len, "Bulgarian (Bulgaria)"));
1049 return(copy_lang (lang_out, lang_len, "Bulgarian"));
1056 return(copy_lang (lang_out, lang_len, "Catalan (Spain)"));
1059 return(copy_lang (lang_out, lang_len, "Catalan"));
1067 return(copy_lang (lang_out, lang_len, "Chinese (Taiwan)"));
1070 return(copy_lang (lang_out, lang_len, "Chinese (PRC)"));
1073 return(copy_lang (lang_out, lang_len, "Chinese (Hong Kong S.A.R.)"));
1076 return(copy_lang (lang_out, lang_len, "Chinese (Singapore)"));
1079 return(copy_lang (lang_out, lang_len, "Chinese (Macau S.A.R.)"));
1086 return(copy_lang (lang_out, lang_len, "Czech (Czech Republic)"));
1089 return(copy_lang (lang_out, lang_len, "Czech"));
1096 return(copy_lang (lang_out, lang_len, "Danish (Denmark)"));
1099 return(copy_lang (lang_out, lang_len, "Danish"));
1107 return(copy_lang (lang_out, lang_len, "German (Germany)"));
1110 return(copy_lang (lang_out, lang_len, "German (Switzerland)"));
1113 return(copy_lang (lang_out, lang_len, "German (Austria)"));
1116 return(copy_lang (lang_out, lang_len, "German (Luxembourg)"));
1119 return(copy_lang (lang_out, lang_len, "German (Liechtenstein)"));
1126 return(copy_lang (lang_out, lang_len, "Greek (Greece)"));
1129 return(copy_lang (lang_out, lang_len, "Greek"));
1137 return(copy_lang (lang_out, lang_len, "English (United States)"));
1140 return(copy_lang (lang_out, lang_len, "English (United Kingdom)"));
1143 return(copy_lang (lang_out, lang_len, "English (Australia)"));
1146 return(copy_lang (lang_out, lang_len, "English (Canada)"));
1149 return(copy_lang (lang_out, lang_len, "English (New Zealand)"));
1152 return(copy_lang (lang_out, lang_len, "English (Ireland)"));
1155 return(copy_lang (lang_out, lang_len, "English (South Africa)"));
1158 return(copy_lang (lang_out, lang_len, "English (Jamaica)"));
1161 return(copy_lang (lang_out, lang_len, "English (Caribbean)"));
1164 return(copy_lang (lang_out, lang_len, "English (Belize)"));
1167 return(copy_lang (lang_out, lang_len, "English (Trinidad and Tobago)"));
1170 return(copy_lang (lang_out, lang_len, "English (Zimbabwe)"));
1173 return(copy_lang (lang_out, lang_len, "English (Philippines)"));
1176 return(copy_lang (lang_out, lang_len, "English (India)"));
1179 return(copy_lang (lang_out, lang_len, "English (Malaysia)"));
1182 return(copy_lang (lang_out, lang_len, "English (Singapore)"));
1189 return(copy_lang (lang_out, lang_len, "Spanish (Spain)"));
1192 return(copy_lang (lang_out, lang_len, "Spanish (Traditional Sort)"));
1195 return(copy_lang (lang_out, lang_len, "Spanish (Mexico)"));
1198 return(copy_lang (lang_out, lang_len, "Spanish (International Sort)"));
1201 return(copy_lang (lang_out, lang_len, "Spanish (Guatemala)"));
1204 return(copy_lang (lang_out, lang_len, "Spanish (Costa Rica)"));
1207 return(copy_lang (lang_out, lang_len, "Spanish (Panama)"));
1210 return(copy_lang (lang_out, lang_len, "Spanish (Dominican Republic)"));
1213 return(copy_lang (lang_out, lang_len, "Spanish (Venezuela)"));
1216 return(copy_lang (lang_out, lang_len, "Spanish (Colombia)"));
1219 return(copy_lang (lang_out, lang_len, "Spanish (Peru)"));
1222 return(copy_lang (lang_out, lang_len, "Spanish (Argentina)"));
1225 return(copy_lang (lang_out, lang_len, "Spanish (Ecuador)"));
1228 return(copy_lang (lang_out, lang_len, "Spanish (Chile)"));
1231 return(copy_lang (lang_out, lang_len, "Spanish (Uruguay)"));
1234 return(copy_lang (lang_out, lang_len, "Spanish (Paraguay)"));
1237 return(copy_lang (lang_out, lang_len, "Spanish (Bolivia)"));
1240 return(copy_lang (lang_out, lang_len, "Spanish (El Salvador)"));
1243 return(copy_lang (lang_out, lang_len, "Spanish (Honduras)"));
1246 return(copy_lang (lang_out, lang_len, "Spanish (Nicaragua)"));
1249 return(copy_lang (lang_out, lang_len, "Spanish (Puerto Rico)"));
1252 return(copy_lang (lang_out, lang_len, "Spanish (United States)"));
1259 return(copy_lang (lang_out, lang_len, "Finnish (Finland)"));
1262 return(copy_lang (lang_out, lang_len, "Finnish"));
1270 return(copy_lang (lang_out, lang_len, "French (France)"));
1273 return(copy_lang (lang_out, lang_len, "French (Belgium)"));
1276 return(copy_lang (lang_out, lang_len, "French (Canada)"));
1279 return(copy_lang (lang_out, lang_len, "French (Switzerland)"));
1282 return(copy_lang (lang_out, lang_len, "French (Luxembourg)"));
1285 return(copy_lang (lang_out, lang_len, "French (Monaco)"));
1292 return(copy_lang (lang_out, lang_len, "Hebrew (Israel)"));
1295 return(copy_lang (lang_out, lang_len, "Hebrew"));
1302 return(copy_lang (lang_out, lang_len, "Hungarian (Hungary)"));
1305 return(copy_lang (lang_out, lang_len, "Hungarian"));
1312 return(copy_lang (lang_out, lang_len, "Icelandic (Iceland)"));
1315 return(copy_lang (lang_out, lang_len, "Icelandic"));
1323 return(copy_lang (lang_out, lang_len, "Italian (Italy)"));
1326 return(copy_lang (lang_out, lang_len, "Italian (Switzerland)"));
1333 return(copy_lang (lang_out, lang_len, "Japanese (Japan)"));
1336 return(copy_lang (lang_out, lang_len, "Japanese"));
1343 return(copy_lang (lang_out, lang_len, "Korean (Korea)"));
1346 return(copy_lang (lang_out, lang_len, "Korean"));
1354 return(copy_lang (lang_out, lang_len, "Dutch (Netherlands)"));
1357 return(copy_lang (lang_out, lang_len, "Dutch (Belgium)"));
1365 return(copy_lang (lang_out, lang_len, "Norwegian (Bokmal)"));
1368 return(copy_lang (lang_out, lang_len, "Norwegian (Nynorsk)"));
1375 return(copy_lang (lang_out, lang_len, "Polish (Poland)"));
1378 return(copy_lang (lang_out, lang_len, "Polish"));
1386 return(copy_lang (lang_out, lang_len, "Portuguese (Brazil)"));
1389 return(copy_lang (lang_out, lang_len, "Portuguese (Portugal)"));
1396 return(copy_lang (lang_out, lang_len, "Romansh (Switzerland)"));
1403 return(copy_lang (lang_out, lang_len, "Romanian (Romania)"));
1406 return(copy_lang (lang_out, lang_len, "Romanian"));
1413 return(copy_lang (lang_out, lang_len, "Russian (Russia)"));
1416 return(copy_lang (lang_out, lang_len, "Russian"));
1423 return(copy_lang (lang_out, lang_len, "Croatian (Croatia)"));
1426 return(copy_lang (lang_out, lang_len, "Croatian"));
1429 return(copy_lang (lang_out, lang_len, "Serbian (Latin)"));
1432 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic)"));
1435 return(copy_lang (lang_out, lang_len, "Croatian (Bosnia and Herzegovina)"));
1438 return(copy_lang (lang_out, lang_len, "Bosnian (Latin, Bosnia and Herzegovina)"));
1441 return(copy_lang (lang_out, lang_len, "Serbian (Latin, Bosnia and Herzegovina)"));
1444 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic, Bosnia and Herzegovina)"));
1447 return(copy_lang (lang_out, lang_len, "Bosnian (Cyrillic, Bosnia and Herzegovina)"));
1454 return(copy_lang (lang_out, lang_len, "Slovak (Slovakia)"));
1457 return(copy_lang (lang_out, lang_len, "Slovak"));
1464 return(copy_lang (lang_out, lang_len, "Albanian (Albania)"));
1467 return(copy_lang (lang_out, lang_len, "Albanian"));
1474 return(copy_lang (lang_out, lang_len, "Swedish (Sweden)"));
1477 return(copy_lang (lang_out, lang_len, "Swedish"));
1480 return(copy_lang (lang_out, lang_len, "Swedish (Finland)"));
1487 return(copy_lang (lang_out, lang_len, "Thai (Thailand)"));
1490 return(copy_lang (lang_out, lang_len, "Thai"));
1497 return(copy_lang (lang_out, lang_len, "Turkish (Turkey)"));
1500 return(copy_lang (lang_out, lang_len, "Turkish"));
1507 return(copy_lang (lang_out, lang_len, "Urdu (Islamic Republic of Pakistan)"));
1510 return(copy_lang (lang_out, lang_len, "Urdu"));
1517 return(copy_lang (lang_out, lang_len, "Indonesian (Indonesia)"));
1520 return(copy_lang (lang_out, lang_len, "Indonesian"));
1527 return(copy_lang (lang_out, lang_len, "Ukrainian (Ukraine)"));
1530 return(copy_lang (lang_out, lang_len, "Ukrainian"));
1537 return(copy_lang (lang_out, lang_len, "Belarusian (Belarus)"));
1540 return(copy_lang (lang_out, lang_len, "Belarusian"));
1547 return(copy_lang (lang_out, lang_len, "Slovenian (Slovenia)"));
1550 return(copy_lang (lang_out, lang_len, "Slovenian"));
1557 return(copy_lang (lang_out, lang_len, "Estonian (Estonia)"));
1560 return(copy_lang (lang_out, lang_len, "Estonian"));
1567 return(copy_lang (lang_out, lang_len, "Latvian (Latvia)"));
1570 return(copy_lang (lang_out, lang_len, "Latvian"));
1577 return(copy_lang (lang_out, lang_len, "Lithuanian (Lithuania)"));
1580 return(copy_lang (lang_out, lang_len, "Lithuanian"));
1587 return(copy_lang (lang_out, lang_len, "Tajik (Tajikistan)"));
1594 return(copy_lang (lang_out, lang_len, "Farsi (Iran)"));
1597 return(copy_lang (lang_out, lang_len, "Farsi"));
1604 return(copy_lang (lang_out, lang_len, "Vietnamese (Viet Nam)"));
1607 return(copy_lang (lang_out, lang_len, "Vietnamese"));
1614 return(copy_lang (lang_out, lang_len, "Armenian (Armenia)"));
1617 return(copy_lang (lang_out, lang_len, "Armenian"));
1624 return(copy_lang (lang_out, lang_len, "Azeri (Latin) (Azerbaijan)"));
1627 return(copy_lang (lang_out, lang_len, "Azeri (Latin)"));
1630 return(copy_lang (lang_out, lang_len, "Azeri (Cyrillic)"));
1637 return(copy_lang (lang_out, lang_len, "Basque (Spain)"));
1640 return(copy_lang (lang_out, lang_len, "Basque"));
1647 return(copy_lang (lang_out, lang_len, "Upper Sorbian (Germany)"));
1650 return(copy_lang (lang_out, lang_len, "Lower Sorbian (Germany)"));
1657 return(copy_lang (lang_out, lang_len, "FYRO Macedonian (Former Yugoslav Republic of Macedonia)"));
1660 return(copy_lang (lang_out, lang_len, "FYRO Macedonian"));
1667 return(copy_lang (lang_out, lang_len, "Tswana (South Africa)"));
1670 return(copy_lang (lang_out, lang_len, "Tswana"));
1677 return(copy_lang (lang_out, lang_len, "Xhosa (South Africa)"));
1680 return(copy_lang (lang_out, lang_len, "Xhosa"));
1687 return(copy_lang (lang_out, lang_len, "Zulu (South Africa)"));
1690 return(copy_lang (lang_out, lang_len, "Zulu"));
1697 return(copy_lang (lang_out, lang_len, "Afrikaans (South Africa)"));
1700 return(copy_lang (lang_out, lang_len, "Afrikaans"));
1707 return(copy_lang (lang_out, lang_len, "Georgian (Georgia)"));
1710 return(copy_lang (lang_out, lang_len, "Georgian"));
1717 return(copy_lang (lang_out, lang_len, "Faroese (Faroe Islands)"));
1720 return(copy_lang (lang_out, lang_len, "Faroese"));
1727 return(copy_lang (lang_out, lang_len, "Hindi (India)"));
1730 return(copy_lang (lang_out, lang_len, "Hindi"));
1737 return(copy_lang (lang_out, lang_len, "Maltese (Malta)"));
1740 return(copy_lang (lang_out, lang_len, "Maltese"));
1747 return(copy_lang (lang_out, lang_len, "Sami (Northern) (Norway)"));
1750 return(copy_lang (lang_out, lang_len, "Sami, Northern (Norway)"));
1753 return(copy_lang (lang_out, lang_len, "Sami, Northern (Sweden)"));
1756 return(copy_lang (lang_out, lang_len, "Sami, Northern (Finland)"));
1759 return(copy_lang (lang_out, lang_len, "Sami, Lule (Norway)"));
1762 return(copy_lang (lang_out, lang_len, "Sami, Lule (Sweden)"));
1765 return(copy_lang (lang_out, lang_len, "Sami, Southern (Norway)"));
1768 return(copy_lang (lang_out, lang_len, "Sami, Southern (Sweden)"));
1771 return(copy_lang (lang_out, lang_len, "Sami, Skolt (Finland)"));
1774 return(copy_lang (lang_out, lang_len, "Sami, Inari (Finland)"));
1781 return(copy_lang (lang_out, lang_len, "Irish (Ireland)"));
1789 return(copy_lang (lang_out, lang_len, "Malay (Malaysia)"));
1792 return(copy_lang (lang_out, lang_len, "Malay (Brunei Darussalam)"));
1799 return(copy_lang (lang_out, lang_len, "Kazakh (Kazakhstan)"));
1802 return(copy_lang (lang_out, lang_len, "Kazakh"));
1809 return(copy_lang (lang_out, lang_len, "Kyrgyz (Kyrgyzstan)"));
1812 return(copy_lang (lang_out, lang_len, "Kyrgyz (Cyrillic)"));
1819 return(copy_lang (lang_out, lang_len, "Swahili (Kenya)"));
1822 return(copy_lang (lang_out, lang_len, "Swahili"));
1829 return(copy_lang (lang_out, lang_len, "Turkmen (Turkmenistan)"));
1836 return(copy_lang (lang_out, lang_len, "Uzbek (Latin) (Uzbekistan)"));
1839 return(copy_lang (lang_out, lang_len, "Uzbek (Latin)"));
1842 return(copy_lang (lang_out, lang_len, "Uzbek (Cyrillic)"));
1849 return(copy_lang (lang_out, lang_len, "Tatar (Russia)"));
1852 return(copy_lang (lang_out, lang_len, "Tatar"));
1860 return(copy_lang (lang_out, lang_len, "Bengali (India)"));
1867 return(copy_lang (lang_out, lang_len, "Punjabi (India)"));
1870 return(copy_lang (lang_out, lang_len, "Punjabi"));
1877 return(copy_lang (lang_out, lang_len, "Gujarati (India)"));
1880 return(copy_lang (lang_out, lang_len, "Gujarati"));
1887 return(copy_lang (lang_out, lang_len, "Tamil (India)"));
1890 return(copy_lang (lang_out, lang_len, "Tamil"));
1897 return(copy_lang (lang_out, lang_len, "Telugu (India)"));
1900 return(copy_lang (lang_out, lang_len, "Telugu"));
1907 return(copy_lang (lang_out, lang_len, "Kannada (India)"));
1910 return(copy_lang (lang_out, lang_len, "Kannada"));
1918 return(copy_lang (lang_out, lang_len, "Malayalam (India)"));
1925 return(copy_lang (lang_out, lang_len, "Assamese (India)"));
1932 return(copy_lang (lang_out, lang_len, "Marathi (India)"));
1935 return(copy_lang (lang_out, lang_len, "Marathi"));
1942 return(copy_lang (lang_out, lang_len, "Sanskrit (India)"));
1945 return(copy_lang (lang_out, lang_len, "Sanskrit"));
1952 return(copy_lang (lang_out, lang_len, "Mongolian (Mongolia)"));
1955 return(copy_lang (lang_out, lang_len, "Mongolian (Cyrillic)"));
1958 return(copy_lang (lang_out, lang_len, "Mongolian (PRC)"));
1965 return(copy_lang (lang_out, lang_len, "Tibetan (PRC)"));
1968 return(copy_lang (lang_out, lang_len, "Tibetan (Bhutan)"));
1975 return(copy_lang (lang_out, lang_len, "Welsh (United Kingdom)"));
1978 return(copy_lang (lang_out, lang_len, "Welsh"));
1985 return(copy_lang (lang_out, lang_len, "Khmer (Cambodia)"));
1992 return(copy_lang (lang_out, lang_len, "Lao (Lao PDR)"));
1999 return(copy_lang (lang_out, lang_len, "Galician (Spain)"));
2002 return(copy_lang (lang_out, lang_len, "Galician"));
2009 return(copy_lang (lang_out, lang_len, "Konkani (India)"));
2012 return(copy_lang (lang_out, lang_len, "Konkani"));
2019 return(copy_lang (lang_out, lang_len, "Syriac (Syria)"));
2022 return(copy_lang (lang_out, lang_len, "Syriac"));
2029 return(copy_lang (lang_out, lang_len, "Sinhala (Sri Lanka)"));
2036 return(copy_lang (lang_out, lang_len, "Inuktitut (Syllabics, Canada)"));
2039 return(copy_lang (lang_out, lang_len, "Inuktitut (Latin, Canada)"));
2046 return(copy_lang (lang_out, lang_len, "Amharic (Ethiopia)"));
2053 return(copy_lang (lang_out, lang_len, "Tamazight (Algeria, Latin)"));
2060 return(copy_lang (lang_out, lang_len, "Nepali (Nepal)"));
2067 return(copy_lang (lang_out, lang_len, "Frisian (Netherlands)"));
2074 return(copy_lang (lang_out, lang_len, "Pashto (Afghanistan)"));
2081 return(copy_lang (lang_out, lang_len, "Filipino (Philippines)"));
2088 return(copy_lang (lang_out, lang_len, "Divehi (Maldives)"));
2091 return(copy_lang (lang_out, lang_len, "Divehi"));
2098 return(copy_lang (lang_out, lang_len, "Hausa (Nigeria, Latin)"));
2105 return(copy_lang (lang_out, lang_len, "Yoruba (Nigeria)"));
2113 return(copy_lang (lang_out, lang_len, "Quechua (Bolivia)"));
2116 return(copy_lang (lang_out, lang_len, "Quechua (Ecuador)"));
2119 return(copy_lang (lang_out, lang_len, "Quechua (Peru)"));
2126 return(copy_lang (lang_out, lang_len, "Northern Sotho (South Africa)"));
2129 return(copy_lang (lang_out, lang_len, "Northern Sotho"));
2136 return(copy_lang (lang_out, lang_len, "Bashkir (Russia)"));
2143 return(copy_lang (lang_out, lang_len, "Luxembourgish (Luxembourg)"));
2150 return(copy_lang (lang_out, lang_len, "Greenlandic (Greenland)"));
2157 return(copy_lang (lang_out, lang_len, "Yi (PRC)"));
2164 return(copy_lang (lang_out, lang_len, "Mapudungun (Chile)"));
2171 return(copy_lang (lang_out, lang_len, "Mohawk (Mohawk)"));
2178 return(copy_lang (lang_out, lang_len, "Breton (France)"));
2185 return(copy_lang (lang_out, lang_len, "Invariant Language (Invariant Country)"));
2192 return(copy_lang (lang_out, lang_len, "Uighur (PRC)"));
2199 return(copy_lang (lang_out, lang_len, "Maori (New Zealand)"));
2202 return(copy_lang (lang_out, lang_len, "Maori"));
2209 return(copy_lang (lang_out, lang_len, "Corsican (France)"));
2216 return(copy_lang (lang_out, lang_len, "Alsatian (France)"));
2223 return(copy_lang (lang_out, lang_len, "Yakut (Russia)"));
2230 return(copy_lang (lang_out, lang_len, "K'iche (Guatemala)"));
2237 return(copy_lang (lang_out, lang_len, "Kinyarwanda (Rwanda)"));
2244 return(copy_lang (lang_out, lang_len, "Wolof (Senegal)"));
2251 return(copy_lang (lang_out, lang_len, "Dari (Afghanistan)"));
2257 return(copy_lang (lang_out, lang_len, "Language Neutral"));
2261 return(copy_lang (lang_out, lang_len, "Language Neutral"));