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
);