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 DEBUG(...) g_message(__VA_ARGS__)
34 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
36 static WapiImageSectionHeader *
37 get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
39 WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
42 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
43 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
45 size = GUINT32_FROM_LE (section->SizeOfRawData);
48 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
49 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
57 /* This works for both 32bit and 64bit files, as the differences are
58 * all after the section header block
61 get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders, gpointer file_map)
63 WapiImageSectionHeader *section_header;
66 section_header = get_enclosing_section_header (rva, ntheaders);
67 if (section_header == NULL) {
71 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
72 GUINT32_FROM_LE (section_header->PointerToRawData));
74 return((guint8 *)file_map + rva - delta);
78 scan_resource_dir (WapiImageResourceDirectory *root,
79 WapiImageNTHeaders32 *nt_headers,
81 WapiImageResourceDirectoryEntry *entry,
82 int level, guint32 res_id, guint32 lang_id,
85 WapiImageResourceDirectoryEntry swapped_entry;
86 gboolean is_string, is_dir;
87 guint32 name_offset, dir_offset, data_offset;
89 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
90 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
92 is_string = swapped_entry.NameIsString;
93 is_dir = swapped_entry.DataIsDirectory;
94 name_offset = swapped_entry.NameOffset;
95 dir_offset = swapped_entry.OffsetToDirectory;
96 data_offset = swapped_entry.OffsetToData;
99 /* Normally holds a directory entry for each type of
102 if ((is_string == FALSE &&
103 name_offset != res_id) ||
104 (is_string == TRUE)) {
107 } else if (level == 1) {
108 /* Normally holds a directory entry for each resource
111 } else if (level == 2) {
112 /* Normally holds a directory entry for each language
114 if ((is_string == FALSE &&
115 name_offset != lang_id &&
117 (is_string == TRUE)) {
121 g_assert_not_reached ();
124 if (is_dir == TRUE) {
125 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
126 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
129 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
131 for (i = 0; i < entries; i++) {
132 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
135 ret = scan_resource_dir (root, nt_headers, file_map,
136 sub_entry, level + 1, res_id,
145 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
146 *size = GUINT32_FROM_LE (data_entry->Size);
148 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
153 find_pe_file_resources32 (gpointer file_map, guint32 map_size,
154 guint32 res_id, guint32 lang_id,
157 WapiImageDosHeader *dos_header;
158 WapiImageNTHeaders32 *nt_headers;
159 WapiImageResourceDirectory *resource_dir;
160 WapiImageResourceDirectoryEntry *resource_dir_entry;
161 guint32 resource_rva, entries, i;
164 dos_header = (WapiImageDosHeader *)file_map;
165 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
166 DEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
168 SetLastError (ERROR_INVALID_DATA);
172 if (map_size < sizeof(WapiImageNTHeaders32) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
173 DEBUG ("%s: File is too small: %d", __func__, map_size);
175 SetLastError (ERROR_BAD_LENGTH);
179 nt_headers = (WapiImageNTHeaders32 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
180 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
181 DEBUG ("%s: Bad NT signature 0x%x", __func__, nt_headers->Signature);
183 SetLastError (ERROR_INVALID_DATA);
187 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
188 /* Do 64-bit stuff */
189 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
191 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
194 if (resource_rva == 0) {
195 DEBUG ("%s: No resources in file!", __func__);
197 SetLastError (ERROR_INVALID_DATA);
201 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
202 if (resource_dir == NULL) {
203 DEBUG ("%s: Can't find resource directory", __func__);
205 SetLastError (ERROR_INVALID_DATA);
209 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
210 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
212 for (i = 0; i < entries; i++) {
213 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
214 ret = scan_resource_dir (resource_dir,
215 (WapiImageNTHeaders32 *)nt_headers,
216 file_map, direntry, 0, res_id,
227 find_pe_file_resources64 (gpointer file_map, guint32 map_size,
228 guint32 res_id, guint32 lang_id,
231 WapiImageDosHeader *dos_header;
232 WapiImageNTHeaders64 *nt_headers;
233 WapiImageResourceDirectory *resource_dir;
234 WapiImageResourceDirectoryEntry *resource_dir_entry;
235 guint32 resource_rva, entries, i;
238 dos_header = (WapiImageDosHeader *)file_map;
239 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
240 DEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
242 SetLastError (ERROR_INVALID_DATA);
246 if (map_size < sizeof(WapiImageNTHeaders64) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
247 DEBUG ("%s: File is too small: %d", __func__, map_size);
249 SetLastError (ERROR_BAD_LENGTH);
253 nt_headers = (WapiImageNTHeaders64 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
254 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
255 DEBUG ("%s: Bad NT signature 0x%x", __func__,
256 nt_headers->Signature);
258 SetLastError (ERROR_INVALID_DATA);
262 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
263 /* Do 64-bit stuff */
264 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
266 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
269 if (resource_rva == 0) {
270 DEBUG ("%s: No resources in file!", __func__);
272 SetLastError (ERROR_INVALID_DATA);
276 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
277 if (resource_dir == NULL) {
278 DEBUG ("%s: Can't find resource directory", __func__);
280 SetLastError (ERROR_INVALID_DATA);
284 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
285 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
287 for (i = 0; i < entries; i++) {
288 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
289 ret = scan_resource_dir (resource_dir,
290 (WapiImageNTHeaders32 *)nt_headers,
291 file_map, direntry, 0, res_id,
302 find_pe_file_resources (gpointer file_map, guint32 map_size,
303 guint32 res_id, guint32 lang_id,
306 /* Figure this out when we support 64bit PE files */
308 return find_pe_file_resources32 (file_map, map_size, res_id,
311 return find_pe_file_resources64 (file_map, map_size, res_id,
317 map_pe_file (gunichar2 *filename, gint32 *map_size, void **handle)
324 /* According to the MSDN docs, a search path is applied to
325 * filename. FIXME: implement this, for now just pass it
329 filename_ext = mono_unicode_to_external (filename);
330 if (filename_ext == NULL) {
331 DEBUG ("%s: unicode conversion returned NULL", __func__);
333 SetLastError (ERROR_INVALID_NAME);
337 fd = _wapi_open (filename_ext, O_RDONLY, 0);
339 DEBUG ("%s: Error opening file %s: %s", __func__, filename_ext, strerror (errno));
341 SetLastError (_wapi_get_win32_file_error (errno));
342 g_free (filename_ext);
347 if (fstat (fd, &statbuf) == -1) {
348 DEBUG ("%s: Error stat()ing file %s: %s", __func__, filename_ext, strerror (errno));
350 SetLastError (_wapi_get_win32_file_error (errno));
351 g_free (filename_ext);
355 *map_size = statbuf.st_size;
357 /* Check basic file size */
358 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
359 DEBUG ("%s: File %s is too small: %lld", __func__, filename_ext, statbuf.st_size);
361 SetLastError (ERROR_BAD_LENGTH);
362 g_free (filename_ext);
367 file_map = mono_file_map (statbuf.st_size, MONO_MMAP_READ | MONO_MMAP_PRIVATE, fd, 0, handle);
368 if (file_map == NULL) {
369 DEBUG ("%s: Error mmap()int file %s: %s", __func__, filename_ext, strerror (errno));
371 SetLastError (_wapi_get_win32_file_error (errno));
372 g_free (filename_ext);
377 /* Don't need the fd any more */
379 g_free (filename_ext);
385 unmap_pe_file (gpointer file_map, void *handle)
387 mono_file_unmap (file_map, handle);
391 unicode_chars (const gunichar2 *str)
396 if (str[len] == '\0') {
404 unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
406 while (*str1 && *str2) {
407 if (*str1 != *str2) {
414 return(*str1 == *str2);
417 /* compare a little-endian null-terminated utf16 string and a normal string.
418 * Can be used only for ascii or latin1 chars.
421 unicode_string_equals (const gunichar2 *str1, const gchar *str2)
423 while (*str1 && *str2) {
424 if (GUINT16_TO_LE (*str1) != *str2) {
431 return(*str1 == *str2);
442 /* Returns a pointer to the value data, because there's no way to know
443 * how big that data is (value_len is set to zero for most blocks :-( )
446 get_versioninfo_block (gconstpointer data, version_data *block)
448 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
449 data = (char *)data + sizeof(guint16);
450 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
451 data = (char *)data + sizeof(guint16);
453 /* No idea what the type is supposed to indicate */
454 block->type = GUINT16_FROM_LE (*((guint16 *)data));
455 data = (char *)data + sizeof(guint16);
456 block->key = ((gunichar2 *)data);
458 /* Skip over the key (including the terminator) */
459 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
461 /* align on a 32-bit boundary */
468 get_fixedfileinfo_block (gconstpointer data, version_data *block)
470 gconstpointer data_ptr;
471 WapiFixedFileInfo *ffi;
473 data_ptr = get_versioninfo_block (data, block);
475 if (block->value_len != sizeof(WapiFixedFileInfo)) {
476 DEBUG ("%s: FIXEDFILEINFO size mismatch", __func__);
480 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
481 DEBUG ("%s: VS_VERSION_INFO mismatch", __func__);
486 ffi = ((WapiFixedFileInfo *)data_ptr);
487 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
488 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
489 DEBUG ("%s: FIXEDFILEINFO bad signature", __func__);
498 get_varfileinfo_block (gconstpointer data_ptr, version_data *block)
500 /* data is pointing at a Var block
502 data_ptr = get_versioninfo_block (data_ptr, block);
508 get_string_block (gconstpointer data_ptr,
509 const gunichar2 *string_key,
510 gpointer *string_value,
511 guint32 *string_value_len,
514 guint16 data_len = block->data_len;
515 guint16 string_len = 28; /* Length of the StringTable block */
516 char *orig_data_ptr = (char *)data_ptr - 28;
518 /* data_ptr is pointing at an array of one or more String blocks
519 * with total length (not including alignment padding) of
522 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
523 /* align on a 32-bit boundary */
526 data_ptr = get_versioninfo_block (data_ptr, block);
527 if (block->data_len == 0) {
528 /* We must have hit padding, so give up
531 DEBUG ("%s: Hit 0-length block, giving up", __func__);
536 string_len = string_len + block->data_len;
538 if (string_key != NULL &&
539 string_value != NULL &&
540 string_value_len != NULL &&
541 unicode_compare (string_key, block->key) == TRUE) {
542 *string_value = (gpointer)data_ptr;
543 *string_value_len = block->value_len;
546 /* Skip over the value */
547 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
553 /* Returns a pointer to the byte following the Stringtable block, or
554 * NULL if the data read hits padding. We can't recover from this
555 * because the data length does not include padding bytes, so it's not
556 * possible to just return the start position + length
558 * If lang == NULL it means we're just stepping through this block
561 get_stringtable_block (gconstpointer data_ptr,
563 const gunichar2 *string_key,
564 gpointer *string_value,
565 guint32 *string_value_len,
568 guint16 data_len = block->data_len;
569 guint16 string_len = 36; /* length of the StringFileInfo block */
571 gchar *lowercase_lang;
573 /* data_ptr is pointing at an array of StringTable blocks,
574 * with total length (not including alignment padding) of
578 while(string_len < data_len) {
579 /* align on a 32-bit boundary */
582 data_ptr = get_versioninfo_block (data_ptr, block);
583 if (block->data_len == 0) {
584 /* We must have hit padding, so give up
587 DEBUG ("%s: Hit 0-length block, giving up", __func__);
591 string_len = string_len + block->data_len;
593 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
594 if (found_lang == NULL) {
595 DEBUG ("%s: Didn't find a valid language key, giving up", __func__);
599 lowercase_lang = g_utf8_strdown (found_lang, -1);
601 found_lang = lowercase_lang;
602 lowercase_lang = NULL;
604 if (lang != NULL && !strcmp (found_lang, lang)) {
605 /* Got the one we're interested in */
606 data_ptr = get_string_block (data_ptr, string_key,
608 string_value_len, block);
610 data_ptr = get_string_block (data_ptr, NULL, NULL,
616 if (data_ptr == NULL) {
617 /* Child block hit padding */
618 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
626 #if G_BYTE_ORDER == G_BIG_ENDIAN
628 big_up_string_block (gconstpointer data_ptr, version_data *block)
630 guint16 data_len = block->data_len;
631 guint16 string_len = 28; /* Length of the StringTable block */
633 char *orig_data_ptr = (char *)data_ptr - 28;
635 /* data_ptr is pointing at an array of one or more String
636 * blocks with total length (not including alignment padding)
639 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
640 /* align on a 32-bit boundary */
643 data_ptr = get_versioninfo_block (data_ptr, block);
644 if (block->data_len == 0) {
645 /* We must have hit padding, so give up
648 DEBUG ("%s: Hit 0-length block, giving up", __func__);
652 string_len = string_len + block->data_len;
654 big_value = g_convert ((gchar *)block->key,
655 unicode_chars (block->key) * 2,
656 "UTF-16BE", "UTF-16LE", NULL, NULL,
658 if (big_value == NULL) {
659 DEBUG ("%s: Didn't find a valid string, giving up", __func__);
663 /* The swapped string should be exactly the same
664 * length as the original little-endian one, but only
665 * copy the number of original chars just to be on the
668 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
671 big_value = g_convert ((gchar *)data_ptr,
672 unicode_chars (data_ptr) * 2,
673 "UTF-16BE", "UTF-16LE", NULL, NULL,
675 if (big_value == NULL) {
676 DEBUG ("%s: Didn't find a valid data string, giving up", __func__);
679 memcpy ((gpointer)data_ptr, big_value,
680 unicode_chars (data_ptr) * 2);
683 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
689 /* Returns a pointer to the byte following the Stringtable block, or
690 * NULL if the data read hits padding. We can't recover from this
691 * because the data length does not include padding bytes, so it's not
692 * possible to just return the start position + length
695 big_up_stringtable_block (gconstpointer data_ptr, version_data *block)
697 guint16 data_len = block->data_len;
698 guint16 string_len = 36; /* length of the StringFileInfo block */
701 /* data_ptr is pointing at an array of StringTable blocks,
702 * with total length (not including alignment padding) of
706 while(string_len < data_len) {
707 /* align on a 32-bit boundary */
710 data_ptr = get_versioninfo_block (data_ptr, block);
711 if (block->data_len == 0) {
712 /* We must have hit padding, so give up
715 DEBUG ("%s: Hit 0-length block, giving up", __func__);
719 string_len = string_len + block->data_len;
721 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
722 "UTF-16LE", NULL, NULL, NULL);
723 if (big_value == NULL) {
724 DEBUG ("%s: Didn't find a valid string, giving up", __func__);
728 memcpy (block->key, big_value, 16);
731 data_ptr = big_up_string_block (data_ptr, block);
733 if (data_ptr == NULL) {
734 /* Child block hit padding */
735 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
743 /* Follows the data structures and turns all UTF-16 strings from the
744 * LE found in the resource section into UTF-16BE
747 big_up (gconstpointer datablock, guint32 size)
749 gconstpointer data_ptr;
750 gint32 data_len; /* signed to guard against underflow */
753 data_ptr = get_fixedfileinfo_block (datablock, &block);
754 if (data_ptr != NULL) {
755 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
757 /* Byteswap all the fields */
758 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
759 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
760 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
761 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
762 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
763 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
764 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
765 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
766 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
767 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
768 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
770 /* The FFI and header occupies the first 92 bytes
772 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
773 data_len = block.data_len - 92;
775 /* There now follow zero or one StringFileInfo blocks
776 * and zero or one VarFileInfo blocks
778 while (data_len > 0) {
779 /* align on a 32-bit boundary */
782 data_ptr = get_versioninfo_block (data_ptr, &block);
783 if (block.data_len == 0) {
784 /* We must have hit padding, so give
787 DEBUG ("%s: Hit 0-length block, giving up", __func__);
791 data_len = data_len - block.data_len;
793 if (unicode_string_equals (block.key, "VarFileInfo")) {
794 data_ptr = get_varfileinfo_block (data_ptr,
796 data_ptr = ((guchar *)data_ptr) + block.value_len;
797 } else if (unicode_string_equals (block.key,
799 data_ptr = big_up_stringtable_block (data_ptr,
803 DEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
807 if (data_ptr == NULL) {
808 /* Child block hit padding */
809 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
818 VerQueryValue (gconstpointer datablock, const gunichar2 *subblock, gpointer *buffer, guint32 *len)
820 gchar *subblock_utf8, *lang_utf8 = NULL;
821 gboolean ret = FALSE;
823 gconstpointer data_ptr;
824 gint32 data_len; /* signed to guard against underflow */
825 gboolean want_var = FALSE;
826 gboolean want_string = FALSE;
828 const gunichar2 *string_key = NULL;
829 gpointer string_value = NULL;
830 guint32 string_value_len = 0;
831 gchar *lowercase_lang;
833 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
834 if (subblock_utf8 == NULL) {
838 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
840 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
842 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
843 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
844 lowercase_lang = g_utf8_strdown (lang_utf8, -1);
846 lang_utf8 = lowercase_lang;
847 lowercase_lang = NULL;
848 string_key = subblock + 25;
851 if (!strcmp (subblock_utf8, "\\")) {
852 data_ptr = get_fixedfileinfo_block (datablock, &block);
853 if (data_ptr != NULL) {
854 *buffer = (gpointer)data_ptr;
855 *len = block.value_len;
859 } else if (want_var || want_string) {
860 data_ptr = get_fixedfileinfo_block (datablock, &block);
861 if (data_ptr != NULL) {
862 /* The FFI and header occupies the first 92
865 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
866 data_len = block.data_len - 92;
868 /* There now follow zero or one StringFileInfo
869 * blocks and zero or one VarFileInfo blocks
871 while (data_len > 0) {
872 /* align on a 32-bit boundary */
875 data_ptr = get_versioninfo_block (data_ptr,
877 if (block.data_len == 0) {
878 /* We must have hit padding,
879 * so give up processing now
881 DEBUG ("%s: Hit 0-length block, giving up", __func__);
885 data_len = data_len - block.data_len;
887 if (unicode_string_equals (block.key, "VarFileInfo")) {
888 data_ptr = get_varfileinfo_block (data_ptr, &block);
890 *buffer = (gpointer)data_ptr;
891 *len = block.value_len;
895 /* Skip over the Var block */
896 data_ptr = ((guchar *)data_ptr) + block.value_len;
898 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
899 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
901 string_value != NULL &&
902 string_value_len != 0) {
903 *buffer = string_value;
904 *len = unicode_chars ((const gunichar2 *)string_value) + 1; /* Include trailing null */
910 DEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
914 if (data_ptr == NULL) {
915 /* Child block hit padding */
916 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
928 g_free (subblock_utf8);
933 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
936 gpointer versioninfo;
941 /* This value is unused, but set to zero */
944 file_map = map_pe_file (filename, &map_size, &map_handle);
945 if (file_map == NULL) {
949 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION, 0, &size);
950 if (versioninfo == NULL) {
951 /* Didn't find the resource, so set the return value
957 unmap_pe_file (file_map, map_handle);
963 GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED, guint32 len, gpointer data)
966 gpointer versioninfo;
970 gboolean ret = FALSE;
972 file_map = map_pe_file (filename, &map_size, &map_handle);
973 if (file_map == NULL) {
977 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
979 if (versioninfo != NULL) {
980 /* This could probably process the data so that
981 * VerQueryValue() doesn't have to follow the data
982 * blocks every time. But hey, these functions aren't
983 * likely to appear in many profiles.
985 memcpy (data, versioninfo, len < size?len:size);
988 #if G_BYTE_ORDER == G_BIG_ENDIAN
993 unmap_pe_file (file_map, map_handle);
999 copy_lang (gunichar2 *lang_out, guint32 lang_len, const gchar *text)
1002 int chars = strlen (text);
1005 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1006 g_assert (unitext != NULL);
1008 if (chars < (lang_len - 1)) {
1009 memcpy (lang_out, (gpointer)unitext, chars * 2);
1010 lang_out[chars] = '\0';
1013 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1014 lang_out[lang_len] = '\0';
1024 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1026 int primary, secondary;
1027 const char *name = NULL;
1029 primary = lang & 0x3FF;
1030 secondary = (lang >> 10) & 0x3F;
1036 name = "Process Default Language";
1044 name = "Arabic (Saudi Arabia)";
1047 name = "Arabic (Iraq)";
1050 name = "Arabic (Egypt)";
1053 name = "Arabic (Libya)";
1056 name = "Arabic (Algeria)";
1059 name = "Arabic (Morocco)";
1062 name = "Arabic (Tunisia)";
1065 name = "Arabic (Oman)";
1068 name = "Arabic (Yemen)";
1071 name = "Arabic (Syria)";
1074 name = "Arabic (Jordan)";
1077 name = "Arabic (Lebanon)";
1080 name = "Arabic (Kuwait)";
1083 name = "Arabic (U.A.E.)";
1086 name = "Arabic (Bahrain)";
1089 name = "Arabic (Qatar)";
1096 name = "Bulgarian (Bulgaria)";
1106 name = "Catalan (Spain)";
1117 name = "Chinese (Taiwan)";
1120 name = "Chinese (PRC)";
1123 name = "Chinese (Hong Kong S.A.R.)";
1126 name = "Chinese (Singapore)";
1129 name = "Chinese (Macau S.A.R.)";
1136 name = "Czech (Czech Republic)";
1146 name = "Danish (Denmark)";
1157 name = "German (Germany)";
1160 name = "German (Switzerland)";
1163 name = "German (Austria)";
1166 name = "German (Luxembourg)";
1169 name = "German (Liechtenstein)";
1176 name = "Greek (Greece)";
1187 name = "English (United States)";
1190 name = "English (United Kingdom)";
1193 name = "English (Australia)";
1196 name = "English (Canada)";
1199 name = "English (New Zealand)";
1202 name = "English (Ireland)";
1205 name = "English (South Africa)";
1208 name = "English (Jamaica)";
1211 name = "English (Caribbean)";
1214 name = "English (Belize)";
1217 name = "English (Trinidad and Tobago)";
1220 name = "English (Zimbabwe)";
1223 name = "English (Philippines)";
1226 name = "English (India)";
1229 name = "English (Malaysia)";
1232 name = "English (Singapore)";
1239 name = "Spanish (Spain)";
1242 name = "Spanish (Traditional Sort)";
1245 name = "Spanish (Mexico)";
1248 name = "Spanish (International Sort)";
1251 name = "Spanish (Guatemala)";
1254 name = "Spanish (Costa Rica)";
1257 name = "Spanish (Panama)";
1260 name = "Spanish (Dominican Republic)";
1263 name = "Spanish (Venezuela)";
1266 name = "Spanish (Colombia)";
1269 name = "Spanish (Peru)";
1272 name = "Spanish (Argentina)";
1275 name = "Spanish (Ecuador)";
1278 name = "Spanish (Chile)";
1281 name = "Spanish (Uruguay)";
1284 name = "Spanish (Paraguay)";
1287 name = "Spanish (Bolivia)";
1290 name = "Spanish (El Salvador)";
1293 name = "Spanish (Honduras)";
1296 name = "Spanish (Nicaragua)";
1299 name = "Spanish (Puerto Rico)";
1302 name = "Spanish (United States)";
1309 name = "Finnish (Finland)";
1320 name = "French (France)";
1323 name = "French (Belgium)";
1326 name = "French (Canada)";
1329 name = "French (Switzerland)";
1332 name = "French (Luxembourg)";
1335 name = "French (Monaco)";
1342 name = "Hebrew (Israel)";
1352 name = "Hungarian (Hungary)";
1362 name = "Icelandic (Iceland)";
1373 name = "Italian (Italy)";
1376 name = "Italian (Switzerland)";
1383 name = "Japanese (Japan)";
1393 name = "Korean (Korea)";
1404 name = "Dutch (Netherlands)";
1407 name = "Dutch (Belgium)";
1415 name = "Norwegian (Bokmal)";
1418 name = "Norwegian (Nynorsk)";
1425 name = "Polish (Poland)";
1436 name = "Portuguese (Brazil)";
1439 name = "Portuguese (Portugal)";
1446 name = "Romansh (Switzerland)";
1453 name = "Romanian (Romania)";
1463 name = "Russian (Russia)";
1473 name = "Croatian (Croatia)";
1479 name = "Serbian (Latin)";
1482 name = "Serbian (Cyrillic)";
1485 name = "Croatian (Bosnia and Herzegovina)";
1488 name = "Bosnian (Latin, Bosnia and Herzegovina)";
1491 name = "Serbian (Latin, Bosnia and Herzegovina)";
1494 name = "Serbian (Cyrillic, Bosnia and Herzegovina)";
1497 name = "Bosnian (Cyrillic, Bosnia and Herzegovina)";
1504 name = "Slovak (Slovakia)";
1514 name = "Albanian (Albania)";
1524 name = "Swedish (Sweden)";
1530 name = "Swedish (Finland)";
1537 name = "Thai (Thailand)";
1547 name = "Turkish (Turkey)";
1557 name = "Urdu (Islamic Republic of Pakistan)";
1567 name = "Indonesian (Indonesia)";
1570 name = "Indonesian";
1577 name = "Ukrainian (Ukraine)";
1587 name = "Belarusian (Belarus)";
1590 name = "Belarusian";
1597 name = "Slovenian (Slovenia)";
1607 name = "Estonian (Estonia)";
1617 name = "Latvian (Latvia)";
1627 name = "Lithuanian (Lithuania)";
1630 name = "Lithuanian";
1637 name = "Tajik (Tajikistan)";
1644 name = "Farsi (Iran)";
1654 name = "Vietnamese (Viet Nam)";
1657 name = "Vietnamese";
1664 name = "Armenian (Armenia)";
1674 name = "Azeri (Latin) (Azerbaijan)";
1677 name = "Azeri (Latin)";
1680 name = "Azeri (Cyrillic)";
1687 name = "Basque (Spain)";
1697 name = "Upper Sorbian (Germany)";
1700 name = "Lower Sorbian (Germany)";
1707 name = "FYRO Macedonian (Former Yugoslav Republic of Macedonia)";
1710 name = "FYRO Macedonian";
1717 name = "Tswana (South Africa)";
1727 name = "Xhosa (South Africa)";
1737 name = "Zulu (South Africa)";
1747 name = "Afrikaans (South Africa)";
1757 name = "Georgian (Georgia)";
1767 name = "Faroese (Faroe Islands)";
1777 name = "Hindi (India)";
1787 name = "Maltese (Malta)";
1797 name = "Sami (Northern) (Norway)";
1800 name = "Sami, Northern (Norway)";
1803 name = "Sami, Northern (Sweden)";
1806 name = "Sami, Northern (Finland)";
1809 name = "Sami, Lule (Norway)";
1812 name = "Sami, Lule (Sweden)";
1815 name = "Sami, Southern (Norway)";
1818 name = "Sami, Southern (Sweden)";
1821 name = "Sami, Skolt (Finland)";
1824 name = "Sami, Inari (Finland)";
1831 name = "Irish (Ireland)";
1839 name = "Malay (Malaysia)";
1842 name = "Malay (Brunei Darussalam)";
1849 name = "Kazakh (Kazakhstan)";
1859 name = "Kyrgyz (Kyrgyzstan)";
1862 name = "Kyrgyz (Cyrillic)";
1869 name = "Swahili (Kenya)";
1879 name = "Turkmen (Turkmenistan)";
1886 name = "Uzbek (Latin) (Uzbekistan)";
1889 name = "Uzbek (Latin)";
1892 name = "Uzbek (Cyrillic)";
1899 name = "Tatar (Russia)";
1910 name = "Bengali (India)";
1917 name = "Punjabi (India)";
1927 name = "Gujarati (India)";
1937 name = "Tamil (India)";
1947 name = "Telugu (India)";
1957 name = "Kannada (India)";
1968 name = "Malayalam (India)";
1975 name = "Assamese (India)";
1982 name = "Marathi (India)";
1992 name = "Sanskrit (India)";
2002 name = "Mongolian (Mongolia)";
2005 name = "Mongolian (Cyrillic)";
2008 name = "Mongolian (PRC)";
2015 name = "Tibetan (PRC)";
2018 name = "Tibetan (Bhutan)";
2025 name = "Welsh (United Kingdom)";
2035 name = "Khmer (Cambodia)";
2042 name = "Lao (Lao PDR)";
2049 name = "Galician (Spain)";
2059 name = "Konkani (India)";
2069 name = "Syriac (Syria)";
2079 name = "Sinhala (Sri Lanka)";
2086 name = "Inuktitut (Syllabics, Canada)";
2089 name = "Inuktitut (Latin, Canada)";
2096 name = "Amharic (Ethiopia)";
2103 name = "Tamazight (Algeria, Latin)";
2110 name = "Nepali (Nepal)";
2117 name = "Frisian (Netherlands)";
2124 name = "Pashto (Afghanistan)";
2131 name = "Filipino (Philippines)";
2138 name = "Divehi (Maldives)";
2148 name = "Hausa (Nigeria, Latin)";
2155 name = "Yoruba (Nigeria)";
2163 name = "Quechua (Bolivia)";
2166 name = "Quechua (Ecuador)";
2169 name = "Quechua (Peru)";
2176 name = "Northern Sotho (South Africa)";
2179 name = "Northern Sotho";
2186 name = "Bashkir (Russia)";
2193 name = "Luxembourgish (Luxembourg)";
2200 name = "Greenlandic (Greenland)";
2214 name = "Mapudungun (Chile)";
2221 name = "Mohawk (Mohawk)";
2228 name = "Breton (France)";
2235 name = "Invariant Language (Invariant Country)";
2242 name = "Uighur (PRC)";
2249 name = "Maori (New Zealand)";
2259 name = "Corsican (France)";
2266 name = "Alsatian (France)";
2273 name = "Yakut (Russia)";
2280 name = "K'iche (Guatemala)";
2287 name = "Kinyarwanda (Rwanda)";
2294 name = "Wolof (Senegal)";
2301 name = "Dari (Afghanistan)";
2307 name = "Language Neutral";
2312 name = "Language Neutral";
2314 return copy_lang (lang_out, lang_len, name);