Bump external/ikdasm to master.
[mono-project.git] / mono / io-layer / versioninfo.c
blob2ba7fc96d5554ee1eaa5d1c7221aabaa020896ae
1 /*
2 * versioninfo.c: Version information support
4 * Author:
5 * Dick Porter (dick@ximian.com)
7 * (C) 2007 Novell, Inc.
8 */
10 #include <config.h>
11 #include <glib.h>
12 #include <string.h>
13 #include <pthread.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <errno.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>
28 #if 0
29 // #define DEBUG(...) g_message(__VA_ARGS__)
30 #else
31 #define DEBUG(...)
32 #endif
34 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
36 static WapiImageSectionHeader *
37 get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
39 WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
40 guint32 i;
42 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
43 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
44 if (size == 0) {
45 size = GUINT32_FROM_LE (section->SizeOfRawData);
48 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
49 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
50 return(section);
54 return(NULL);
57 /* This works for both 32bit and 64bit files, as the differences are
58 * all after the section header block
60 static gpointer
61 get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders, gpointer file_map)
63 WapiImageSectionHeader *section_header;
64 guint32 delta;
66 section_header = get_enclosing_section_header (rva, ntheaders);
67 if (section_header == NULL) {
68 return(NULL);
71 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
72 GUINT32_FROM_LE (section_header->PointerToRawData));
74 return((guint8 *)file_map + rva - delta);
77 static gpointer
78 scan_resource_dir (WapiImageResourceDirectory *root,
79 WapiImageNTHeaders32 *nt_headers,
80 gpointer file_map,
81 WapiImageResourceDirectoryEntry *entry,
82 int level, guint32 res_id, guint32 lang_id,
83 guint32 *size)
85 WapiImageResourceDirectoryEntry swapped_entry;
86 gboolean is_string, is_dir;
87 guint32 name_offset, dir_offset, data_offset;
89 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
90 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
92 is_string = swapped_entry.NameIsString;
93 is_dir = swapped_entry.DataIsDirectory;
94 name_offset = swapped_entry.NameOffset;
95 dir_offset = swapped_entry.OffsetToDirectory;
96 data_offset = swapped_entry.OffsetToData;
98 if (level == 0) {
99 /* Normally holds a directory entry for each type of
100 * resource
102 if ((is_string == FALSE &&
103 name_offset != res_id) ||
104 (is_string == TRUE)) {
105 return(NULL);
107 } else if (level == 1) {
108 /* Normally holds a directory entry for each resource
109 * item
111 } else if (level == 2) {
112 /* Normally holds a directory entry for each language
114 if ((is_string == FALSE &&
115 name_offset != lang_id &&
116 lang_id != 0) ||
117 (is_string == TRUE)) {
118 return(NULL);
120 } else {
121 g_assert_not_reached ();
124 if (is_dir == TRUE) {
125 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
126 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
127 guint32 entries, i;
129 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
131 for (i = 0; i < entries; i++) {
132 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
133 gpointer ret;
135 ret = scan_resource_dir (root, nt_headers, file_map,
136 sub_entry, level + 1, res_id,
137 lang_id, size);
138 if (ret != NULL) {
139 return(ret);
143 return(NULL);
144 } else {
145 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
146 *size = GUINT32_FROM_LE (data_entry->Size);
148 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
152 static gpointer
153 find_pe_file_resources32 (gpointer file_map, guint32 map_size,
154 guint32 res_id, guint32 lang_id,
155 guint32 *size)
157 WapiImageDosHeader *dos_header;
158 WapiImageNTHeaders32 *nt_headers;
159 WapiImageResourceDirectory *resource_dir;
160 WapiImageResourceDirectoryEntry *resource_dir_entry;
161 guint32 resource_rva, entries, i;
162 gpointer ret = NULL;
164 dos_header = (WapiImageDosHeader *)file_map;
165 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
166 DEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
168 SetLastError (ERROR_INVALID_DATA);
169 return(NULL);
172 if (map_size < sizeof(WapiImageNTHeaders32) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
173 DEBUG ("%s: File is too small: %d", __func__, map_size);
175 SetLastError (ERROR_BAD_LENGTH);
176 return(NULL);
179 nt_headers = (WapiImageNTHeaders32 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
180 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
181 DEBUG ("%s: Bad NT signature 0x%x", __func__, nt_headers->Signature);
183 SetLastError (ERROR_INVALID_DATA);
184 return(NULL);
187 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
188 /* Do 64-bit stuff */
189 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
190 } else {
191 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
194 if (resource_rva == 0) {
195 DEBUG ("%s: No resources in file!", __func__);
197 SetLastError (ERROR_INVALID_DATA);
198 return(NULL);
201 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
202 if (resource_dir == NULL) {
203 DEBUG ("%s: Can't find resource directory", __func__);
205 SetLastError (ERROR_INVALID_DATA);
206 return(NULL);
209 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
210 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
212 for (i = 0; i < entries; i++) {
213 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
214 ret = scan_resource_dir (resource_dir,
215 (WapiImageNTHeaders32 *)nt_headers,
216 file_map, direntry, 0, res_id,
217 lang_id, size);
218 if (ret != NULL) {
219 return(ret);
223 return(NULL);
226 static gpointer
227 find_pe_file_resources64 (gpointer file_map, guint32 map_size,
228 guint32 res_id, guint32 lang_id,
229 guint32 *size)
231 WapiImageDosHeader *dos_header;
232 WapiImageNTHeaders64 *nt_headers;
233 WapiImageResourceDirectory *resource_dir;
234 WapiImageResourceDirectoryEntry *resource_dir_entry;
235 guint32 resource_rva, entries, i;
236 gpointer ret = NULL;
238 dos_header = (WapiImageDosHeader *)file_map;
239 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
240 DEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
242 SetLastError (ERROR_INVALID_DATA);
243 return(NULL);
246 if (map_size < sizeof(WapiImageNTHeaders64) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
247 DEBUG ("%s: File is too small: %d", __func__, map_size);
249 SetLastError (ERROR_BAD_LENGTH);
250 return(NULL);
253 nt_headers = (WapiImageNTHeaders64 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
254 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
255 DEBUG ("%s: Bad NT signature 0x%x", __func__,
256 nt_headers->Signature);
258 SetLastError (ERROR_INVALID_DATA);
259 return(NULL);
262 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
263 /* Do 64-bit stuff */
264 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
265 } else {
266 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
269 if (resource_rva == 0) {
270 DEBUG ("%s: No resources in file!", __func__);
272 SetLastError (ERROR_INVALID_DATA);
273 return(NULL);
276 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
277 if (resource_dir == NULL) {
278 DEBUG ("%s: Can't find resource directory", __func__);
280 SetLastError (ERROR_INVALID_DATA);
281 return(NULL);
284 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
285 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
287 for (i = 0; i < entries; i++) {
288 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
289 ret = scan_resource_dir (resource_dir,
290 (WapiImageNTHeaders32 *)nt_headers,
291 file_map, direntry, 0, res_id,
292 lang_id, size);
293 if (ret != NULL) {
294 return(ret);
298 return(NULL);
301 static gpointer
302 find_pe_file_resources (gpointer file_map, guint32 map_size,
303 guint32 res_id, guint32 lang_id,
304 guint32 *size)
306 /* Figure this out when we support 64bit PE files */
307 if (1) {
308 return find_pe_file_resources32 (file_map, map_size, res_id,
309 lang_id, size);
310 } else {
311 return find_pe_file_resources64 (file_map, map_size, res_id,
312 lang_id, size);
316 static gpointer
317 map_pe_file (gunichar2 *filename, gint32 *map_size, void **handle)
319 gchar *filename_ext;
320 int fd;
321 struct stat statbuf;
322 gpointer file_map;
324 /* According to the MSDN docs, a search path is applied to
325 * filename. FIXME: implement this, for now just pass it
326 * straight to fopen
329 filename_ext = mono_unicode_to_external (filename);
330 if (filename_ext == NULL) {
331 DEBUG ("%s: unicode conversion returned NULL", __func__);
333 SetLastError (ERROR_INVALID_NAME);
334 return(NULL);
337 fd = _wapi_open (filename_ext, O_RDONLY, 0);
338 if (fd == -1) {
339 DEBUG ("%s: Error opening file %s: %s", __func__, filename_ext, strerror (errno));
341 SetLastError (_wapi_get_win32_file_error (errno));
342 g_free (filename_ext);
344 return(NULL);
347 if (fstat (fd, &statbuf) == -1) {
348 DEBUG ("%s: Error stat()ing file %s: %s", __func__, filename_ext, strerror (errno));
350 SetLastError (_wapi_get_win32_file_error (errno));
351 g_free (filename_ext);
352 close (fd);
353 return(NULL);
355 *map_size = statbuf.st_size;
357 /* Check basic file size */
358 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
359 DEBUG ("%s: File %s is too small: %lld", __func__, filename_ext, statbuf.st_size);
361 SetLastError (ERROR_BAD_LENGTH);
362 g_free (filename_ext);
363 close (fd);
364 return(NULL);
367 file_map = mono_file_map (statbuf.st_size, MONO_MMAP_READ | MONO_MMAP_PRIVATE, fd, 0, handle);
368 if (file_map == NULL) {
369 DEBUG ("%s: Error mmap()int file %s: %s", __func__, filename_ext, strerror (errno));
371 SetLastError (_wapi_get_win32_file_error (errno));
372 g_free (filename_ext);
373 close (fd);
374 return(NULL);
377 /* Don't need the fd any more */
378 close (fd);
379 g_free (filename_ext);
381 return(file_map);
384 static void
385 unmap_pe_file (gpointer file_map, void *handle)
387 mono_file_unmap (file_map, handle);
390 static guint32
391 unicode_chars (const gunichar2 *str)
393 guint32 len = 0;
395 do {
396 if (str[len] == '\0') {
397 return(len);
399 len++;
400 } while(1);
403 static gboolean
404 unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
406 while (*str1 && *str2) {
407 if (*str1 != *str2) {
408 return(FALSE);
410 ++str1;
411 ++str2;
414 return(*str1 == *str2);
417 /* compare a little-endian null-terminated utf16 string and a normal string.
418 * Can be used only for ascii or latin1 chars.
420 static gboolean
421 unicode_string_equals (const gunichar2 *str1, const gchar *str2)
423 while (*str1 && *str2) {
424 if (GUINT16_TO_LE (*str1) != *str2) {
425 return(FALSE);
427 ++str1;
428 ++str2;
431 return(*str1 == *str2);
434 typedef struct
436 guint16 data_len;
437 guint16 value_len;
438 guint16 type;
439 gunichar2 *key;
440 } version_data;
442 /* Returns a pointer to the value data, because there's no way to know
443 * how big that data is (value_len is set to zero for most blocks :-( )
445 static gconstpointer
446 get_versioninfo_block (gconstpointer data, version_data *block)
448 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
449 data = (char *)data + sizeof(guint16);
450 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
451 data = (char *)data + sizeof(guint16);
453 /* No idea what the type is supposed to indicate */
454 block->type = GUINT16_FROM_LE (*((guint16 *)data));
455 data = (char *)data + sizeof(guint16);
456 block->key = ((gunichar2 *)data);
458 /* Skip over the key (including the terminator) */
459 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
461 /* align on a 32-bit boundary */
462 ALIGN32 (data);
464 return(data);
467 static gconstpointer
468 get_fixedfileinfo_block (gconstpointer data, version_data *block)
470 gconstpointer data_ptr;
471 gint32 data_len; /* signed to guard against underflow */
472 WapiFixedFileInfo *ffi;
474 data_ptr = get_versioninfo_block (data, block);
475 data_len = block->data_len;
477 if (block->value_len != sizeof(WapiFixedFileInfo)) {
478 DEBUG ("%s: FIXEDFILEINFO size mismatch", __func__);
479 return(NULL);
482 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
483 DEBUG ("%s: VS_VERSION_INFO mismatch", __func__);
485 return(NULL);
488 ffi = ((WapiFixedFileInfo *)data_ptr);
489 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
490 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
491 DEBUG ("%s: FIXEDFILEINFO bad signature", __func__);
493 return(NULL);
496 return(data_ptr);
499 static gconstpointer
500 get_varfileinfo_block (gconstpointer data_ptr, version_data *block)
502 /* data is pointing at a Var block
504 data_ptr = get_versioninfo_block (data_ptr, block);
506 return(data_ptr);
509 static gconstpointer
510 get_string_block (gconstpointer data_ptr,
511 const gunichar2 *string_key,
512 gpointer *string_value,
513 guint32 *string_value_len,
514 version_data *block)
516 guint16 data_len = block->data_len;
517 guint16 string_len = 28; /* Length of the StringTable block */
518 char *orig_data_ptr = (char *)data_ptr - 28;
520 /* data_ptr is pointing at an array of one or more String blocks
521 * with total length (not including alignment padding) of
522 * data_len
524 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
525 /* align on a 32-bit boundary */
526 ALIGN32 (data_ptr);
528 data_ptr = get_versioninfo_block (data_ptr, block);
529 if (block->data_len == 0) {
530 /* We must have hit padding, so give up
531 * processing now
533 DEBUG ("%s: Hit 0-length block, giving up", __func__);
535 return(NULL);
538 string_len = string_len + block->data_len;
540 if (string_key != NULL &&
541 string_value != NULL &&
542 string_value_len != NULL &&
543 unicode_compare (string_key, block->key) == TRUE) {
544 *string_value = (gpointer)data_ptr;
545 *string_value_len = block->value_len;
548 /* Skip over the value */
549 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
552 return(data_ptr);
555 /* Returns a pointer to the byte following the Stringtable block, or
556 * NULL if the data read hits padding. We can't recover from this
557 * because the data length does not include padding bytes, so it's not
558 * possible to just return the start position + length
560 * If lang == NULL it means we're just stepping through this block
562 static gconstpointer
563 get_stringtable_block (gconstpointer data_ptr,
564 gchar *lang,
565 const gunichar2 *string_key,
566 gpointer *string_value,
567 guint32 *string_value_len,
568 version_data *block)
570 guint16 data_len = block->data_len;
571 guint16 string_len = 36; /* length of the StringFileInfo block */
572 gchar *found_lang;
573 gchar *lowercase_lang;
575 /* data_ptr is pointing at an array of StringTable blocks,
576 * with total length (not including alignment padding) of
577 * data_len
580 while(string_len < data_len) {
581 /* align on a 32-bit boundary */
582 ALIGN32 (data_ptr);
584 data_ptr = get_versioninfo_block (data_ptr, block);
585 if (block->data_len == 0) {
586 /* We must have hit padding, so give up
587 * processing now
589 DEBUG ("%s: Hit 0-length block, giving up", __func__);
590 return(NULL);
593 string_len = string_len + block->data_len;
595 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
596 if (found_lang == NULL) {
597 DEBUG ("%s: Didn't find a valid language key, giving up", __func__);
598 return(NULL);
601 lowercase_lang = g_utf8_strdown (found_lang, -1);
602 g_free (found_lang);
603 found_lang = lowercase_lang;
604 lowercase_lang = NULL;
606 if (lang != NULL && !strcmp (found_lang, lang)) {
607 /* Got the one we're interested in */
608 data_ptr = get_string_block (data_ptr, string_key,
609 string_value,
610 string_value_len, block);
611 } else {
612 data_ptr = get_string_block (data_ptr, NULL, NULL,
613 NULL, block);
616 g_free (found_lang);
618 if (data_ptr == NULL) {
619 /* Child block hit padding */
620 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
621 return(NULL);
625 return(data_ptr);
628 #if G_BYTE_ORDER == G_BIG_ENDIAN
629 static gconstpointer
630 big_up_string_block (gconstpointer data_ptr, version_data *block)
632 guint16 data_len = block->data_len;
633 guint16 string_len = 28; /* Length of the StringTable block */
634 gchar *big_value;
635 char *orig_data_ptr = (char *)data_ptr - 28;
637 /* data_ptr is pointing at an array of one or more String
638 * blocks with total length (not including alignment padding)
639 * of data_len
641 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
642 /* align on a 32-bit boundary */
643 ALIGN32 (data_ptr);
645 data_ptr = get_versioninfo_block (data_ptr, block);
646 if (block->data_len == 0) {
647 /* We must have hit padding, so give up
648 * processing now
650 DEBUG ("%s: Hit 0-length block, giving up", __func__);
651 return(NULL);
654 string_len = string_len + block->data_len;
656 big_value = g_convert ((gchar *)block->key,
657 unicode_chars (block->key) * 2,
658 "UTF-16BE", "UTF-16LE", NULL, NULL,
659 NULL);
660 if (big_value == NULL) {
661 DEBUG ("%s: Didn't find a valid string, giving up", __func__);
662 return(NULL);
665 /* The swapped string should be exactly the same
666 * length as the original little-endian one, but only
667 * copy the number of original chars just to be on the
668 * safe side
670 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
671 g_free (big_value);
673 big_value = g_convert ((gchar *)data_ptr,
674 unicode_chars (data_ptr) * 2,
675 "UTF-16BE", "UTF-16LE", NULL, NULL,
676 NULL);
677 if (big_value == NULL) {
678 DEBUG ("%s: Didn't find a valid data string, giving up", __func__);
679 return(NULL);
681 memcpy ((gpointer)data_ptr, big_value,
682 unicode_chars (data_ptr) * 2);
683 g_free (big_value);
685 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
688 return(data_ptr);
691 /* Returns a pointer to the byte following the Stringtable block, or
692 * NULL if the data read hits padding. We can't recover from this
693 * because the data length does not include padding bytes, so it's not
694 * possible to just return the start position + length
696 static gconstpointer
697 big_up_stringtable_block (gconstpointer data_ptr, version_data *block)
699 guint16 data_len = block->data_len;
700 guint16 string_len = 36; /* length of the StringFileInfo block */
701 gchar *big_value;
703 /* data_ptr is pointing at an array of StringTable blocks,
704 * with total length (not including alignment padding) of
705 * data_len
708 while(string_len < data_len) {
709 /* align on a 32-bit boundary */
710 ALIGN32 (data_ptr);
712 data_ptr = get_versioninfo_block (data_ptr, block);
713 if (block->data_len == 0) {
714 /* We must have hit padding, so give up
715 * processing now
717 DEBUG ("%s: Hit 0-length block, giving up", __func__);
718 return(NULL);
721 string_len = string_len + block->data_len;
723 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
724 "UTF-16LE", NULL, NULL, NULL);
725 if (big_value == NULL) {
726 DEBUG ("%s: Didn't find a valid string, giving up", __func__);
727 return(NULL);
730 memcpy (block->key, big_value, 16);
731 g_free (big_value);
733 data_ptr = big_up_string_block (data_ptr, block);
735 if (data_ptr == NULL) {
736 /* Child block hit padding */
737 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
738 return(NULL);
742 return(data_ptr);
745 /* Follows the data structures and turns all UTF-16 strings from the
746 * LE found in the resource section into UTF-16BE
748 static void
749 big_up (gconstpointer datablock, guint32 size)
751 gconstpointer data_ptr;
752 gint32 data_len; /* signed to guard against underflow */
753 version_data block;
755 data_ptr = get_fixedfileinfo_block (datablock, &block);
756 if (data_ptr != NULL) {
757 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
759 /* Byteswap all the fields */
760 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
761 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
762 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
763 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
764 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
765 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
766 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
767 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
768 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
769 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
770 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
772 /* The FFI and header occupies the first 92 bytes
774 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
775 data_len = block.data_len - 92;
777 /* There now follow zero or one StringFileInfo blocks
778 * and zero or one VarFileInfo blocks
780 while (data_len > 0) {
781 /* align on a 32-bit boundary */
782 ALIGN32 (data_ptr);
784 data_ptr = get_versioninfo_block (data_ptr, &block);
785 if (block.data_len == 0) {
786 /* We must have hit padding, so give
787 * up processing now
789 DEBUG ("%s: Hit 0-length block, giving up", __func__);
790 return;
793 data_len = data_len - block.data_len;
795 if (unicode_string_equals (block.key, "VarFileInfo")) {
796 data_ptr = get_varfileinfo_block (data_ptr,
797 &block);
798 data_ptr = ((guchar *)data_ptr) + block.value_len;
799 } else if (unicode_string_equals (block.key,
800 "StringFileInfo")) {
801 data_ptr = big_up_stringtable_block (data_ptr,
802 &block);
803 } else {
804 /* Bogus data */
805 DEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
806 return;
809 if (data_ptr == NULL) {
810 /* Child block hit padding */
811 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
812 return;
817 #endif
819 gboolean
820 VerQueryValue (gconstpointer datablock, const gunichar2 *subblock, gpointer *buffer, guint32 *len)
822 gchar *subblock_utf8, *lang_utf8 = NULL;
823 gboolean ret = FALSE;
824 version_data block;
825 gconstpointer data_ptr;
826 gint32 data_len; /* signed to guard against underflow */
827 gboolean want_var = FALSE;
828 gboolean want_string = FALSE;
829 gunichar2 lang[8];
830 const gunichar2 *string_key = NULL;
831 gpointer string_value = NULL;
832 guint32 string_value_len = 0;
833 gchar *lowercase_lang;
835 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
836 if (subblock_utf8 == NULL) {
837 return(FALSE);
840 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
841 want_var = TRUE;
842 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
843 want_string = TRUE;
844 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
845 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
846 lowercase_lang = g_utf8_strdown (lang_utf8, -1);
847 g_free (lang_utf8);
848 lang_utf8 = lowercase_lang;
849 lowercase_lang = NULL;
850 string_key = subblock + 25;
853 if (!strcmp (subblock_utf8, "\\")) {
854 data_ptr = get_fixedfileinfo_block (datablock, &block);
855 if (data_ptr != NULL) {
856 *buffer = (gpointer)data_ptr;
857 *len = block.value_len;
859 ret = TRUE;
861 } else if (want_var || want_string) {
862 data_ptr = get_fixedfileinfo_block (datablock, &block);
863 if (data_ptr != NULL) {
864 /* The FFI and header occupies the first 92
865 * bytes
867 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
868 data_len = block.data_len - 92;
870 /* There now follow zero or one StringFileInfo
871 * blocks and zero or one VarFileInfo blocks
873 while (data_len > 0) {
874 /* align on a 32-bit boundary */
875 ALIGN32 (data_ptr);
877 data_ptr = get_versioninfo_block (data_ptr,
878 &block);
879 if (block.data_len == 0) {
880 /* We must have hit padding,
881 * so give up processing now
883 DEBUG ("%s: Hit 0-length block, giving up", __func__);
884 goto done;
887 data_len = data_len - block.data_len;
889 if (unicode_string_equals (block.key, "VarFileInfo")) {
890 data_ptr = get_varfileinfo_block (data_ptr, &block);
891 if (want_var) {
892 *buffer = (gpointer)data_ptr;
893 *len = block.value_len;
894 ret = TRUE;
895 goto done;
896 } else {
897 /* Skip over the Var block */
898 data_ptr = ((guchar *)data_ptr) + block.value_len;
900 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
901 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
902 if (want_string &&
903 string_value != NULL &&
904 string_value_len != 0) {
905 *buffer = string_value;
906 *len = unicode_chars (string_value) + 1; /* Include trailing null */
907 ret = TRUE;
908 goto done;
910 } else {
911 /* Bogus data */
912 DEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
913 goto done;
916 if (data_ptr == NULL) {
917 /* Child block hit padding */
918 DEBUG ("%s: Child block hit 0-length block, giving up", __func__);
919 goto done;
925 done:
926 if (lang_utf8) {
927 g_free (lang_utf8);
930 g_free (subblock_utf8);
931 return(ret);
934 guint32
935 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
937 gpointer file_map;
938 gpointer versioninfo;
939 void *map_handle;
940 gint32 map_size;
941 guint32 size;
943 /* This value is unused, but set to zero */
944 *handle = 0;
946 file_map = map_pe_file (filename, &map_size, &map_handle);
947 if (file_map == NULL) {
948 return(0);
951 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION, 0, &size);
952 if (versioninfo == NULL) {
953 /* Didn't find the resource, so set the return value
954 * to 0
956 size = 0;
959 unmap_pe_file (file_map, map_handle);
961 return(size);
964 gboolean
965 GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED, guint32 len, gpointer data)
967 gpointer file_map;
968 gpointer versioninfo;
969 void *map_handle;
970 gint32 map_size;
971 guint32 size;
972 gboolean ret = FALSE;
974 file_map = map_pe_file (filename, &map_size, &map_handle);
975 if (file_map == NULL) {
976 return(FALSE);
979 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
980 0, &size);
981 if (versioninfo != NULL) {
982 /* This could probably process the data so that
983 * VerQueryValue() doesn't have to follow the data
984 * blocks every time. But hey, these functions aren't
985 * likely to appear in many profiles.
987 memcpy (data, versioninfo, len < size?len:size);
988 ret = TRUE;
990 #if G_BYTE_ORDER == G_BIG_ENDIAN
991 big_up (data, size);
992 #endif
995 unmap_pe_file (file_map, map_handle);
997 return(ret);
1000 static guint32
1001 copy_lang (gunichar2 *lang_out, guint32 lang_len, const gchar *text)
1003 gunichar2 *unitext;
1004 int chars = strlen (text);
1005 int ret;
1007 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1008 g_assert (unitext != NULL);
1010 if (chars < (lang_len - 1)) {
1011 memcpy (lang_out, (gpointer)unitext, chars * 2);
1012 lang_out[chars] = '\0';
1013 ret = chars;
1014 } else {
1015 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1016 lang_out[lang_len] = '\0';
1017 ret = lang_len;
1020 g_free (unitext);
1022 return(ret);
1025 guint32
1026 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1028 int primary, secondary;
1029 const char *name = NULL;
1031 primary = lang & 0x3FF;
1032 secondary = (lang >> 10) & 0x3F;
1034 switch(primary) {
1035 case 0x00:
1036 switch(secondary) {
1037 case 0x01:
1038 name = "Process Default Language";
1039 break;
1041 break;
1042 case 0x01:
1043 switch(secondary) {
1044 case 0x00:
1045 case 0x01:
1046 name = "Arabic (Saudi Arabia)";
1047 break;
1048 case 0x02:
1049 name = "Arabic (Iraq)";
1050 break;
1051 case 0x03:
1052 name = "Arabic (Egypt)";
1053 break;
1054 case 0x04:
1055 name = "Arabic (Libya)";
1056 break;
1057 case 0x05:
1058 name = "Arabic (Algeria)";
1059 break;
1060 case 0x06:
1061 name = "Arabic (Morocco)";
1062 break;
1063 case 0x07:
1064 name = "Arabic (Tunisia)";
1065 break;
1066 case 0x08:
1067 name = "Arabic (Oman)";
1068 break;
1069 case 0x09:
1070 name = "Arabic (Yemen)";
1071 break;
1072 case 0x0a:
1073 name = "Arabic (Syria)";
1074 break;
1075 case 0x0b:
1076 name = "Arabic (Jordan)";
1077 break;
1078 case 0x0c:
1079 name = "Arabic (Lebanon)";
1080 break;
1081 case 0x0d:
1082 name = "Arabic (Kuwait)";
1083 break;
1084 case 0x0e:
1085 name = "Arabic (U.A.E.)";
1086 break;
1087 case 0x0f:
1088 name = "Arabic (Bahrain)";
1089 break;
1090 case 0x10:
1091 name = "Arabic (Qatar)";
1092 break;
1094 break;
1095 case 0x02:
1096 switch(secondary) {
1097 case 0x00:
1098 name = "Bulgarian (Bulgaria)";
1099 break;
1100 case 0x01:
1101 name = "Bulgarian";
1102 break;
1104 break;
1105 case 0x03:
1106 switch(secondary) {
1107 case 0x00:
1108 name = "Catalan (Spain)";
1109 break;
1110 case 0x01:
1111 name = "Catalan";
1112 break;
1114 break;
1115 case 0x04:
1116 switch(secondary) {
1117 case 0x00:
1118 case 0x01:
1119 name = "Chinese (Taiwan)";
1120 break;
1121 case 0x02:
1122 name = "Chinese (PRC)";
1123 break;
1124 case 0x03:
1125 name = "Chinese (Hong Kong S.A.R.)";
1126 break;
1127 case 0x04:
1128 name = "Chinese (Singapore)";
1129 break;
1130 case 0x05:
1131 name = "Chinese (Macau S.A.R.)";
1132 break;
1134 break;
1135 case 0x05:
1136 switch(secondary) {
1137 case 0x00:
1138 name = "Czech (Czech Republic)";
1139 break;
1140 case 0x01:
1141 name = "Czech";
1142 break;
1144 break;
1145 case 0x06:
1146 switch(secondary) {
1147 case 0x00:
1148 name = "Danish (Denmark)";
1149 break;
1150 case 0x01:
1151 name = "Danish";
1152 break;
1154 break;
1155 case 0x07:
1156 switch(secondary) {
1157 case 0x00:
1158 case 0x01:
1159 name = "German (Germany)";
1160 break;
1161 case 0x02:
1162 name = "German (Switzerland)";
1163 break;
1164 case 0x03:
1165 name = "German (Austria)";
1166 break;
1167 case 0x04:
1168 name = "German (Luxembourg)";
1169 break;
1170 case 0x05:
1171 name = "German (Liechtenstein)";
1172 break;
1174 break;
1175 case 0x08:
1176 switch(secondary) {
1177 case 0x00:
1178 name = "Greek (Greece)";
1179 break;
1180 case 0x01:
1181 name = "Greek";
1182 break;
1184 break;
1185 case 0x09:
1186 switch(secondary) {
1187 case 0x00:
1188 case 0x01:
1189 name = "English (United States)";
1190 break;
1191 case 0x02:
1192 name = "English (United Kingdom)";
1193 break;
1194 case 0x03:
1195 name = "English (Australia)";
1196 break;
1197 case 0x04:
1198 name = "English (Canada)";
1199 break;
1200 case 0x05:
1201 name = "English (New Zealand)";
1202 break;
1203 case 0x06:
1204 name = "English (Ireland)";
1205 break;
1206 case 0x07:
1207 name = "English (South Africa)";
1208 break;
1209 case 0x08:
1210 name = "English (Jamaica)";
1211 break;
1212 case 0x09:
1213 name = "English (Caribbean)";
1214 break;
1215 case 0x0a:
1216 name = "English (Belize)";
1217 break;
1218 case 0x0b:
1219 name = "English (Trinidad and Tobago)";
1220 break;
1221 case 0x0c:
1222 name = "English (Zimbabwe)";
1223 break;
1224 case 0x0d:
1225 name = "English (Philippines)";
1226 break;
1227 case 0x10:
1228 name = "English (India)";
1229 break;
1230 case 0x11:
1231 name = "English (Malaysia)";
1232 break;
1233 case 0x12:
1234 name = "English (Singapore)";
1235 break;
1237 break;
1238 case 0x0a:
1239 switch(secondary) {
1240 case 0x00:
1241 name = "Spanish (Spain)";
1242 break;
1243 case 0x01:
1244 name = "Spanish (Traditional Sort)";
1245 break;
1246 case 0x02:
1247 name = "Spanish (Mexico)";
1248 break;
1249 case 0x03:
1250 name = "Spanish (International Sort)";
1251 break;
1252 case 0x04:
1253 name = "Spanish (Guatemala)";
1254 break;
1255 case 0x05:
1256 name = "Spanish (Costa Rica)";
1257 break;
1258 case 0x06:
1259 name = "Spanish (Panama)";
1260 break;
1261 case 0x07:
1262 name = "Spanish (Dominican Republic)";
1263 break;
1264 case 0x08:
1265 name = "Spanish (Venezuela)";
1266 break;
1267 case 0x09:
1268 name = "Spanish (Colombia)";
1269 break;
1270 case 0x0a:
1271 name = "Spanish (Peru)";
1272 break;
1273 case 0x0b:
1274 name = "Spanish (Argentina)";
1275 break;
1276 case 0x0c:
1277 name = "Spanish (Ecuador)";
1278 break;
1279 case 0x0d:
1280 name = "Spanish (Chile)";
1281 break;
1282 case 0x0e:
1283 name = "Spanish (Uruguay)";
1284 break;
1285 case 0x0f:
1286 name = "Spanish (Paraguay)";
1287 break;
1288 case 0x10:
1289 name = "Spanish (Bolivia)";
1290 break;
1291 case 0x11:
1292 name = "Spanish (El Salvador)";
1293 break;
1294 case 0x12:
1295 name = "Spanish (Honduras)";
1296 break;
1297 case 0x13:
1298 name = "Spanish (Nicaragua)";
1299 break;
1300 case 0x14:
1301 name = "Spanish (Puerto Rico)";
1302 break;
1303 case 0x15:
1304 name = "Spanish (United States)";
1305 break;
1307 break;
1308 case 0x0b:
1309 switch(secondary) {
1310 case 0x00:
1311 name = "Finnish (Finland)";
1312 break;
1313 case 0x01:
1314 name = "Finnish";
1315 break;
1317 break;
1318 case 0x0c:
1319 switch(secondary) {
1320 case 0x00:
1321 case 0x01:
1322 name = "French (France)";
1323 break;
1324 case 0x02:
1325 name = "French (Belgium)";
1326 break;
1327 case 0x03:
1328 name = "French (Canada)";
1329 break;
1330 case 0x04:
1331 name = "French (Switzerland)";
1332 break;
1333 case 0x05:
1334 name = "French (Luxembourg)";
1335 break;
1336 case 0x06:
1337 name = "French (Monaco)";
1338 break;
1340 break;
1341 case 0x0d:
1342 switch(secondary) {
1343 case 0x00:
1344 name = "Hebrew (Israel)";
1345 break;
1346 case 0x01:
1347 name = "Hebrew";
1348 break;
1350 break;
1351 case 0x0e:
1352 switch(secondary) {
1353 case 0x00:
1354 name = "Hungarian (Hungary)";
1355 break;
1356 case 0x01:
1357 name = "Hungarian";
1358 break;
1360 break;
1361 case 0x0f:
1362 switch(secondary) {
1363 case 0x00:
1364 name = "Icelandic (Iceland)";
1365 break;
1366 case 0x01:
1367 name = "Icelandic";
1368 break;
1370 break;
1371 case 0x10:
1372 switch(secondary) {
1373 case 0x00:
1374 case 0x01:
1375 name = "Italian (Italy)";
1376 break;
1377 case 0x02:
1378 name = "Italian (Switzerland)";
1379 break;
1381 break;
1382 case 0x11:
1383 switch(secondary) {
1384 case 0x00:
1385 name = "Japanese (Japan)";
1386 break;
1387 case 0x01:
1388 name = "Japanese";
1389 break;
1391 break;
1392 case 0x12:
1393 switch(secondary) {
1394 case 0x00:
1395 name = "Korean (Korea)";
1396 break;
1397 case 0x01:
1398 name = "Korean";
1399 break;
1401 break;
1402 case 0x13:
1403 switch(secondary) {
1404 case 0x00:
1405 case 0x01:
1406 name = "Dutch (Netherlands)";
1407 break;
1408 case 0x02:
1409 name = "Dutch (Belgium)";
1410 break;
1412 break;
1413 case 0x14:
1414 switch(secondary) {
1415 case 0x00:
1416 case 0x01:
1417 name = "Norwegian (Bokmal)";
1418 break;
1419 case 0x02:
1420 name = "Norwegian (Nynorsk)";
1421 break;
1423 break;
1424 case 0x15:
1425 switch(secondary) {
1426 case 0x00:
1427 name = "Polish (Poland)";
1428 break;
1429 case 0x01:
1430 name = "Polish";
1431 break;
1433 break;
1434 case 0x16:
1435 switch(secondary) {
1436 case 0x00:
1437 case 0x01:
1438 name = "Portuguese (Brazil)";
1439 break;
1440 case 0x02:
1441 name = "Portuguese (Portugal)";
1442 break;
1444 break;
1445 case 0x17:
1446 switch(secondary) {
1447 case 0x01:
1448 name = "Romansh (Switzerland)";
1449 break;
1451 break;
1452 case 0x18:
1453 switch(secondary) {
1454 case 0x00:
1455 name = "Romanian (Romania)";
1456 break;
1457 case 0x01:
1458 name = "Romanian";
1459 break;
1461 break;
1462 case 0x19:
1463 switch(secondary) {
1464 case 0x00:
1465 name = "Russian (Russia)";
1466 break;
1467 case 0x01:
1468 name = "Russian";
1469 break;
1471 break;
1472 case 0x1a:
1473 switch(secondary) {
1474 case 0x00:
1475 name = "Croatian (Croatia)";
1476 break;
1477 case 0x01:
1478 name = "Croatian";
1479 break;
1480 case 0x02:
1481 name = "Serbian (Latin)";
1482 break;
1483 case 0x03:
1484 name = "Serbian (Cyrillic)";
1485 break;
1486 case 0x04:
1487 name = "Croatian (Bosnia and Herzegovina)";
1488 break;
1489 case 0x05:
1490 name = "Bosnian (Latin, Bosnia and Herzegovina)";
1491 break;
1492 case 0x06:
1493 name = "Serbian (Latin, Bosnia and Herzegovina)";
1494 break;
1495 case 0x07:
1496 name = "Serbian (Cyrillic, Bosnia and Herzegovina)";
1497 break;
1498 case 0x08:
1499 name = "Bosnian (Cyrillic, Bosnia and Herzegovina)";
1500 break;
1502 break;
1503 case 0x1b:
1504 switch(secondary) {
1505 case 0x00:
1506 name = "Slovak (Slovakia)";
1507 break;
1508 case 0x01:
1509 name = "Slovak";
1510 break;
1512 break;
1513 case 0x1c:
1514 switch(secondary) {
1515 case 0x00:
1516 name = "Albanian (Albania)";
1517 break;
1518 case 0x01:
1519 name = "Albanian";
1520 break;
1522 break;
1523 case 0x1d:
1524 switch(secondary) {
1525 case 0x00:
1526 name = "Swedish (Sweden)";
1527 break;
1528 case 0x01:
1529 name = "Swedish";
1530 break;
1531 case 0x02:
1532 name = "Swedish (Finland)";
1533 break;
1535 break;
1536 case 0x1e:
1537 switch(secondary) {
1538 case 0x00:
1539 name = "Thai (Thailand)";
1540 break;
1541 case 0x01:
1542 name = "Thai";
1543 break;
1545 break;
1546 case 0x1f:
1547 switch(secondary) {
1548 case 0x00:
1549 name = "Turkish (Turkey)";
1550 break;
1551 case 0x01:
1552 name = "Turkish";
1553 break;
1555 break;
1556 case 0x20:
1557 switch(secondary) {
1558 case 0x00:
1559 name = "Urdu (Islamic Republic of Pakistan)";
1560 break;
1561 case 0x01:
1562 name = "Urdu";
1563 break;
1565 break;
1566 case 0x21:
1567 switch(secondary) {
1568 case 0x00:
1569 name = "Indonesian (Indonesia)";
1570 break;
1571 case 0x01:
1572 name = "Indonesian";
1573 break;
1575 break;
1576 case 0x22:
1577 switch(secondary) {
1578 case 0x00:
1579 name = "Ukrainian (Ukraine)";
1580 break;
1581 case 0x01:
1582 name = "Ukrainian";
1583 break;
1585 break;
1586 case 0x23:
1587 switch(secondary) {
1588 case 0x00:
1589 name = "Belarusian (Belarus)";
1590 break;
1591 case 0x01:
1592 name = "Belarusian";
1593 break;
1595 break;
1596 case 0x24:
1597 switch(secondary) {
1598 case 0x00:
1599 name = "Slovenian (Slovenia)";
1600 break;
1601 case 0x01:
1602 name = "Slovenian";
1603 break;
1605 break;
1606 case 0x25:
1607 switch(secondary) {
1608 case 0x00:
1609 name = "Estonian (Estonia)";
1610 break;
1611 case 0x01:
1612 name = "Estonian";
1613 break;
1615 break;
1616 case 0x26:
1617 switch(secondary) {
1618 case 0x00:
1619 name = "Latvian (Latvia)";
1620 break;
1621 case 0x01:
1622 name = "Latvian";
1623 break;
1625 break;
1626 case 0x27:
1627 switch(secondary) {
1628 case 0x00:
1629 name = "Lithuanian (Lithuania)";
1630 break;
1631 case 0x01:
1632 name = "Lithuanian";
1633 break;
1635 break;
1636 case 0x28:
1637 switch(secondary) {
1638 case 0x01:
1639 name = "Tajik (Tajikistan)";
1640 break;
1642 break;
1643 case 0x29:
1644 switch(secondary) {
1645 case 0x00:
1646 name = "Farsi (Iran)";
1647 break;
1648 case 0x01:
1649 name = "Farsi";
1650 break;
1652 break;
1653 case 0x2a:
1654 switch(secondary) {
1655 case 0x00:
1656 name = "Vietnamese (Viet Nam)";
1657 break;
1658 case 0x01:
1659 name = "Vietnamese";
1660 break;
1662 break;
1663 case 0x2b:
1664 switch(secondary) {
1665 case 0x00:
1666 name = "Armenian (Armenia)";
1667 break;
1668 case 0x01:
1669 name = "Armenian";
1670 break;
1672 break;
1673 case 0x2c:
1674 switch(secondary) {
1675 case 0x00:
1676 name = "Azeri (Latin) (Azerbaijan)";
1677 break;
1678 case 0x01:
1679 name = "Azeri (Latin)";
1680 break;
1681 case 0x02:
1682 name = "Azeri (Cyrillic)";
1683 break;
1685 break;
1686 case 0x2d:
1687 switch(secondary) {
1688 case 0x00:
1689 name = "Basque (Spain)";
1690 break;
1691 case 0x01:
1692 name = "Basque";
1693 break;
1695 break;
1696 case 0x2e:
1697 switch(secondary) {
1698 case 0x01:
1699 name = "Upper Sorbian (Germany)";
1700 break;
1701 case 0x02:
1702 name = "Lower Sorbian (Germany)";
1703 break;
1705 break;
1706 case 0x2f:
1707 switch(secondary) {
1708 case 0x00:
1709 name = "FYRO Macedonian (Former Yugoslav Republic of Macedonia)";
1710 break;
1711 case 0x01:
1712 name = "FYRO Macedonian";
1713 break;
1715 break;
1716 case 0x32:
1717 switch(secondary) {
1718 case 0x00:
1719 name = "Tswana (South Africa)";
1720 break;
1721 case 0x01:
1722 name = "Tswana";
1723 break;
1725 break;
1726 case 0x34:
1727 switch(secondary) {
1728 case 0x00:
1729 name = "Xhosa (South Africa)";
1730 break;
1731 case 0x01:
1732 name = "Xhosa";
1733 break;
1735 break;
1736 case 0x35:
1737 switch(secondary) {
1738 case 0x00:
1739 name = "Zulu (South Africa)";
1740 break;
1741 case 0x01:
1742 name = "Zulu";
1743 break;
1745 break;
1746 case 0x36:
1747 switch(secondary) {
1748 case 0x00:
1749 name = "Afrikaans (South Africa)";
1750 break;
1751 case 0x01:
1752 name = "Afrikaans";
1753 break;
1755 break;
1756 case 0x37:
1757 switch(secondary) {
1758 case 0x00:
1759 name = "Georgian (Georgia)";
1760 break;
1761 case 0x01:
1762 name = "Georgian";
1763 break;
1765 break;
1766 case 0x38:
1767 switch(secondary) {
1768 case 0x00:
1769 name = "Faroese (Faroe Islands)";
1770 break;
1771 case 0x01:
1772 name = "Faroese";
1773 break;
1775 break;
1776 case 0x39:
1777 switch(secondary) {
1778 case 0x00:
1779 name = "Hindi (India)";
1780 break;
1781 case 0x01:
1782 name = "Hindi";
1783 break;
1785 break;
1786 case 0x3a:
1787 switch(secondary) {
1788 case 0x00:
1789 name = "Maltese (Malta)";
1790 break;
1791 case 0x01:
1792 name = "Maltese";
1793 break;
1795 break;
1796 case 0x3b:
1797 switch(secondary) {
1798 case 0x00:
1799 name = "Sami (Northern) (Norway)";
1800 break;
1801 case 0x01:
1802 name = "Sami, Northern (Norway)";
1803 break;
1804 case 0x02:
1805 name = "Sami, Northern (Sweden)";
1806 break;
1807 case 0x03:
1808 name = "Sami, Northern (Finland)";
1809 break;
1810 case 0x04:
1811 name = "Sami, Lule (Norway)";
1812 break;
1813 case 0x05:
1814 name = "Sami, Lule (Sweden)";
1815 break;
1816 case 0x06:
1817 name = "Sami, Southern (Norway)";
1818 break;
1819 case 0x07:
1820 name = "Sami, Southern (Sweden)";
1821 break;
1822 case 0x08:
1823 name = "Sami, Skolt (Finland)";
1824 break;
1825 case 0x09:
1826 name = "Sami, Inari (Finland)";
1827 break;
1829 break;
1830 case 0x3c:
1831 switch(secondary) {
1832 case 0x02:
1833 name = "Irish (Ireland)";
1834 break;
1836 break;
1837 case 0x3e:
1838 switch(secondary) {
1839 case 0x00:
1840 case 0x01:
1841 name = "Malay (Malaysia)";
1842 break;
1843 case 0x02:
1844 name = "Malay (Brunei Darussalam)";
1845 break;
1847 break;
1848 case 0x3f:
1849 switch(secondary) {
1850 case 0x00:
1851 name = "Kazakh (Kazakhstan)";
1852 break;
1853 case 0x01:
1854 name = "Kazakh";
1855 break;
1857 break;
1858 case 0x40:
1859 switch(secondary) {
1860 case 0x00:
1861 name = "Kyrgyz (Kyrgyzstan)";
1862 break;
1863 case 0x01:
1864 name = "Kyrgyz (Cyrillic)";
1865 break;
1867 break;
1868 case 0x41:
1869 switch(secondary) {
1870 case 0x00:
1871 name = "Swahili (Kenya)";
1872 break;
1873 case 0x01:
1874 name = "Swahili";
1875 break;
1877 break;
1878 case 0x42:
1879 switch(secondary) {
1880 case 0x01:
1881 name = "Turkmen (Turkmenistan)";
1882 break;
1884 break;
1885 case 0x43:
1886 switch(secondary) {
1887 case 0x00:
1888 name = "Uzbek (Latin) (Uzbekistan)";
1889 break;
1890 case 0x01:
1891 name = "Uzbek (Latin)";
1892 break;
1893 case 0x02:
1894 name = "Uzbek (Cyrillic)";
1895 break;
1897 break;
1898 case 0x44:
1899 switch(secondary) {
1900 case 0x00:
1901 name = "Tatar (Russia)";
1902 break;
1903 case 0x01:
1904 name = "Tatar";
1905 break;
1907 break;
1908 case 0x45:
1909 switch(secondary) {
1910 case 0x00:
1911 case 0x01:
1912 name = "Bengali (India)";
1913 break;
1915 break;
1916 case 0x46:
1917 switch(secondary) {
1918 case 0x00:
1919 name = "Punjabi (India)";
1920 break;
1921 case 0x01:
1922 name = "Punjabi";
1923 break;
1925 break;
1926 case 0x47:
1927 switch(secondary) {
1928 case 0x00:
1929 name = "Gujarati (India)";
1930 break;
1931 case 0x01:
1932 name = "Gujarati";
1933 break;
1935 break;
1936 case 0x49:
1937 switch(secondary) {
1938 case 0x00:
1939 name = "Tamil (India)";
1940 break;
1941 case 0x01:
1942 name = "Tamil";
1943 break;
1945 break;
1946 case 0x4a:
1947 switch(secondary) {
1948 case 0x00:
1949 name = "Telugu (India)";
1950 break;
1951 case 0x01:
1952 name = "Telugu";
1953 break;
1955 break;
1956 case 0x4b:
1957 switch(secondary) {
1958 case 0x00:
1959 name = "Kannada (India)";
1960 break;
1961 case 0x01:
1962 name = "Kannada";
1963 break;
1965 break;
1966 case 0x4c:
1967 switch(secondary) {
1968 case 0x00:
1969 case 0x01:
1970 name = "Malayalam (India)";
1971 break;
1973 break;
1974 case 0x4d:
1975 switch(secondary) {
1976 case 0x01:
1977 name = "Assamese (India)";
1978 break;
1980 break;
1981 case 0x4e:
1982 switch(secondary) {
1983 case 0x00:
1984 name = "Marathi (India)";
1985 break;
1986 case 0x01:
1987 name = "Marathi";
1988 break;
1990 break;
1991 case 0x4f:
1992 switch(secondary) {
1993 case 0x00:
1994 name = "Sanskrit (India)";
1995 break;
1996 case 0x01:
1997 name = "Sanskrit";
1998 break;
2000 break;
2001 case 0x50:
2002 switch(secondary) {
2003 case 0x00:
2004 name = "Mongolian (Mongolia)";
2005 break;
2006 case 0x01:
2007 name = "Mongolian (Cyrillic)";
2008 break;
2009 case 0x02:
2010 name = "Mongolian (PRC)";
2011 break;
2013 break;
2014 case 0x51:
2015 switch(secondary) {
2016 case 0x01:
2017 name = "Tibetan (PRC)";
2018 break;
2019 case 0x02:
2020 name = "Tibetan (Bhutan)";
2021 break;
2023 break;
2024 case 0x52:
2025 switch(secondary) {
2026 case 0x00:
2027 name = "Welsh (United Kingdom)";
2028 break;
2029 case 0x01:
2030 name = "Welsh";
2031 break;
2033 break;
2034 case 0x53:
2035 switch(secondary) {
2036 case 0x01:
2037 name = "Khmer (Cambodia)";
2038 break;
2040 break;
2041 case 0x54:
2042 switch(secondary) {
2043 case 0x01:
2044 name = "Lao (Lao PDR)";
2045 break;
2047 break;
2048 case 0x56:
2049 switch(secondary) {
2050 case 0x00:
2051 name = "Galician (Spain)";
2052 break;
2053 case 0x01:
2054 name = "Galician";
2055 break;
2057 break;
2058 case 0x57:
2059 switch(secondary) {
2060 case 0x00:
2061 name = "Konkani (India)";
2062 break;
2063 case 0x01:
2064 name = "Konkani";
2065 break;
2067 break;
2068 case 0x5a:
2069 switch(secondary) {
2070 case 0x00:
2071 name = "Syriac (Syria)";
2072 break;
2073 case 0x01:
2074 name = "Syriac";
2075 break;
2077 break;
2078 case 0x5b:
2079 switch(secondary) {
2080 case 0x01:
2081 name = "Sinhala (Sri Lanka)";
2082 break;
2084 break;
2085 case 0x5d:
2086 switch(secondary) {
2087 case 0x01:
2088 name = "Inuktitut (Syllabics, Canada)";
2089 break;
2090 case 0x02:
2091 name = "Inuktitut (Latin, Canada)";
2092 break;
2094 break;
2095 case 0x5e:
2096 switch(secondary) {
2097 case 0x01:
2098 name = "Amharic (Ethiopia)";
2099 break;
2101 break;
2102 case 0x5f:
2103 switch(secondary) {
2104 case 0x02:
2105 name = "Tamazight (Algeria, Latin)";
2106 break;
2108 break;
2109 case 0x61:
2110 switch(secondary) {
2111 case 0x01:
2112 name = "Nepali (Nepal)";
2113 break;
2115 break;
2116 case 0x62:
2117 switch(secondary) {
2118 case 0x01:
2119 name = "Frisian (Netherlands)";
2120 break;
2122 break;
2123 case 0x63:
2124 switch(secondary) {
2125 case 0x01:
2126 name = "Pashto (Afghanistan)";
2127 break;
2129 break;
2130 case 0x64:
2131 switch(secondary) {
2132 case 0x01:
2133 name = "Filipino (Philippines)";
2134 break;
2136 break;
2137 case 0x65:
2138 switch(secondary) {
2139 case 0x00:
2140 name = "Divehi (Maldives)";
2141 break;
2142 case 0x01:
2143 name = "Divehi";
2144 break;
2146 break;
2147 case 0x68:
2148 switch(secondary) {
2149 case 0x01:
2150 name = "Hausa (Nigeria, Latin)";
2151 break;
2153 break;
2154 case 0x6a:
2155 switch(secondary) {
2156 case 0x01:
2157 name = "Yoruba (Nigeria)";
2158 break;
2160 break;
2161 case 0x6b:
2162 switch(secondary) {
2163 case 0x00:
2164 case 0x01:
2165 name = "Quechua (Bolivia)";
2166 break;
2167 case 0x02:
2168 name = "Quechua (Ecuador)";
2169 break;
2170 case 0x03:
2171 name = "Quechua (Peru)";
2172 break;
2174 break;
2175 case 0x6c:
2176 switch(secondary) {
2177 case 0x00:
2178 name = "Northern Sotho (South Africa)";
2179 break;
2180 case 0x01:
2181 name = "Northern Sotho";
2182 break;
2184 break;
2185 case 0x6d:
2186 switch(secondary) {
2187 case 0x01:
2188 name = "Bashkir (Russia)";
2189 break;
2191 break;
2192 case 0x6e:
2193 switch(secondary) {
2194 case 0x01:
2195 name = "Luxembourgish (Luxembourg)";
2196 break;
2198 break;
2199 case 0x6f:
2200 switch(secondary) {
2201 case 0x01:
2202 name = "Greenlandic (Greenland)";
2203 break;
2205 break;
2206 case 0x78:
2207 switch(secondary) {
2208 case 0x01:
2209 name = "Yi (PRC)";
2210 break;
2212 break;
2213 case 0x7a:
2214 switch(secondary) {
2215 case 0x01:
2216 name = "Mapudungun (Chile)";
2217 break;
2219 break;
2220 case 0x7c:
2221 switch(secondary) {
2222 case 0x01:
2223 name = "Mohawk (Mohawk)";
2224 break;
2226 break;
2227 case 0x7e:
2228 switch(secondary) {
2229 case 0x01:
2230 name = "Breton (France)";
2231 break;
2233 break;
2234 case 0x7f:
2235 switch(secondary) {
2236 case 0x00:
2237 name = "Invariant Language (Invariant Country)";
2238 break;
2240 break;
2241 case 0x80:
2242 switch(secondary) {
2243 case 0x01:
2244 name = "Uighur (PRC)";
2245 break;
2247 break;
2248 case 0x81:
2249 switch(secondary) {
2250 case 0x00:
2251 name = "Maori (New Zealand)";
2252 break;
2253 case 0x01:
2254 name = "Maori";
2255 break;
2257 break;
2258 case 0x83:
2259 switch(secondary) {
2260 case 0x01:
2261 name = "Corsican (France)";
2262 break;
2264 break;
2265 case 0x84:
2266 switch(secondary) {
2267 case 0x01:
2268 name = "Alsatian (France)";
2269 break;
2271 break;
2272 case 0x85:
2273 switch(secondary) {
2274 case 0x01:
2275 name = "Yakut (Russia)";
2276 break;
2278 break;
2279 case 0x86:
2280 switch(secondary) {
2281 case 0x01:
2282 name = "K'iche (Guatemala)";
2283 break;
2285 break;
2286 case 0x87:
2287 switch(secondary) {
2288 case 0x01:
2289 name = "Kinyarwanda (Rwanda)";
2290 break;
2292 break;
2293 case 0x88:
2294 switch(secondary) {
2295 case 0x01:
2296 name = "Wolof (Senegal)";
2297 break;
2299 break;
2300 case 0x8c:
2301 switch(secondary) {
2302 case 0x01:
2303 name = "Dari (Afghanistan)";
2304 break;
2306 break;
2308 default:
2309 name = "Language Neutral";
2313 if (!name)
2314 name = "Language Neutral";
2316 return copy_lang (lang_out, lang_len, name);