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 */
597 /* data_ptr is pointing at an array of StringTable blocks,
598 * with total length (not including alignment padding) of
602 while(string_len < data_len) {
603 /* align on a 32-bit boundary */
606 data_ptr = get_versioninfo_block (data_ptr, block);
607 if (block->data_len == 0) {
608 /* We must have hit padding, so give up
612 g_message ("%s: Hit 0-length block, giving up",
618 string_len = string_len + block->data_len;
620 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
621 if (found_lang == NULL) {
623 g_message ("%s: Didn't find a valid language key, giving up", __func__);
628 g_strdown (found_lang);
630 if (lang != NULL && !strcmp (found_lang, lang)) {
631 /* Got the one we're interested in */
632 data_ptr = get_string_block (data_ptr, string_key,
634 string_value_len, block);
636 data_ptr = get_string_block (data_ptr, NULL, NULL,
642 if (data_ptr == NULL) {
643 /* Child block hit padding */
645 g_message ("%s: Child block hit 0-length block, giving up", __func__);
654 #if G_BYTE_ORDER == G_BIG_ENDIAN
655 static gconstpointer big_up_string_block (gconstpointer data_ptr,
658 guint16 data_len = block->data_len;
659 guint16 string_len = 28; /* Length of the StringTable block */
661 char *orig_data_ptr = (char *)data_ptr - 28;
663 /* data_ptr is pointing at an array of one or more String
664 * blocks with total length (not including alignment padding)
667 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
668 /* align on a 32-bit boundary */
671 data_ptr = get_versioninfo_block (data_ptr, block);
672 if (block->data_len == 0) {
673 /* We must have hit padding, so give up
677 g_message ("%s: Hit 0-length block, giving up",
683 string_len = string_len + block->data_len;
685 big_value = g_convert ((gchar *)block->key,
686 unicode_chars (block->key) * 2,
687 "UTF-16BE", "UTF-16LE", NULL, NULL,
689 if (big_value == NULL) {
691 g_message ("%s: Didn't find a valid string, giving up",
697 /* The swapped string should be exactly the same
698 * length as the original little-endian one, but only
699 * copy the number of original chars just to be on the
702 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
705 big_value = g_convert ((gchar *)data_ptr,
706 unicode_chars (data_ptr) * 2,
707 "UTF-16BE", "UTF-16LE", NULL, NULL,
709 if (big_value == NULL) {
711 g_message ("%s: Didn't find a valid data string, giving up", __func__);
715 memcpy ((gpointer)data_ptr, big_value,
716 unicode_chars (data_ptr) * 2);
719 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
725 /* Returns a pointer to the byte following the Stringtable block, or
726 * NULL if the data read hits padding. We can't recover from this
727 * because the data length does not include padding bytes, so it's not
728 * possible to just return the start position + length
730 static gconstpointer big_up_stringtable_block (gconstpointer data_ptr,
733 guint16 data_len = block->data_len;
734 guint16 string_len = 36; /* length of the StringFileInfo block */
737 /* data_ptr is pointing at an array of StringTable blocks,
738 * with total length (not including alignment padding) of
742 while(string_len < data_len) {
743 /* align on a 32-bit boundary */
746 data_ptr = get_versioninfo_block (data_ptr, block);
747 if (block->data_len == 0) {
748 /* We must have hit padding, so give up
752 g_message ("%s: Hit 0-length block, giving up",
758 string_len = string_len + block->data_len;
760 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
761 "UTF-16LE", NULL, NULL, NULL);
762 if (big_value == NULL) {
764 g_message ("%s: Didn't find a valid string, giving up",
770 memcpy (block->key, big_value, 16);
773 data_ptr = big_up_string_block (data_ptr, block);
775 if (data_ptr == NULL) {
776 /* Child block hit padding */
778 g_message ("%s: Child block hit 0-length block, giving up", __func__);
787 /* Follows the data structures and turns all UTF-16 strings from the
788 * LE found in the resource section into UTF-16BE
790 static void big_up (gconstpointer datablock, guint32 size)
792 gconstpointer data_ptr;
793 gint32 data_len; /* signed to guard against underflow */
796 data_ptr = get_fixedfileinfo_block (datablock, &block);
797 if (data_ptr != NULL) {
798 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
800 /* Byteswap all the fields */
801 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
802 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
803 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
804 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
805 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
806 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
807 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
808 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
809 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
810 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
811 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
813 /* The FFI and header occupies the first 92 bytes
815 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
816 data_len = block.data_len - 92;
818 /* There now follow zero or one StringFileInfo blocks
819 * and zero or one VarFileInfo blocks
821 while (data_len > 0) {
822 /* align on a 32-bit boundary */
825 data_ptr = get_versioninfo_block (data_ptr, &block);
826 if (block.data_len == 0) {
827 /* We must have hit padding, so give
831 g_message ("%s: Hit 0-length block, giving up",
837 data_len = data_len - block.data_len;
839 if (unicode_string_equals (block.key, "VarFileInfo")) {
840 data_ptr = get_varfileinfo_block (data_ptr,
842 data_ptr = ((guchar *)data_ptr) + block.value_len;
843 } else if (unicode_string_equals (block.key,
845 data_ptr = big_up_stringtable_block (data_ptr,
850 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
855 if (data_ptr == NULL) {
856 /* Child block hit padding */
858 g_message ("%s: Child block hit 0-length block, giving up", __func__);
867 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
868 gpointer *buffer, guint32 *len)
870 gchar *subblock_utf8, *lang_utf8 = NULL;
871 gboolean ret = FALSE;
873 gconstpointer data_ptr;
874 gint32 data_len; /* signed to guard against underflow */
875 gboolean want_var = FALSE;
876 gboolean want_string = FALSE;
878 const gunichar2 *string_key = NULL;
879 gpointer string_value = NULL;
880 guint32 string_value_len = 0;
882 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
883 if (subblock_utf8 == NULL) {
887 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
889 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
891 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
892 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
893 g_strdown (lang_utf8);
894 string_key = subblock + 25;
897 if (!strcmp (subblock_utf8, "\\")) {
898 data_ptr = get_fixedfileinfo_block (datablock, &block);
899 if (data_ptr != NULL) {
900 *buffer = (gpointer)data_ptr;
901 *len = block.value_len;
905 } else if (want_var || want_string) {
906 data_ptr = get_fixedfileinfo_block (datablock, &block);
907 if (data_ptr != NULL) {
908 /* The FFI and header occupies the first 92
911 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
912 data_len = block.data_len - 92;
914 /* There now follow zero or one StringFileInfo
915 * blocks and zero or one VarFileInfo blocks
917 while (data_len > 0) {
918 /* align on a 32-bit boundary */
921 data_ptr = get_versioninfo_block (data_ptr,
923 if (block.data_len == 0) {
924 /* We must have hit padding,
925 * so give up processing now
928 g_message ("%s: Hit 0-length block, giving up", __func__);
933 data_len = data_len - block.data_len;
935 if (unicode_string_equals (block.key, "VarFileInfo")) {
936 data_ptr = get_varfileinfo_block (data_ptr, &block);
938 *buffer = (gpointer)data_ptr;
939 *len = block.value_len;
943 /* Skip over the Var block */
944 data_ptr = ((guchar *)data_ptr) + block.value_len;
946 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
947 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
949 string_value != NULL &&
950 string_value_len != 0) {
951 *buffer = string_value;
952 *len = unicode_chars (string_value) + 1; /* Include trailing null */
959 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
964 if (data_ptr == NULL) {
965 /* Child block hit padding */
967 g_message ("%s: Child block hit 0-length block, giving up", __func__);
980 g_free (subblock_utf8);
984 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
987 gpointer versioninfo;
991 /* This value is unused, but set to zero */
994 file_map = map_pe_file (filename, &map_size);
995 if (file_map == NULL) {
999 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1001 if (versioninfo == NULL) {
1002 /* Didn't find the resource, so set the return value
1008 unmap_pe_file (file_map, map_size);
1013 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
1014 guint32 len, gpointer data)
1017 gpointer versioninfo;
1020 gboolean ret = FALSE;
1022 file_map = map_pe_file (filename, &map_size);
1023 if (file_map == NULL) {
1027 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1029 if (versioninfo != NULL) {
1030 /* This could probably process the data so that
1031 * VerQueryValue() doesn't have to follow the data
1032 * blocks every time. But hey, these functions aren't
1033 * likely to appear in many profiles.
1035 memcpy (data, versioninfo, len < size?len:size);
1038 #if G_BYTE_ORDER == G_BIG_ENDIAN
1039 big_up (data, size);
1043 unmap_pe_file (file_map, map_size);
1048 static guint32 copy_lang (gunichar2 *lang_out, guint32 lang_len,
1052 int chars = strlen (text);
1055 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1056 g_assert (unitext != NULL);
1058 if (chars < (lang_len - 1)) {
1059 memcpy (lang_out, (gpointer)unitext, chars * 2);
1060 lang_out[chars] = '\0';
1063 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1064 lang_out[lang_len] = '\0';
1073 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1075 int primary, secondary;
1077 primary = lang & 0x3FF;
1078 secondary = (lang >> 10) & 0x3F;
1084 return(copy_lang (lang_out, lang_len, "Process Default Language"));
1092 return(copy_lang (lang_out, lang_len, "Arabic (Saudi Arabia)"));
1095 return(copy_lang (lang_out, lang_len, "Arabic (Iraq)"));
1098 return(copy_lang (lang_out, lang_len, "Arabic (Egypt)"));
1101 return(copy_lang (lang_out, lang_len, "Arabic (Libya)"));
1104 return(copy_lang (lang_out, lang_len, "Arabic (Algeria)"));
1107 return(copy_lang (lang_out, lang_len, "Arabic (Morocco)"));
1110 return(copy_lang (lang_out, lang_len, "Arabic (Tunisia)"));
1113 return(copy_lang (lang_out, lang_len, "Arabic (Oman)"));
1116 return(copy_lang (lang_out, lang_len, "Arabic (Yemen)"));
1119 return(copy_lang (lang_out, lang_len, "Arabic (Syria)"));
1122 return(copy_lang (lang_out, lang_len, "Arabic (Jordan)"));
1125 return(copy_lang (lang_out, lang_len, "Arabic (Lebanon)"));
1128 return(copy_lang (lang_out, lang_len, "Arabic (Kuwait)"));
1131 return(copy_lang (lang_out, lang_len, "Arabic (U.A.E.)"));
1134 return(copy_lang (lang_out, lang_len, "Arabic (Bahrain)"));
1137 return(copy_lang (lang_out, lang_len, "Arabic (Qatar)"));
1144 return(copy_lang (lang_out, lang_len, "Bulgarian (Bulgaria)"));
1147 return(copy_lang (lang_out, lang_len, "Bulgarian"));
1154 return(copy_lang (lang_out, lang_len, "Catalan (Spain)"));
1157 return(copy_lang (lang_out, lang_len, "Catalan"));
1165 return(copy_lang (lang_out, lang_len, "Chinese (Taiwan)"));
1168 return(copy_lang (lang_out, lang_len, "Chinese (PRC)"));
1171 return(copy_lang (lang_out, lang_len, "Chinese (Hong Kong S.A.R.)"));
1174 return(copy_lang (lang_out, lang_len, "Chinese (Singapore)"));
1177 return(copy_lang (lang_out, lang_len, "Chinese (Macau S.A.R.)"));
1184 return(copy_lang (lang_out, lang_len, "Czech (Czech Republic)"));
1187 return(copy_lang (lang_out, lang_len, "Czech"));
1194 return(copy_lang (lang_out, lang_len, "Danish (Denmark)"));
1197 return(copy_lang (lang_out, lang_len, "Danish"));
1205 return(copy_lang (lang_out, lang_len, "German (Germany)"));
1208 return(copy_lang (lang_out, lang_len, "German (Switzerland)"));
1211 return(copy_lang (lang_out, lang_len, "German (Austria)"));
1214 return(copy_lang (lang_out, lang_len, "German (Luxembourg)"));
1217 return(copy_lang (lang_out, lang_len, "German (Liechtenstein)"));
1224 return(copy_lang (lang_out, lang_len, "Greek (Greece)"));
1227 return(copy_lang (lang_out, lang_len, "Greek"));
1235 return(copy_lang (lang_out, lang_len, "English (United States)"));
1238 return(copy_lang (lang_out, lang_len, "English (United Kingdom)"));
1241 return(copy_lang (lang_out, lang_len, "English (Australia)"));
1244 return(copy_lang (lang_out, lang_len, "English (Canada)"));
1247 return(copy_lang (lang_out, lang_len, "English (New Zealand)"));
1250 return(copy_lang (lang_out, lang_len, "English (Ireland)"));
1253 return(copy_lang (lang_out, lang_len, "English (South Africa)"));
1256 return(copy_lang (lang_out, lang_len, "English (Jamaica)"));
1259 return(copy_lang (lang_out, lang_len, "English (Caribbean)"));
1262 return(copy_lang (lang_out, lang_len, "English (Belize)"));
1265 return(copy_lang (lang_out, lang_len, "English (Trinidad and Tobago)"));
1268 return(copy_lang (lang_out, lang_len, "English (Zimbabwe)"));
1271 return(copy_lang (lang_out, lang_len, "English (Philippines)"));
1274 return(copy_lang (lang_out, lang_len, "English (India)"));
1277 return(copy_lang (lang_out, lang_len, "English (Malaysia)"));
1280 return(copy_lang (lang_out, lang_len, "English (Singapore)"));
1287 return(copy_lang (lang_out, lang_len, "Spanish (Spain)"));
1290 return(copy_lang (lang_out, lang_len, "Spanish (Traditional Sort)"));
1293 return(copy_lang (lang_out, lang_len, "Spanish (Mexico)"));
1296 return(copy_lang (lang_out, lang_len, "Spanish (International Sort)"));
1299 return(copy_lang (lang_out, lang_len, "Spanish (Guatemala)"));
1302 return(copy_lang (lang_out, lang_len, "Spanish (Costa Rica)"));
1305 return(copy_lang (lang_out, lang_len, "Spanish (Panama)"));
1308 return(copy_lang (lang_out, lang_len, "Spanish (Dominican Republic)"));
1311 return(copy_lang (lang_out, lang_len, "Spanish (Venezuela)"));
1314 return(copy_lang (lang_out, lang_len, "Spanish (Colombia)"));
1317 return(copy_lang (lang_out, lang_len, "Spanish (Peru)"));
1320 return(copy_lang (lang_out, lang_len, "Spanish (Argentina)"));
1323 return(copy_lang (lang_out, lang_len, "Spanish (Ecuador)"));
1326 return(copy_lang (lang_out, lang_len, "Spanish (Chile)"));
1329 return(copy_lang (lang_out, lang_len, "Spanish (Uruguay)"));
1332 return(copy_lang (lang_out, lang_len, "Spanish (Paraguay)"));
1335 return(copy_lang (lang_out, lang_len, "Spanish (Bolivia)"));
1338 return(copy_lang (lang_out, lang_len, "Spanish (El Salvador)"));
1341 return(copy_lang (lang_out, lang_len, "Spanish (Honduras)"));
1344 return(copy_lang (lang_out, lang_len, "Spanish (Nicaragua)"));
1347 return(copy_lang (lang_out, lang_len, "Spanish (Puerto Rico)"));
1350 return(copy_lang (lang_out, lang_len, "Spanish (United States)"));
1357 return(copy_lang (lang_out, lang_len, "Finnish (Finland)"));
1360 return(copy_lang (lang_out, lang_len, "Finnish"));
1368 return(copy_lang (lang_out, lang_len, "French (France)"));
1371 return(copy_lang (lang_out, lang_len, "French (Belgium)"));
1374 return(copy_lang (lang_out, lang_len, "French (Canada)"));
1377 return(copy_lang (lang_out, lang_len, "French (Switzerland)"));
1380 return(copy_lang (lang_out, lang_len, "French (Luxembourg)"));
1383 return(copy_lang (lang_out, lang_len, "French (Monaco)"));
1390 return(copy_lang (lang_out, lang_len, "Hebrew (Israel)"));
1393 return(copy_lang (lang_out, lang_len, "Hebrew"));
1400 return(copy_lang (lang_out, lang_len, "Hungarian (Hungary)"));
1403 return(copy_lang (lang_out, lang_len, "Hungarian"));
1410 return(copy_lang (lang_out, lang_len, "Icelandic (Iceland)"));
1413 return(copy_lang (lang_out, lang_len, "Icelandic"));
1421 return(copy_lang (lang_out, lang_len, "Italian (Italy)"));
1424 return(copy_lang (lang_out, lang_len, "Italian (Switzerland)"));
1431 return(copy_lang (lang_out, lang_len, "Japanese (Japan)"));
1434 return(copy_lang (lang_out, lang_len, "Japanese"));
1441 return(copy_lang (lang_out, lang_len, "Korean (Korea)"));
1444 return(copy_lang (lang_out, lang_len, "Korean"));
1452 return(copy_lang (lang_out, lang_len, "Dutch (Netherlands)"));
1455 return(copy_lang (lang_out, lang_len, "Dutch (Belgium)"));
1463 return(copy_lang (lang_out, lang_len, "Norwegian (Bokmal)"));
1466 return(copy_lang (lang_out, lang_len, "Norwegian (Nynorsk)"));
1473 return(copy_lang (lang_out, lang_len, "Polish (Poland)"));
1476 return(copy_lang (lang_out, lang_len, "Polish"));
1484 return(copy_lang (lang_out, lang_len, "Portuguese (Brazil)"));
1487 return(copy_lang (lang_out, lang_len, "Portuguese (Portugal)"));
1494 return(copy_lang (lang_out, lang_len, "Romansh (Switzerland)"));
1501 return(copy_lang (lang_out, lang_len, "Romanian (Romania)"));
1504 return(copy_lang (lang_out, lang_len, "Romanian"));
1511 return(copy_lang (lang_out, lang_len, "Russian (Russia)"));
1514 return(copy_lang (lang_out, lang_len, "Russian"));
1521 return(copy_lang (lang_out, lang_len, "Croatian (Croatia)"));
1524 return(copy_lang (lang_out, lang_len, "Croatian"));
1527 return(copy_lang (lang_out, lang_len, "Serbian (Latin)"));
1530 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic)"));
1533 return(copy_lang (lang_out, lang_len, "Croatian (Bosnia and Herzegovina)"));
1536 return(copy_lang (lang_out, lang_len, "Bosnian (Latin, Bosnia and Herzegovina)"));
1539 return(copy_lang (lang_out, lang_len, "Serbian (Latin, Bosnia and Herzegovina)"));
1542 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic, Bosnia and Herzegovina)"));
1545 return(copy_lang (lang_out, lang_len, "Bosnian (Cyrillic, Bosnia and Herzegovina)"));
1552 return(copy_lang (lang_out, lang_len, "Slovak (Slovakia)"));
1555 return(copy_lang (lang_out, lang_len, "Slovak"));
1562 return(copy_lang (lang_out, lang_len, "Albanian (Albania)"));
1565 return(copy_lang (lang_out, lang_len, "Albanian"));
1572 return(copy_lang (lang_out, lang_len, "Swedish (Sweden)"));
1575 return(copy_lang (lang_out, lang_len, "Swedish"));
1578 return(copy_lang (lang_out, lang_len, "Swedish (Finland)"));
1585 return(copy_lang (lang_out, lang_len, "Thai (Thailand)"));
1588 return(copy_lang (lang_out, lang_len, "Thai"));
1595 return(copy_lang (lang_out, lang_len, "Turkish (Turkey)"));
1598 return(copy_lang (lang_out, lang_len, "Turkish"));
1605 return(copy_lang (lang_out, lang_len, "Urdu (Islamic Republic of Pakistan)"));
1608 return(copy_lang (lang_out, lang_len, "Urdu"));
1615 return(copy_lang (lang_out, lang_len, "Indonesian (Indonesia)"));
1618 return(copy_lang (lang_out, lang_len, "Indonesian"));
1625 return(copy_lang (lang_out, lang_len, "Ukrainian (Ukraine)"));
1628 return(copy_lang (lang_out, lang_len, "Ukrainian"));
1635 return(copy_lang (lang_out, lang_len, "Belarusian (Belarus)"));
1638 return(copy_lang (lang_out, lang_len, "Belarusian"));
1645 return(copy_lang (lang_out, lang_len, "Slovenian (Slovenia)"));
1648 return(copy_lang (lang_out, lang_len, "Slovenian"));
1655 return(copy_lang (lang_out, lang_len, "Estonian (Estonia)"));
1658 return(copy_lang (lang_out, lang_len, "Estonian"));
1665 return(copy_lang (lang_out, lang_len, "Latvian (Latvia)"));
1668 return(copy_lang (lang_out, lang_len, "Latvian"));
1675 return(copy_lang (lang_out, lang_len, "Lithuanian (Lithuania)"));
1678 return(copy_lang (lang_out, lang_len, "Lithuanian"));
1685 return(copy_lang (lang_out, lang_len, "Tajik (Tajikistan)"));
1692 return(copy_lang (lang_out, lang_len, "Farsi (Iran)"));
1695 return(copy_lang (lang_out, lang_len, "Farsi"));
1702 return(copy_lang (lang_out, lang_len, "Vietnamese (Viet Nam)"));
1705 return(copy_lang (lang_out, lang_len, "Vietnamese"));
1712 return(copy_lang (lang_out, lang_len, "Armenian (Armenia)"));
1715 return(copy_lang (lang_out, lang_len, "Armenian"));
1722 return(copy_lang (lang_out, lang_len, "Azeri (Latin) (Azerbaijan)"));
1725 return(copy_lang (lang_out, lang_len, "Azeri (Latin)"));
1728 return(copy_lang (lang_out, lang_len, "Azeri (Cyrillic)"));
1735 return(copy_lang (lang_out, lang_len, "Basque (Spain)"));
1738 return(copy_lang (lang_out, lang_len, "Basque"));
1745 return(copy_lang (lang_out, lang_len, "Upper Sorbian (Germany)"));
1748 return(copy_lang (lang_out, lang_len, "Lower Sorbian (Germany)"));
1755 return(copy_lang (lang_out, lang_len, "FYRO Macedonian (Former Yugoslav Republic of Macedonia)"));
1758 return(copy_lang (lang_out, lang_len, "FYRO Macedonian"));
1765 return(copy_lang (lang_out, lang_len, "Tswana (South Africa)"));
1768 return(copy_lang (lang_out, lang_len, "Tswana"));
1775 return(copy_lang (lang_out, lang_len, "Xhosa (South Africa)"));
1778 return(copy_lang (lang_out, lang_len, "Xhosa"));
1785 return(copy_lang (lang_out, lang_len, "Zulu (South Africa)"));
1788 return(copy_lang (lang_out, lang_len, "Zulu"));
1795 return(copy_lang (lang_out, lang_len, "Afrikaans (South Africa)"));
1798 return(copy_lang (lang_out, lang_len, "Afrikaans"));
1805 return(copy_lang (lang_out, lang_len, "Georgian (Georgia)"));
1808 return(copy_lang (lang_out, lang_len, "Georgian"));
1815 return(copy_lang (lang_out, lang_len, "Faroese (Faroe Islands)"));
1818 return(copy_lang (lang_out, lang_len, "Faroese"));
1825 return(copy_lang (lang_out, lang_len, "Hindi (India)"));
1828 return(copy_lang (lang_out, lang_len, "Hindi"));
1835 return(copy_lang (lang_out, lang_len, "Maltese (Malta)"));
1838 return(copy_lang (lang_out, lang_len, "Maltese"));
1845 return(copy_lang (lang_out, lang_len, "Sami (Northern) (Norway)"));
1848 return(copy_lang (lang_out, lang_len, "Sami, Northern (Norway)"));
1851 return(copy_lang (lang_out, lang_len, "Sami, Northern (Sweden)"));
1854 return(copy_lang (lang_out, lang_len, "Sami, Northern (Finland)"));
1857 return(copy_lang (lang_out, lang_len, "Sami, Lule (Norway)"));
1860 return(copy_lang (lang_out, lang_len, "Sami, Lule (Sweden)"));
1863 return(copy_lang (lang_out, lang_len, "Sami, Southern (Norway)"));
1866 return(copy_lang (lang_out, lang_len, "Sami, Southern (Sweden)"));
1869 return(copy_lang (lang_out, lang_len, "Sami, Skolt (Finland)"));
1872 return(copy_lang (lang_out, lang_len, "Sami, Inari (Finland)"));
1879 return(copy_lang (lang_out, lang_len, "Irish (Ireland)"));
1887 return(copy_lang (lang_out, lang_len, "Malay (Malaysia)"));
1890 return(copy_lang (lang_out, lang_len, "Malay (Brunei Darussalam)"));
1897 return(copy_lang (lang_out, lang_len, "Kazakh (Kazakhstan)"));
1900 return(copy_lang (lang_out, lang_len, "Kazakh"));
1907 return(copy_lang (lang_out, lang_len, "Kyrgyz (Kyrgyzstan)"));
1910 return(copy_lang (lang_out, lang_len, "Kyrgyz (Cyrillic)"));
1917 return(copy_lang (lang_out, lang_len, "Swahili (Kenya)"));
1920 return(copy_lang (lang_out, lang_len, "Swahili"));
1927 return(copy_lang (lang_out, lang_len, "Turkmen (Turkmenistan)"));
1934 return(copy_lang (lang_out, lang_len, "Uzbek (Latin) (Uzbekistan)"));
1937 return(copy_lang (lang_out, lang_len, "Uzbek (Latin)"));
1940 return(copy_lang (lang_out, lang_len, "Uzbek (Cyrillic)"));
1947 return(copy_lang (lang_out, lang_len, "Tatar (Russia)"));
1950 return(copy_lang (lang_out, lang_len, "Tatar"));
1958 return(copy_lang (lang_out, lang_len, "Bengali (India)"));
1965 return(copy_lang (lang_out, lang_len, "Punjabi (India)"));
1968 return(copy_lang (lang_out, lang_len, "Punjabi"));
1975 return(copy_lang (lang_out, lang_len, "Gujarati (India)"));
1978 return(copy_lang (lang_out, lang_len, "Gujarati"));
1985 return(copy_lang (lang_out, lang_len, "Tamil (India)"));
1988 return(copy_lang (lang_out, lang_len, "Tamil"));
1995 return(copy_lang (lang_out, lang_len, "Telugu (India)"));
1998 return(copy_lang (lang_out, lang_len, "Telugu"));
2005 return(copy_lang (lang_out, lang_len, "Kannada (India)"));
2008 return(copy_lang (lang_out, lang_len, "Kannada"));
2016 return(copy_lang (lang_out, lang_len, "Malayalam (India)"));
2023 return(copy_lang (lang_out, lang_len, "Assamese (India)"));
2030 return(copy_lang (lang_out, lang_len, "Marathi (India)"));
2033 return(copy_lang (lang_out, lang_len, "Marathi"));
2040 return(copy_lang (lang_out, lang_len, "Sanskrit (India)"));
2043 return(copy_lang (lang_out, lang_len, "Sanskrit"));
2050 return(copy_lang (lang_out, lang_len, "Mongolian (Mongolia)"));
2053 return(copy_lang (lang_out, lang_len, "Mongolian (Cyrillic)"));
2056 return(copy_lang (lang_out, lang_len, "Mongolian (PRC)"));
2063 return(copy_lang (lang_out, lang_len, "Tibetan (PRC)"));
2066 return(copy_lang (lang_out, lang_len, "Tibetan (Bhutan)"));
2073 return(copy_lang (lang_out, lang_len, "Welsh (United Kingdom)"));
2076 return(copy_lang (lang_out, lang_len, "Welsh"));
2083 return(copy_lang (lang_out, lang_len, "Khmer (Cambodia)"));
2090 return(copy_lang (lang_out, lang_len, "Lao (Lao PDR)"));
2097 return(copy_lang (lang_out, lang_len, "Galician (Spain)"));
2100 return(copy_lang (lang_out, lang_len, "Galician"));
2107 return(copy_lang (lang_out, lang_len, "Konkani (India)"));
2110 return(copy_lang (lang_out, lang_len, "Konkani"));
2117 return(copy_lang (lang_out, lang_len, "Syriac (Syria)"));
2120 return(copy_lang (lang_out, lang_len, "Syriac"));
2127 return(copy_lang (lang_out, lang_len, "Sinhala (Sri Lanka)"));
2134 return(copy_lang (lang_out, lang_len, "Inuktitut (Syllabics, Canada)"));
2137 return(copy_lang (lang_out, lang_len, "Inuktitut (Latin, Canada)"));
2144 return(copy_lang (lang_out, lang_len, "Amharic (Ethiopia)"));
2151 return(copy_lang (lang_out, lang_len, "Tamazight (Algeria, Latin)"));
2158 return(copy_lang (lang_out, lang_len, "Nepali (Nepal)"));
2165 return(copy_lang (lang_out, lang_len, "Frisian (Netherlands)"));
2172 return(copy_lang (lang_out, lang_len, "Pashto (Afghanistan)"));
2179 return(copy_lang (lang_out, lang_len, "Filipino (Philippines)"));
2186 return(copy_lang (lang_out, lang_len, "Divehi (Maldives)"));
2189 return(copy_lang (lang_out, lang_len, "Divehi"));
2196 return(copy_lang (lang_out, lang_len, "Hausa (Nigeria, Latin)"));
2203 return(copy_lang (lang_out, lang_len, "Yoruba (Nigeria)"));
2211 return(copy_lang (lang_out, lang_len, "Quechua (Bolivia)"));
2214 return(copy_lang (lang_out, lang_len, "Quechua (Ecuador)"));
2217 return(copy_lang (lang_out, lang_len, "Quechua (Peru)"));
2224 return(copy_lang (lang_out, lang_len, "Northern Sotho (South Africa)"));
2227 return(copy_lang (lang_out, lang_len, "Northern Sotho"));
2234 return(copy_lang (lang_out, lang_len, "Bashkir (Russia)"));
2241 return(copy_lang (lang_out, lang_len, "Luxembourgish (Luxembourg)"));
2248 return(copy_lang (lang_out, lang_len, "Greenlandic (Greenland)"));
2255 return(copy_lang (lang_out, lang_len, "Yi (PRC)"));
2262 return(copy_lang (lang_out, lang_len, "Mapudungun (Chile)"));
2269 return(copy_lang (lang_out, lang_len, "Mohawk (Mohawk)"));
2276 return(copy_lang (lang_out, lang_len, "Breton (France)"));
2283 return(copy_lang (lang_out, lang_len, "Invariant Language (Invariant Country)"));
2290 return(copy_lang (lang_out, lang_len, "Uighur (PRC)"));
2297 return(copy_lang (lang_out, lang_len, "Maori (New Zealand)"));
2300 return(copy_lang (lang_out, lang_len, "Maori"));
2307 return(copy_lang (lang_out, lang_len, "Corsican (France)"));
2314 return(copy_lang (lang_out, lang_len, "Alsatian (France)"));
2321 return(copy_lang (lang_out, lang_len, "Yakut (Russia)"));
2328 return(copy_lang (lang_out, lang_len, "K'iche (Guatemala)"));
2335 return(copy_lang (lang_out, lang_len, "Kinyarwanda (Rwanda)"));
2342 return(copy_lang (lang_out, lang_len, "Wolof (Senegal)"));
2349 return(copy_lang (lang_out, lang_len, "Dari (Afghanistan)"));
2355 return(copy_lang (lang_out, lang_len, "Language Neutral"));
2359 return(copy_lang (lang_out, lang_len, "Language Neutral"));