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 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
32 static WapiImageSectionHeader
*get_enclosing_section_header (guint32 rva
, WapiImageNTHeaders32
*nt_headers
)
34 WapiImageSectionHeader
*section
= _WAPI_IMAGE_FIRST_SECTION32 (nt_headers
);
37 for (i
= 0; i
< GUINT16_FROM_LE (nt_headers
->FileHeader
.NumberOfSections
); i
++, section
++) {
38 guint32 size
= GUINT32_FROM_LE (section
->Misc
.VirtualSize
);
40 size
= GUINT32_FROM_LE (section
->SizeOfRawData
);
43 if ((rva
>= GUINT32_FROM_LE (section
->VirtualAddress
)) &&
44 (rva
< (GUINT32_FROM_LE (section
->VirtualAddress
) + size
))) {
52 /* This works for both 32bit and 64bit files, as the differences are
53 * all after the section header block
55 static gpointer
get_ptr_from_rva (guint32 rva
, WapiImageNTHeaders32
*ntheaders
,
58 WapiImageSectionHeader
*section_header
;
61 section_header
= get_enclosing_section_header (rva
, ntheaders
);
62 if (section_header
== NULL
) {
66 delta
= (guint32
)(GUINT32_FROM_LE (section_header
->VirtualAddress
) -
67 GUINT32_FROM_LE (section_header
->PointerToRawData
));
69 return((guint8
*)file_map
+ rva
- delta
);
72 static gpointer
scan_resource_dir (WapiImageResourceDirectory
*root
,
73 WapiImageNTHeaders32
*nt_headers
,
75 WapiImageResourceDirectoryEntry
*entry
,
76 int level
, guint32 res_id
, guint32 lang_id
,
79 WapiImageResourceDirectoryEntry swapped_entry
;
80 gboolean is_string
, is_dir
;
81 guint32 name_offset
, dir_offset
, data_offset
;
83 swapped_entry
.Name
= GUINT32_FROM_LE (entry
->Name
);
84 swapped_entry
.OffsetToData
= GUINT32_FROM_LE (entry
->OffsetToData
);
86 is_string
= swapped_entry
.NameIsString
;
87 is_dir
= swapped_entry
.DataIsDirectory
;
88 name_offset
= swapped_entry
.NameOffset
;
89 dir_offset
= swapped_entry
.OffsetToDirectory
;
90 data_offset
= swapped_entry
.OffsetToData
;
93 /* Normally holds a directory entry for each type of
96 if ((is_string
== FALSE
&&
97 name_offset
!= res_id
) ||
98 (is_string
== TRUE
)) {
101 } else if (level
== 1) {
102 /* Normally holds a directory entry for each resource
105 } else if (level
== 2) {
106 /* Normally holds a directory entry for each language
108 if ((is_string
== FALSE
&&
109 name_offset
!= lang_id
&&
111 (is_string
== TRUE
)) {
115 g_assert_not_reached ();
118 if (is_dir
== TRUE
) {
119 WapiImageResourceDirectory
*res_dir
= (WapiImageResourceDirectory
*)((guint8
*)root
+ dir_offset
);
120 WapiImageResourceDirectoryEntry
*sub_entries
= (WapiImageResourceDirectoryEntry
*)(res_dir
+ 1);
123 entries
= GUINT16_FROM_LE (res_dir
->NumberOfNamedEntries
) + GUINT16_FROM_LE (res_dir
->NumberOfIdEntries
);
125 for (i
= 0; i
< entries
; i
++) {
126 WapiImageResourceDirectoryEntry
*sub_entry
= &sub_entries
[i
];
129 ret
= scan_resource_dir (root
, nt_headers
, file_map
,
130 sub_entry
, level
+ 1, res_id
,
139 WapiImageResourceDataEntry
*data_entry
= (WapiImageResourceDataEntry
*)((guint8
*)root
+ data_offset
);
140 *size
= GUINT32_FROM_LE (data_entry
->Size
);
142 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry
->OffsetToData
), nt_headers
, file_map
));
146 static gpointer
find_pe_file_resources32 (gpointer file_map
, guint32 map_size
,
147 guint32 res_id
, guint32 lang_id
,
150 WapiImageDosHeader
*dos_header
;
151 WapiImageNTHeaders32
*nt_headers
;
152 WapiImageResourceDirectory
*resource_dir
;
153 WapiImageResourceDirectoryEntry
*resource_dir_entry
;
154 guint32 resource_rva
, entries
, i
;
157 dos_header
= (WapiImageDosHeader
*)file_map
;
158 if (dos_header
->e_magic
!= IMAGE_DOS_SIGNATURE
) {
160 g_message ("%s: Bad dos signature 0x%x", __func__
,
161 dos_header
->e_magic
);
164 SetLastError (ERROR_INVALID_DATA
);
168 if (map_size
< sizeof(WapiImageNTHeaders32
) + GUINT32_FROM_LE (dos_header
->e_lfanew
)) {
170 g_message ("%s: File is too small: %d", __func__
, map_size
);
173 SetLastError (ERROR_BAD_LENGTH
);
177 nt_headers
= (WapiImageNTHeaders32
*)((guint8
*)file_map
+ GUINT32_FROM_LE (dos_header
->e_lfanew
));
178 if (nt_headers
->Signature
!= IMAGE_NT_SIGNATURE
) {
180 g_message ("%s: Bad NT signature 0x%x", __func__
,
181 nt_headers
->Signature
);
184 SetLastError (ERROR_INVALID_DATA
);
188 if (nt_headers
->OptionalHeader
.Magic
== IMAGE_NT_OPTIONAL_HDR64_MAGIC
) {
189 /* Do 64-bit stuff */
190 resource_rva
= GUINT32_FROM_LE (((WapiImageNTHeaders64
*)nt_headers
)->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_RESOURCE
].VirtualAddress
);
192 resource_rva
= GUINT32_FROM_LE (nt_headers
->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_RESOURCE
].VirtualAddress
);
195 if (resource_rva
== 0) {
197 g_message ("%s: No resources in file!", __func__
);
199 SetLastError (ERROR_INVALID_DATA
);
203 resource_dir
= (WapiImageResourceDirectory
*)get_ptr_from_rva (resource_rva
, (WapiImageNTHeaders32
*)nt_headers
, file_map
);
204 if (resource_dir
== NULL
) {
206 g_message ("%s: Can't find resource directory", __func__
);
208 SetLastError (ERROR_INVALID_DATA
);
212 entries
= GUINT16_FROM_LE (resource_dir
->NumberOfNamedEntries
) + GUINT16_FROM_LE (resource_dir
->NumberOfIdEntries
);
213 resource_dir_entry
= (WapiImageResourceDirectoryEntry
*)(resource_dir
+ 1);
215 for (i
= 0; i
< entries
; i
++) {
216 WapiImageResourceDirectoryEntry
*direntry
= &resource_dir_entry
[i
];
217 ret
= scan_resource_dir (resource_dir
,
218 (WapiImageNTHeaders32
*)nt_headers
,
219 file_map
, direntry
, 0, res_id
,
229 static gpointer
find_pe_file_resources64 (gpointer file_map
, guint32 map_size
,
230 guint32 res_id
, guint32 lang_id
,
233 WapiImageDosHeader
*dos_header
;
234 WapiImageNTHeaders64
*nt_headers
;
235 WapiImageResourceDirectory
*resource_dir
;
236 WapiImageResourceDirectoryEntry
*resource_dir_entry
;
237 guint32 resource_rva
, entries
, i
;
240 dos_header
= (WapiImageDosHeader
*)file_map
;
241 if (dos_header
->e_magic
!= IMAGE_DOS_SIGNATURE
) {
243 g_message ("%s: Bad dos signature 0x%x", __func__
,
244 dos_header
->e_magic
);
247 SetLastError (ERROR_INVALID_DATA
);
251 if (map_size
< sizeof(WapiImageNTHeaders64
) + GUINT32_FROM_LE (dos_header
->e_lfanew
)) {
253 g_message ("%s: File is too small: %d", __func__
, map_size
);
256 SetLastError (ERROR_BAD_LENGTH
);
260 nt_headers
= (WapiImageNTHeaders64
*)((guint8
*)file_map
+ GUINT32_FROM_LE (dos_header
->e_lfanew
));
261 if (nt_headers
->Signature
!= IMAGE_NT_SIGNATURE
) {
263 g_message ("%s: Bad NT signature 0x%x", __func__
,
264 nt_headers
->Signature
);
267 SetLastError (ERROR_INVALID_DATA
);
271 if (nt_headers
->OptionalHeader
.Magic
== IMAGE_NT_OPTIONAL_HDR64_MAGIC
) {
272 /* Do 64-bit stuff */
273 resource_rva
= GUINT32_FROM_LE (((WapiImageNTHeaders64
*)nt_headers
)->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_RESOURCE
].VirtualAddress
);
275 resource_rva
= GUINT32_FROM_LE (nt_headers
->OptionalHeader
.DataDirectory
[IMAGE_DIRECTORY_ENTRY_RESOURCE
].VirtualAddress
);
278 if (resource_rva
== 0) {
280 g_message ("%s: No resources in file!", __func__
);
282 SetLastError (ERROR_INVALID_DATA
);
286 resource_dir
= (WapiImageResourceDirectory
*)get_ptr_from_rva (resource_rva
, (WapiImageNTHeaders32
*)nt_headers
, file_map
);
287 if (resource_dir
== NULL
) {
289 g_message ("%s: Can't find resource directory", __func__
);
291 SetLastError (ERROR_INVALID_DATA
);
295 entries
= GUINT16_FROM_LE (resource_dir
->NumberOfNamedEntries
) + GUINT16_FROM_LE (resource_dir
->NumberOfIdEntries
);
296 resource_dir_entry
= (WapiImageResourceDirectoryEntry
*)(resource_dir
+ 1);
298 for (i
= 0; i
< entries
; i
++) {
299 WapiImageResourceDirectoryEntry
*direntry
= &resource_dir_entry
[i
];
300 ret
= scan_resource_dir (resource_dir
,
301 (WapiImageNTHeaders32
*)nt_headers
,
302 file_map
, direntry
, 0, res_id
,
312 static gpointer
find_pe_file_resources (gpointer file_map
, guint32 map_size
,
313 guint32 res_id
, guint32 lang_id
,
316 /* Figure this out when we support 64bit PE files */
318 return find_pe_file_resources32 (file_map
, map_size
, res_id
,
321 return find_pe_file_resources64 (file_map
, map_size
, res_id
,
326 static gpointer
map_pe_file (gunichar2
*filename
, guint32
*map_size
)
333 /* According to the MSDN docs, a search path is applied to
334 * filename. FIXME: implement this, for now just pass it
338 filename_ext
= mono_unicode_to_external (filename
);
339 if (filename_ext
== NULL
) {
341 g_message ("%s: unicode conversion returned NULL", __func__
);
344 SetLastError (ERROR_INVALID_NAME
);
348 fd
= _wapi_open (filename_ext
, O_RDONLY
, 0);
351 g_message ("%s: Error opening file %s: %s", __func__
,
352 filename_ext
, strerror (errno
));
355 SetLastError (_wapi_get_win32_file_error (errno
));
356 g_free (filename_ext
);
361 if (fstat (fd
, &statbuf
) == -1) {
363 g_message ("%s: Error stat()ing file %s: %s", __func__
,
364 filename_ext
, strerror (errno
));
367 SetLastError (_wapi_get_win32_file_error (errno
));
368 g_free (filename_ext
);
372 *map_size
= statbuf
.st_size
;
374 /* Check basic file size */
375 if (statbuf
.st_size
< sizeof(WapiImageDosHeader
)) {
377 g_message ("%s: File %s is too small: %lld", __func__
,
378 filename_ext
, statbuf
.st_size
);
381 SetLastError (ERROR_BAD_LENGTH
);
382 g_free (filename_ext
);
387 file_map
= mmap (NULL
, statbuf
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
388 if (file_map
== MAP_FAILED
) {
390 g_message ("%s: Error mmap()int file %s: %s", __func__
,
391 filename_ext
, strerror (errno
));
394 SetLastError (_wapi_get_win32_file_error (errno
));
395 g_free (filename_ext
);
400 /* Don't need the fd any more */
402 g_free (filename_ext
);
407 static void unmap_pe_file (gpointer file_map
, guint32 map_size
)
409 munmap (file_map
, map_size
);
412 static guint32
unicode_chars (const gunichar2
*str
)
417 if (str
[len
] == '\0') {
424 static gboolean
unicode_compare (const gunichar2
*str1
, const gunichar2
*str2
)
426 while (*str1
&& *str2
) {
427 if (*str1
!= *str2
) {
434 return(*str1
== *str2
);
437 /* compare a little-endian null-terminated utf16 string and a normal string.
438 * Can be used only for ascii or latin1 chars.
440 static gboolean
unicode_string_equals (const gunichar2
*str1
, const gchar
*str2
)
442 while (*str1
&& *str2
) {
443 if (GUINT16_TO_LE (*str1
) != *str2
) {
450 return(*str1
== *str2
);
461 /* Returns a pointer to the value data, because there's no way to know
462 * how big that data is (value_len is set to zero for most blocks :-( )
464 static gconstpointer
get_versioninfo_block (gconstpointer data
,
467 block
->data_len
= GUINT16_FROM_LE (*((guint16
*)data
));
468 data
= (char *)data
+ sizeof(guint16
);
469 block
->value_len
= GUINT16_FROM_LE (*((guint16
*)data
));
470 data
= (char *)data
+ sizeof(guint16
);
472 /* No idea what the type is supposed to indicate */
473 block
->type
= GUINT16_FROM_LE (*((guint16
*)data
));
474 data
= (char *)data
+ sizeof(guint16
);
475 block
->key
= ((gunichar2
*)data
);
477 /* Skip over the key (including the terminator) */
478 data
= ((gunichar2
*)data
) + (unicode_chars (block
->key
) + 1);
480 /* align on a 32-bit boundary */
486 static gconstpointer
get_fixedfileinfo_block (gconstpointer data
,
489 gconstpointer data_ptr
;
490 gint32 data_len
; /* signed to guard against underflow */
491 WapiFixedFileInfo
*ffi
;
493 data_ptr
= get_versioninfo_block (data
, block
);
494 data_len
= block
->data_len
;
496 if (block
->value_len
!= sizeof(WapiFixedFileInfo
)) {
498 g_message ("%s: FIXEDFILEINFO size mismatch", __func__
);
503 if (!unicode_string_equals (block
->key
, "VS_VERSION_INFO")) {
505 g_message ("%s: VS_VERSION_INFO mismatch", __func__
);
510 ffi
= ((WapiFixedFileInfo
*)data_ptr
);
511 if ((ffi
->dwSignature
!= VS_FFI_SIGNATURE
) ||
512 (ffi
->dwStrucVersion
!= VS_FFI_STRUCVERSION
)) {
514 g_message ("%s: FIXEDFILEINFO bad signature", __func__
);
522 static gconstpointer
get_varfileinfo_block (gconstpointer data_ptr
,
525 /* data is pointing at a Var block
527 data_ptr
= get_versioninfo_block (data_ptr
, block
);
532 static gconstpointer
get_string_block (gconstpointer data_ptr
,
533 const gunichar2
*string_key
,
534 gpointer
*string_value
,
535 guint32
*string_value_len
,
538 guint16 data_len
= block
->data_len
;
539 guint16 string_len
= 28; /* Length of the StringTable block */
540 char *orig_data_ptr
= (char *)data_ptr
- 28;
542 /* data_ptr is pointing at an array of one or more String blocks
543 * with total length (not including alignment padding) of
546 while (((char *)data_ptr
- (char *)orig_data_ptr
) < data_len
) {
547 /* align on a 32-bit boundary */
550 data_ptr
= get_versioninfo_block (data_ptr
, block
);
551 if (block
->data_len
== 0) {
552 /* We must have hit padding, so give up
556 g_message ("%s: Hit 0-length block, giving up",
562 string_len
= string_len
+ block
->data_len
;
564 if (string_key
!= NULL
&&
565 string_value
!= NULL
&&
566 string_value_len
!= NULL
&&
567 unicode_compare (string_key
, block
->key
) == TRUE
) {
568 *string_value
= (gpointer
)data_ptr
;
569 *string_value_len
= block
->value_len
;
572 /* Skip over the value */
573 data_ptr
= ((gunichar2
*)data_ptr
) + block
->value_len
;
579 /* Returns a pointer to the byte following the Stringtable block, or
580 * NULL if the data read hits padding. We can't recover from this
581 * because the data length does not include padding bytes, so it's not
582 * possible to just return the start position + length
584 * If lang == NULL it means we're just stepping through this block
586 static gconstpointer
get_stringtable_block (gconstpointer data_ptr
,
588 const gunichar2
*string_key
,
589 gpointer
*string_value
,
590 guint32
*string_value_len
,
593 guint16 data_len
= block
->data_len
;
594 guint16 string_len
= 36; /* length of the StringFileInfo block */
596 gchar
*lowercase_lang
;
598 /* data_ptr is pointing at an array of StringTable blocks,
599 * with total length (not including alignment padding) of
603 while(string_len
< data_len
) {
604 /* align on a 32-bit boundary */
607 data_ptr
= get_versioninfo_block (data_ptr
, block
);
608 if (block
->data_len
== 0) {
609 /* We must have hit padding, so give up
613 g_message ("%s: Hit 0-length block, giving up",
619 string_len
= string_len
+ block
->data_len
;
621 found_lang
= g_utf16_to_utf8 (block
->key
, 8, NULL
, NULL
, NULL
);
622 if (found_lang
== NULL
) {
624 g_message ("%s: Didn't find a valid language key, giving up", __func__
);
629 lowercase_lang
= g_utf8_strdown (found_lang
, -1);
631 found_lang
= lowercase_lang
;
632 lowercase_lang
= NULL
;
634 if (lang
!= NULL
&& !strcmp (found_lang
, lang
)) {
635 /* Got the one we're interested in */
636 data_ptr
= get_string_block (data_ptr
, string_key
,
638 string_value_len
, block
);
640 data_ptr
= get_string_block (data_ptr
, NULL
, NULL
,
646 if (data_ptr
== NULL
) {
647 /* Child block hit padding */
649 g_message ("%s: Child block hit 0-length block, giving up", __func__
);
658 #if G_BYTE_ORDER == G_BIG_ENDIAN
659 static gconstpointer
big_up_string_block (gconstpointer data_ptr
,
662 guint16 data_len
= block
->data_len
;
663 guint16 string_len
= 28; /* Length of the StringTable block */
665 char *orig_data_ptr
= (char *)data_ptr
- 28;
667 /* data_ptr is pointing at an array of one or more String
668 * blocks with total length (not including alignment padding)
671 while (((char *)data_ptr
- (char *)orig_data_ptr
) < data_len
) {
672 /* align on a 32-bit boundary */
675 data_ptr
= get_versioninfo_block (data_ptr
, block
);
676 if (block
->data_len
== 0) {
677 /* We must have hit padding, so give up
681 g_message ("%s: Hit 0-length block, giving up",
687 string_len
= string_len
+ block
->data_len
;
689 big_value
= g_convert ((gchar
*)block
->key
,
690 unicode_chars (block
->key
) * 2,
691 "UTF-16BE", "UTF-16LE", NULL
, NULL
,
693 if (big_value
== NULL
) {
695 g_message ("%s: Didn't find a valid string, giving up",
701 /* The swapped string should be exactly the same
702 * length as the original little-endian one, but only
703 * copy the number of original chars just to be on the
706 memcpy (block
->key
, big_value
, unicode_chars (block
->key
) * 2);
709 big_value
= g_convert ((gchar
*)data_ptr
,
710 unicode_chars (data_ptr
) * 2,
711 "UTF-16BE", "UTF-16LE", NULL
, NULL
,
713 if (big_value
== NULL
) {
715 g_message ("%s: Didn't find a valid data string, giving up", __func__
);
719 memcpy ((gpointer
)data_ptr
, big_value
,
720 unicode_chars (data_ptr
) * 2);
723 data_ptr
= ((gunichar2
*)data_ptr
) + block
->value_len
;
729 /* Returns a pointer to the byte following the Stringtable block, or
730 * NULL if the data read hits padding. We can't recover from this
731 * because the data length does not include padding bytes, so it's not
732 * possible to just return the start position + length
734 static gconstpointer
big_up_stringtable_block (gconstpointer data_ptr
,
737 guint16 data_len
= block
->data_len
;
738 guint16 string_len
= 36; /* length of the StringFileInfo block */
741 /* data_ptr is pointing at an array of StringTable blocks,
742 * with total length (not including alignment padding) of
746 while(string_len
< data_len
) {
747 /* align on a 32-bit boundary */
750 data_ptr
= get_versioninfo_block (data_ptr
, block
);
751 if (block
->data_len
== 0) {
752 /* We must have hit padding, so give up
756 g_message ("%s: Hit 0-length block, giving up",
762 string_len
= string_len
+ block
->data_len
;
764 big_value
= g_convert ((gchar
*)block
->key
, 16, "UTF-16BE",
765 "UTF-16LE", NULL
, NULL
, NULL
);
766 if (big_value
== NULL
) {
768 g_message ("%s: Didn't find a valid string, giving up",
774 memcpy (block
->key
, big_value
, 16);
777 data_ptr
= big_up_string_block (data_ptr
, block
);
779 if (data_ptr
== NULL
) {
780 /* Child block hit padding */
782 g_message ("%s: Child block hit 0-length block, giving up", __func__
);
791 /* Follows the data structures and turns all UTF-16 strings from the
792 * LE found in the resource section into UTF-16BE
794 static void big_up (gconstpointer datablock
, guint32 size
)
796 gconstpointer data_ptr
;
797 gint32 data_len
; /* signed to guard against underflow */
800 data_ptr
= get_fixedfileinfo_block (datablock
, &block
);
801 if (data_ptr
!= NULL
) {
802 WapiFixedFileInfo
*ffi
= (WapiFixedFileInfo
*)data_ptr
;
804 /* Byteswap all the fields */
805 ffi
->dwFileVersionMS
= GUINT32_SWAP_LE_BE (ffi
->dwFileVersionMS
);
806 ffi
->dwFileVersionLS
= GUINT32_SWAP_LE_BE (ffi
->dwFileVersionLS
);
807 ffi
->dwProductVersionMS
= GUINT32_SWAP_LE_BE (ffi
->dwProductVersionMS
);
808 ffi
->dwProductVersionLS
= GUINT32_SWAP_LE_BE (ffi
->dwProductVersionLS
);
809 ffi
->dwFileFlagsMask
= GUINT32_SWAP_LE_BE (ffi
->dwFileFlagsMask
);
810 ffi
->dwFileFlags
= GUINT32_SWAP_LE_BE (ffi
->dwFileFlags
);
811 ffi
->dwFileOS
= GUINT32_SWAP_LE_BE (ffi
->dwFileOS
);
812 ffi
->dwFileType
= GUINT32_SWAP_LE_BE (ffi
->dwFileType
);
813 ffi
->dwFileSubtype
= GUINT32_SWAP_LE_BE (ffi
->dwFileSubtype
);
814 ffi
->dwFileDateMS
= GUINT32_SWAP_LE_BE (ffi
->dwFileDateMS
);
815 ffi
->dwFileDateLS
= GUINT32_SWAP_LE_BE (ffi
->dwFileDateLS
);
817 /* The FFI and header occupies the first 92 bytes
819 data_ptr
= (char *)data_ptr
+ sizeof(WapiFixedFileInfo
);
820 data_len
= block
.data_len
- 92;
822 /* There now follow zero or one StringFileInfo blocks
823 * and zero or one VarFileInfo blocks
825 while (data_len
> 0) {
826 /* align on a 32-bit boundary */
829 data_ptr
= get_versioninfo_block (data_ptr
, &block
);
830 if (block
.data_len
== 0) {
831 /* We must have hit padding, so give
835 g_message ("%s: Hit 0-length block, giving up",
841 data_len
= data_len
- block
.data_len
;
843 if (unicode_string_equals (block
.key
, "VarFileInfo")) {
844 data_ptr
= get_varfileinfo_block (data_ptr
,
846 data_ptr
= ((guchar
*)data_ptr
) + block
.value_len
;
847 } else if (unicode_string_equals (block
.key
,
849 data_ptr
= big_up_stringtable_block (data_ptr
,
854 g_message ("%s: Not a valid VERSIONINFO child block", __func__
);
859 if (data_ptr
== NULL
) {
860 /* Child block hit padding */
862 g_message ("%s: Child block hit 0-length block, giving up", __func__
);
871 gboolean
VerQueryValue (gconstpointer datablock
, const gunichar2
*subblock
,
872 gpointer
*buffer
, guint32
*len
)
874 gchar
*subblock_utf8
, *lang_utf8
= NULL
;
875 gboolean ret
= FALSE
;
877 gconstpointer data_ptr
;
878 gint32 data_len
; /* signed to guard against underflow */
879 gboolean want_var
= FALSE
;
880 gboolean want_string
= FALSE
;
882 const gunichar2
*string_key
= NULL
;
883 gpointer string_value
= NULL
;
884 guint32 string_value_len
= 0;
885 gchar
*lowercase_lang
;
887 subblock_utf8
= g_utf16_to_utf8 (subblock
, -1, NULL
, NULL
, NULL
);
888 if (subblock_utf8
== NULL
) {
892 if (!strcmp (subblock_utf8
, "\\VarFileInfo\\Translation")) {
894 } else if (!strncmp (subblock_utf8
, "\\StringFileInfo\\", 16)) {
896 memcpy (lang
, subblock
+ 16, 8 * sizeof(gunichar2
));
897 lang_utf8
= g_utf16_to_utf8 (lang
, 8, NULL
, NULL
, NULL
);
898 lowercase_lang
= g_utf8_strdown (lang_utf8
, -1);
900 lang_utf8
= lowercase_lang
;
901 lowercase_lang
= NULL
;
902 string_key
= subblock
+ 25;
905 if (!strcmp (subblock_utf8
, "\\")) {
906 data_ptr
= get_fixedfileinfo_block (datablock
, &block
);
907 if (data_ptr
!= NULL
) {
908 *buffer
= (gpointer
)data_ptr
;
909 *len
= block
.value_len
;
913 } else if (want_var
|| want_string
) {
914 data_ptr
= get_fixedfileinfo_block (datablock
, &block
);
915 if (data_ptr
!= NULL
) {
916 /* The FFI and header occupies the first 92
919 data_ptr
= (char *)data_ptr
+ sizeof(WapiFixedFileInfo
);
920 data_len
= block
.data_len
- 92;
922 /* There now follow zero or one StringFileInfo
923 * blocks and zero or one VarFileInfo blocks
925 while (data_len
> 0) {
926 /* align on a 32-bit boundary */
929 data_ptr
= get_versioninfo_block (data_ptr
,
931 if (block
.data_len
== 0) {
932 /* We must have hit padding,
933 * so give up processing now
936 g_message ("%s: Hit 0-length block, giving up", __func__
);
941 data_len
= data_len
- block
.data_len
;
943 if (unicode_string_equals (block
.key
, "VarFileInfo")) {
944 data_ptr
= get_varfileinfo_block (data_ptr
, &block
);
946 *buffer
= (gpointer
)data_ptr
;
947 *len
= block
.value_len
;
951 /* Skip over the Var block */
952 data_ptr
= ((guchar
*)data_ptr
) + block
.value_len
;
954 } else if (unicode_string_equals (block
.key
, "StringFileInfo")) {
955 data_ptr
= get_stringtable_block (data_ptr
, lang_utf8
, string_key
, &string_value
, &string_value_len
, &block
);
957 string_value
!= NULL
&&
958 string_value_len
!= 0) {
959 *buffer
= string_value
;
960 *len
= unicode_chars (string_value
) + 1; /* Include trailing null */
967 g_message ("%s: Not a valid VERSIONINFO child block", __func__
);
972 if (data_ptr
== NULL
) {
973 /* Child block hit padding */
975 g_message ("%s: Child block hit 0-length block, giving up", __func__
);
988 g_free (subblock_utf8
);
992 guint32
GetFileVersionInfoSize (gunichar2
*filename
, guint32
*handle
)
995 gpointer versioninfo
;
999 /* This value is unused, but set to zero */
1002 file_map
= map_pe_file (filename
, &map_size
);
1003 if (file_map
== NULL
) {
1007 versioninfo
= find_pe_file_resources (file_map
, map_size
, RT_VERSION
,
1009 if (versioninfo
== NULL
) {
1010 /* Didn't find the resource, so set the return value
1016 unmap_pe_file (file_map
, map_size
);
1021 gboolean
GetFileVersionInfo (gunichar2
*filename
, guint32 handle G_GNUC_UNUSED
,
1022 guint32 len
, gpointer data
)
1025 gpointer versioninfo
;
1028 gboolean ret
= FALSE
;
1030 file_map
= map_pe_file (filename
, &map_size
);
1031 if (file_map
== NULL
) {
1035 versioninfo
= find_pe_file_resources (file_map
, map_size
, RT_VERSION
,
1037 if (versioninfo
!= NULL
) {
1038 /* This could probably process the data so that
1039 * VerQueryValue() doesn't have to follow the data
1040 * blocks every time. But hey, these functions aren't
1041 * likely to appear in many profiles.
1043 memcpy (data
, versioninfo
, len
< size
?len
:size
);
1046 #if G_BYTE_ORDER == G_BIG_ENDIAN
1047 big_up (data
, size
);
1051 unmap_pe_file (file_map
, map_size
);
1056 static guint32
copy_lang (gunichar2
*lang_out
, guint32 lang_len
,
1060 int chars
= strlen (text
);
1063 unitext
= g_utf8_to_utf16 (text
, -1, NULL
, NULL
, NULL
);
1064 g_assert (unitext
!= NULL
);
1066 if (chars
< (lang_len
- 1)) {
1067 memcpy (lang_out
, (gpointer
)unitext
, chars
* 2);
1068 lang_out
[chars
] = '\0';
1071 memcpy (lang_out
, (gpointer
)unitext
, (lang_len
- 1) * 2);
1072 lang_out
[lang_len
] = '\0';
1081 guint32
VerLanguageName (guint32 lang
, gunichar2
*lang_out
, guint32 lang_len
)
1083 int primary
, secondary
;
1085 primary
= lang
& 0x3FF;
1086 secondary
= (lang
>> 10) & 0x3F;
1092 return(copy_lang (lang_out
, lang_len
, "Process Default Language"));
1100 return(copy_lang (lang_out
, lang_len
, "Arabic (Saudi Arabia)"));
1103 return(copy_lang (lang_out
, lang_len
, "Arabic (Iraq)"));
1106 return(copy_lang (lang_out
, lang_len
, "Arabic (Egypt)"));
1109 return(copy_lang (lang_out
, lang_len
, "Arabic (Libya)"));
1112 return(copy_lang (lang_out
, lang_len
, "Arabic (Algeria)"));
1115 return(copy_lang (lang_out
, lang_len
, "Arabic (Morocco)"));
1118 return(copy_lang (lang_out
, lang_len
, "Arabic (Tunisia)"));
1121 return(copy_lang (lang_out
, lang_len
, "Arabic (Oman)"));
1124 return(copy_lang (lang_out
, lang_len
, "Arabic (Yemen)"));
1127 return(copy_lang (lang_out
, lang_len
, "Arabic (Syria)"));
1130 return(copy_lang (lang_out
, lang_len
, "Arabic (Jordan)"));
1133 return(copy_lang (lang_out
, lang_len
, "Arabic (Lebanon)"));
1136 return(copy_lang (lang_out
, lang_len
, "Arabic (Kuwait)"));
1139 return(copy_lang (lang_out
, lang_len
, "Arabic (U.A.E.)"));
1142 return(copy_lang (lang_out
, lang_len
, "Arabic (Bahrain)"));
1145 return(copy_lang (lang_out
, lang_len
, "Arabic (Qatar)"));
1152 return(copy_lang (lang_out
, lang_len
, "Bulgarian (Bulgaria)"));
1155 return(copy_lang (lang_out
, lang_len
, "Bulgarian"));
1162 return(copy_lang (lang_out
, lang_len
, "Catalan (Spain)"));
1165 return(copy_lang (lang_out
, lang_len
, "Catalan"));
1173 return(copy_lang (lang_out
, lang_len
, "Chinese (Taiwan)"));
1176 return(copy_lang (lang_out
, lang_len
, "Chinese (PRC)"));
1179 return(copy_lang (lang_out
, lang_len
, "Chinese (Hong Kong S.A.R.)"));
1182 return(copy_lang (lang_out
, lang_len
, "Chinese (Singapore)"));
1185 return(copy_lang (lang_out
, lang_len
, "Chinese (Macau S.A.R.)"));
1192 return(copy_lang (lang_out
, lang_len
, "Czech (Czech Republic)"));
1195 return(copy_lang (lang_out
, lang_len
, "Czech"));
1202 return(copy_lang (lang_out
, lang_len
, "Danish (Denmark)"));
1205 return(copy_lang (lang_out
, lang_len
, "Danish"));
1213 return(copy_lang (lang_out
, lang_len
, "German (Germany)"));
1216 return(copy_lang (lang_out
, lang_len
, "German (Switzerland)"));
1219 return(copy_lang (lang_out
, lang_len
, "German (Austria)"));
1222 return(copy_lang (lang_out
, lang_len
, "German (Luxembourg)"));
1225 return(copy_lang (lang_out
, lang_len
, "German (Liechtenstein)"));
1232 return(copy_lang (lang_out
, lang_len
, "Greek (Greece)"));
1235 return(copy_lang (lang_out
, lang_len
, "Greek"));
1243 return(copy_lang (lang_out
, lang_len
, "English (United States)"));
1246 return(copy_lang (lang_out
, lang_len
, "English (United Kingdom)"));
1249 return(copy_lang (lang_out
, lang_len
, "English (Australia)"));
1252 return(copy_lang (lang_out
, lang_len
, "English (Canada)"));
1255 return(copy_lang (lang_out
, lang_len
, "English (New Zealand)"));
1258 return(copy_lang (lang_out
, lang_len
, "English (Ireland)"));
1261 return(copy_lang (lang_out
, lang_len
, "English (South Africa)"));
1264 return(copy_lang (lang_out
, lang_len
, "English (Jamaica)"));
1267 return(copy_lang (lang_out
, lang_len
, "English (Caribbean)"));
1270 return(copy_lang (lang_out
, lang_len
, "English (Belize)"));
1273 return(copy_lang (lang_out
, lang_len
, "English (Trinidad and Tobago)"));
1276 return(copy_lang (lang_out
, lang_len
, "English (Zimbabwe)"));
1279 return(copy_lang (lang_out
, lang_len
, "English (Philippines)"));
1282 return(copy_lang (lang_out
, lang_len
, "English (India)"));
1285 return(copy_lang (lang_out
, lang_len
, "English (Malaysia)"));
1288 return(copy_lang (lang_out
, lang_len
, "English (Singapore)"));
1295 return(copy_lang (lang_out
, lang_len
, "Spanish (Spain)"));
1298 return(copy_lang (lang_out
, lang_len
, "Spanish (Traditional Sort)"));
1301 return(copy_lang (lang_out
, lang_len
, "Spanish (Mexico)"));
1304 return(copy_lang (lang_out
, lang_len
, "Spanish (International Sort)"));
1307 return(copy_lang (lang_out
, lang_len
, "Spanish (Guatemala)"));
1310 return(copy_lang (lang_out
, lang_len
, "Spanish (Costa Rica)"));
1313 return(copy_lang (lang_out
, lang_len
, "Spanish (Panama)"));
1316 return(copy_lang (lang_out
, lang_len
, "Spanish (Dominican Republic)"));
1319 return(copy_lang (lang_out
, lang_len
, "Spanish (Venezuela)"));
1322 return(copy_lang (lang_out
, lang_len
, "Spanish (Colombia)"));
1325 return(copy_lang (lang_out
, lang_len
, "Spanish (Peru)"));
1328 return(copy_lang (lang_out
, lang_len
, "Spanish (Argentina)"));
1331 return(copy_lang (lang_out
, lang_len
, "Spanish (Ecuador)"));
1334 return(copy_lang (lang_out
, lang_len
, "Spanish (Chile)"));
1337 return(copy_lang (lang_out
, lang_len
, "Spanish (Uruguay)"));
1340 return(copy_lang (lang_out
, lang_len
, "Spanish (Paraguay)"));
1343 return(copy_lang (lang_out
, lang_len
, "Spanish (Bolivia)"));
1346 return(copy_lang (lang_out
, lang_len
, "Spanish (El Salvador)"));
1349 return(copy_lang (lang_out
, lang_len
, "Spanish (Honduras)"));
1352 return(copy_lang (lang_out
, lang_len
, "Spanish (Nicaragua)"));
1355 return(copy_lang (lang_out
, lang_len
, "Spanish (Puerto Rico)"));
1358 return(copy_lang (lang_out
, lang_len
, "Spanish (United States)"));
1365 return(copy_lang (lang_out
, lang_len
, "Finnish (Finland)"));
1368 return(copy_lang (lang_out
, lang_len
, "Finnish"));
1376 return(copy_lang (lang_out
, lang_len
, "French (France)"));
1379 return(copy_lang (lang_out
, lang_len
, "French (Belgium)"));
1382 return(copy_lang (lang_out
, lang_len
, "French (Canada)"));
1385 return(copy_lang (lang_out
, lang_len
, "French (Switzerland)"));
1388 return(copy_lang (lang_out
, lang_len
, "French (Luxembourg)"));
1391 return(copy_lang (lang_out
, lang_len
, "French (Monaco)"));
1398 return(copy_lang (lang_out
, lang_len
, "Hebrew (Israel)"));
1401 return(copy_lang (lang_out
, lang_len
, "Hebrew"));
1408 return(copy_lang (lang_out
, lang_len
, "Hungarian (Hungary)"));
1411 return(copy_lang (lang_out
, lang_len
, "Hungarian"));
1418 return(copy_lang (lang_out
, lang_len
, "Icelandic (Iceland)"));
1421 return(copy_lang (lang_out
, lang_len
, "Icelandic"));
1429 return(copy_lang (lang_out
, lang_len
, "Italian (Italy)"));
1432 return(copy_lang (lang_out
, lang_len
, "Italian (Switzerland)"));
1439 return(copy_lang (lang_out
, lang_len
, "Japanese (Japan)"));
1442 return(copy_lang (lang_out
, lang_len
, "Japanese"));
1449 return(copy_lang (lang_out
, lang_len
, "Korean (Korea)"));
1452 return(copy_lang (lang_out
, lang_len
, "Korean"));
1460 return(copy_lang (lang_out
, lang_len
, "Dutch (Netherlands)"));
1463 return(copy_lang (lang_out
, lang_len
, "Dutch (Belgium)"));
1471 return(copy_lang (lang_out
, lang_len
, "Norwegian (Bokmal)"));
1474 return(copy_lang (lang_out
, lang_len
, "Norwegian (Nynorsk)"));
1481 return(copy_lang (lang_out
, lang_len
, "Polish (Poland)"));
1484 return(copy_lang (lang_out
, lang_len
, "Polish"));
1492 return(copy_lang (lang_out
, lang_len
, "Portuguese (Brazil)"));
1495 return(copy_lang (lang_out
, lang_len
, "Portuguese (Portugal)"));
1502 return(copy_lang (lang_out
, lang_len
, "Romansh (Switzerland)"));
1509 return(copy_lang (lang_out
, lang_len
, "Romanian (Romania)"));
1512 return(copy_lang (lang_out
, lang_len
, "Romanian"));
1519 return(copy_lang (lang_out
, lang_len
, "Russian (Russia)"));
1522 return(copy_lang (lang_out
, lang_len
, "Russian"));
1529 return(copy_lang (lang_out
, lang_len
, "Croatian (Croatia)"));
1532 return(copy_lang (lang_out
, lang_len
, "Croatian"));
1535 return(copy_lang (lang_out
, lang_len
, "Serbian (Latin)"));
1538 return(copy_lang (lang_out
, lang_len
, "Serbian (Cyrillic)"));
1541 return(copy_lang (lang_out
, lang_len
, "Croatian (Bosnia and Herzegovina)"));
1544 return(copy_lang (lang_out
, lang_len
, "Bosnian (Latin, Bosnia and Herzegovina)"));
1547 return(copy_lang (lang_out
, lang_len
, "Serbian (Latin, Bosnia and Herzegovina)"));
1550 return(copy_lang (lang_out
, lang_len
, "Serbian (Cyrillic, Bosnia and Herzegovina)"));
1553 return(copy_lang (lang_out
, lang_len
, "Bosnian (Cyrillic, Bosnia and Herzegovina)"));
1560 return(copy_lang (lang_out
, lang_len
, "Slovak (Slovakia)"));
1563 return(copy_lang (lang_out
, lang_len
, "Slovak"));
1570 return(copy_lang (lang_out
, lang_len
, "Albanian (Albania)"));
1573 return(copy_lang (lang_out
, lang_len
, "Albanian"));
1580 return(copy_lang (lang_out
, lang_len
, "Swedish (Sweden)"));
1583 return(copy_lang (lang_out
, lang_len
, "Swedish"));
1586 return(copy_lang (lang_out
, lang_len
, "Swedish (Finland)"));
1593 return(copy_lang (lang_out
, lang_len
, "Thai (Thailand)"));
1596 return(copy_lang (lang_out
, lang_len
, "Thai"));
1603 return(copy_lang (lang_out
, lang_len
, "Turkish (Turkey)"));
1606 return(copy_lang (lang_out
, lang_len
, "Turkish"));
1613 return(copy_lang (lang_out
, lang_len
, "Urdu (Islamic Republic of Pakistan)"));
1616 return(copy_lang (lang_out
, lang_len
, "Urdu"));
1623 return(copy_lang (lang_out
, lang_len
, "Indonesian (Indonesia)"));
1626 return(copy_lang (lang_out
, lang_len
, "Indonesian"));
1633 return(copy_lang (lang_out
, lang_len
, "Ukrainian (Ukraine)"));
1636 return(copy_lang (lang_out
, lang_len
, "Ukrainian"));
1643 return(copy_lang (lang_out
, lang_len
, "Belarusian (Belarus)"));
1646 return(copy_lang (lang_out
, lang_len
, "Belarusian"));
1653 return(copy_lang (lang_out
, lang_len
, "Slovenian (Slovenia)"));
1656 return(copy_lang (lang_out
, lang_len
, "Slovenian"));
1663 return(copy_lang (lang_out
, lang_len
, "Estonian (Estonia)"));
1666 return(copy_lang (lang_out
, lang_len
, "Estonian"));
1673 return(copy_lang (lang_out
, lang_len
, "Latvian (Latvia)"));
1676 return(copy_lang (lang_out
, lang_len
, "Latvian"));
1683 return(copy_lang (lang_out
, lang_len
, "Lithuanian (Lithuania)"));
1686 return(copy_lang (lang_out
, lang_len
, "Lithuanian"));
1693 return(copy_lang (lang_out
, lang_len
, "Tajik (Tajikistan)"));
1700 return(copy_lang (lang_out
, lang_len
, "Farsi (Iran)"));
1703 return(copy_lang (lang_out
, lang_len
, "Farsi"));
1710 return(copy_lang (lang_out
, lang_len
, "Vietnamese (Viet Nam)"));
1713 return(copy_lang (lang_out
, lang_len
, "Vietnamese"));
1720 return(copy_lang (lang_out
, lang_len
, "Armenian (Armenia)"));
1723 return(copy_lang (lang_out
, lang_len
, "Armenian"));
1730 return(copy_lang (lang_out
, lang_len
, "Azeri (Latin) (Azerbaijan)"));
1733 return(copy_lang (lang_out
, lang_len
, "Azeri (Latin)"));
1736 return(copy_lang (lang_out
, lang_len
, "Azeri (Cyrillic)"));
1743 return(copy_lang (lang_out
, lang_len
, "Basque (Spain)"));
1746 return(copy_lang (lang_out
, lang_len
, "Basque"));
1753 return(copy_lang (lang_out
, lang_len
, "Upper Sorbian (Germany)"));
1756 return(copy_lang (lang_out
, lang_len
, "Lower Sorbian (Germany)"));
1763 return(copy_lang (lang_out
, lang_len
, "FYRO Macedonian (Former Yugoslav Republic of Macedonia)"));
1766 return(copy_lang (lang_out
, lang_len
, "FYRO Macedonian"));
1773 return(copy_lang (lang_out
, lang_len
, "Tswana (South Africa)"));
1776 return(copy_lang (lang_out
, lang_len
, "Tswana"));
1783 return(copy_lang (lang_out
, lang_len
, "Xhosa (South Africa)"));
1786 return(copy_lang (lang_out
, lang_len
, "Xhosa"));
1793 return(copy_lang (lang_out
, lang_len
, "Zulu (South Africa)"));
1796 return(copy_lang (lang_out
, lang_len
, "Zulu"));
1803 return(copy_lang (lang_out
, lang_len
, "Afrikaans (South Africa)"));
1806 return(copy_lang (lang_out
, lang_len
, "Afrikaans"));
1813 return(copy_lang (lang_out
, lang_len
, "Georgian (Georgia)"));
1816 return(copy_lang (lang_out
, lang_len
, "Georgian"));
1823 return(copy_lang (lang_out
, lang_len
, "Faroese (Faroe Islands)"));
1826 return(copy_lang (lang_out
, lang_len
, "Faroese"));
1833 return(copy_lang (lang_out
, lang_len
, "Hindi (India)"));
1836 return(copy_lang (lang_out
, lang_len
, "Hindi"));
1843 return(copy_lang (lang_out
, lang_len
, "Maltese (Malta)"));
1846 return(copy_lang (lang_out
, lang_len
, "Maltese"));
1853 return(copy_lang (lang_out
, lang_len
, "Sami (Northern) (Norway)"));
1856 return(copy_lang (lang_out
, lang_len
, "Sami, Northern (Norway)"));
1859 return(copy_lang (lang_out
, lang_len
, "Sami, Northern (Sweden)"));
1862 return(copy_lang (lang_out
, lang_len
, "Sami, Northern (Finland)"));
1865 return(copy_lang (lang_out
, lang_len
, "Sami, Lule (Norway)"));
1868 return(copy_lang (lang_out
, lang_len
, "Sami, Lule (Sweden)"));
1871 return(copy_lang (lang_out
, lang_len
, "Sami, Southern (Norway)"));
1874 return(copy_lang (lang_out
, lang_len
, "Sami, Southern (Sweden)"));
1877 return(copy_lang (lang_out
, lang_len
, "Sami, Skolt (Finland)"));
1880 return(copy_lang (lang_out
, lang_len
, "Sami, Inari (Finland)"));
1887 return(copy_lang (lang_out
, lang_len
, "Irish (Ireland)"));
1895 return(copy_lang (lang_out
, lang_len
, "Malay (Malaysia)"));
1898 return(copy_lang (lang_out
, lang_len
, "Malay (Brunei Darussalam)"));
1905 return(copy_lang (lang_out
, lang_len
, "Kazakh (Kazakhstan)"));
1908 return(copy_lang (lang_out
, lang_len
, "Kazakh"));
1915 return(copy_lang (lang_out
, lang_len
, "Kyrgyz (Kyrgyzstan)"));
1918 return(copy_lang (lang_out
, lang_len
, "Kyrgyz (Cyrillic)"));
1925 return(copy_lang (lang_out
, lang_len
, "Swahili (Kenya)"));
1928 return(copy_lang (lang_out
, lang_len
, "Swahili"));
1935 return(copy_lang (lang_out
, lang_len
, "Turkmen (Turkmenistan)"));
1942 return(copy_lang (lang_out
, lang_len
, "Uzbek (Latin) (Uzbekistan)"));
1945 return(copy_lang (lang_out
, lang_len
, "Uzbek (Latin)"));
1948 return(copy_lang (lang_out
, lang_len
, "Uzbek (Cyrillic)"));
1955 return(copy_lang (lang_out
, lang_len
, "Tatar (Russia)"));
1958 return(copy_lang (lang_out
, lang_len
, "Tatar"));
1966 return(copy_lang (lang_out
, lang_len
, "Bengali (India)"));
1973 return(copy_lang (lang_out
, lang_len
, "Punjabi (India)"));
1976 return(copy_lang (lang_out
, lang_len
, "Punjabi"));
1983 return(copy_lang (lang_out
, lang_len
, "Gujarati (India)"));
1986 return(copy_lang (lang_out
, lang_len
, "Gujarati"));
1993 return(copy_lang (lang_out
, lang_len
, "Tamil (India)"));
1996 return(copy_lang (lang_out
, lang_len
, "Tamil"));
2003 return(copy_lang (lang_out
, lang_len
, "Telugu (India)"));
2006 return(copy_lang (lang_out
, lang_len
, "Telugu"));
2013 return(copy_lang (lang_out
, lang_len
, "Kannada (India)"));
2016 return(copy_lang (lang_out
, lang_len
, "Kannada"));
2024 return(copy_lang (lang_out
, lang_len
, "Malayalam (India)"));
2031 return(copy_lang (lang_out
, lang_len
, "Assamese (India)"));
2038 return(copy_lang (lang_out
, lang_len
, "Marathi (India)"));
2041 return(copy_lang (lang_out
, lang_len
, "Marathi"));
2048 return(copy_lang (lang_out
, lang_len
, "Sanskrit (India)"));
2051 return(copy_lang (lang_out
, lang_len
, "Sanskrit"));
2058 return(copy_lang (lang_out
, lang_len
, "Mongolian (Mongolia)"));
2061 return(copy_lang (lang_out
, lang_len
, "Mongolian (Cyrillic)"));
2064 return(copy_lang (lang_out
, lang_len
, "Mongolian (PRC)"));
2071 return(copy_lang (lang_out
, lang_len
, "Tibetan (PRC)"));
2074 return(copy_lang (lang_out
, lang_len
, "Tibetan (Bhutan)"));
2081 return(copy_lang (lang_out
, lang_len
, "Welsh (United Kingdom)"));
2084 return(copy_lang (lang_out
, lang_len
, "Welsh"));
2091 return(copy_lang (lang_out
, lang_len
, "Khmer (Cambodia)"));
2098 return(copy_lang (lang_out
, lang_len
, "Lao (Lao PDR)"));
2105 return(copy_lang (lang_out
, lang_len
, "Galician (Spain)"));
2108 return(copy_lang (lang_out
, lang_len
, "Galician"));
2115 return(copy_lang (lang_out
, lang_len
, "Konkani (India)"));
2118 return(copy_lang (lang_out
, lang_len
, "Konkani"));
2125 return(copy_lang (lang_out
, lang_len
, "Syriac (Syria)"));
2128 return(copy_lang (lang_out
, lang_len
, "Syriac"));
2135 return(copy_lang (lang_out
, lang_len
, "Sinhala (Sri Lanka)"));
2142 return(copy_lang (lang_out
, lang_len
, "Inuktitut (Syllabics, Canada)"));
2145 return(copy_lang (lang_out
, lang_len
, "Inuktitut (Latin, Canada)"));
2152 return(copy_lang (lang_out
, lang_len
, "Amharic (Ethiopia)"));
2159 return(copy_lang (lang_out
, lang_len
, "Tamazight (Algeria, Latin)"));
2166 return(copy_lang (lang_out
, lang_len
, "Nepali (Nepal)"));
2173 return(copy_lang (lang_out
, lang_len
, "Frisian (Netherlands)"));
2180 return(copy_lang (lang_out
, lang_len
, "Pashto (Afghanistan)"));
2187 return(copy_lang (lang_out
, lang_len
, "Filipino (Philippines)"));
2194 return(copy_lang (lang_out
, lang_len
, "Divehi (Maldives)"));
2197 return(copy_lang (lang_out
, lang_len
, "Divehi"));
2204 return(copy_lang (lang_out
, lang_len
, "Hausa (Nigeria, Latin)"));
2211 return(copy_lang (lang_out
, lang_len
, "Yoruba (Nigeria)"));
2219 return(copy_lang (lang_out
, lang_len
, "Quechua (Bolivia)"));
2222 return(copy_lang (lang_out
, lang_len
, "Quechua (Ecuador)"));
2225 return(copy_lang (lang_out
, lang_len
, "Quechua (Peru)"));
2232 return(copy_lang (lang_out
, lang_len
, "Northern Sotho (South Africa)"));
2235 return(copy_lang (lang_out
, lang_len
, "Northern Sotho"));
2242 return(copy_lang (lang_out
, lang_len
, "Bashkir (Russia)"));
2249 return(copy_lang (lang_out
, lang_len
, "Luxembourgish (Luxembourg)"));
2256 return(copy_lang (lang_out
, lang_len
, "Greenlandic (Greenland)"));
2263 return(copy_lang (lang_out
, lang_len
, "Yi (PRC)"));
2270 return(copy_lang (lang_out
, lang_len
, "Mapudungun (Chile)"));
2277 return(copy_lang (lang_out
, lang_len
, "Mohawk (Mohawk)"));
2284 return(copy_lang (lang_out
, lang_len
, "Breton (France)"));
2291 return(copy_lang (lang_out
, lang_len
, "Invariant Language (Invariant Country)"));
2298 return(copy_lang (lang_out
, lang_len
, "Uighur (PRC)"));
2305 return(copy_lang (lang_out
, lang_len
, "Maori (New Zealand)"));
2308 return(copy_lang (lang_out
, lang_len
, "Maori"));
2315 return(copy_lang (lang_out
, lang_len
, "Corsican (France)"));
2322 return(copy_lang (lang_out
, lang_len
, "Alsatian (France)"));
2329 return(copy_lang (lang_out
, lang_len
, "Yakut (Russia)"));
2336 return(copy_lang (lang_out
, lang_len
, "K'iche (Guatemala)"));
2343 return(copy_lang (lang_out
, lang_len
, "Kinyarwanda (Rwanda)"));
2350 return(copy_lang (lang_out
, lang_len
, "Wolof (Senegal)"));
2357 return(copy_lang (lang_out
, lang_len
, "Dari (Afghanistan)"));
2363 return(copy_lang (lang_out
, lang_len
, "Language Neutral"));
2367 return(copy_lang (lang_out
, lang_len
, "Language Neutral"));