Fix typo
[mono-project.git] / mono / io-layer / versioninfo.c
blob11590c73e0a9d7f504a9cca45ccd2dffc38fde10
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 #define LOGDEBUG(...)
29 //define LOGDEBUG(...) g_message(__VA_ARGS__)
31 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
33 static WapiImageSectionHeader *
34 get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
36 WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
37 guint32 i;
39 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
40 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
41 if (size == 0) {
42 size = GUINT32_FROM_LE (section->SizeOfRawData);
45 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
46 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
47 return(section);
51 return(NULL);
54 /* This works for both 32bit and 64bit files, as the differences are
55 * all after the section header block
57 static gpointer
58 get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders, gpointer file_map)
60 WapiImageSectionHeader *section_header;
61 guint32 delta;
63 section_header = get_enclosing_section_header (rva, ntheaders);
64 if (section_header == NULL) {
65 return(NULL);
68 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
69 GUINT32_FROM_LE (section_header->PointerToRawData));
71 return((guint8 *)file_map + rva - delta);
74 static gpointer
75 scan_resource_dir (WapiImageResourceDirectory *root,
76 WapiImageNTHeaders32 *nt_headers,
77 gpointer file_map,
78 WapiImageResourceDirectoryEntry *entry,
79 int level, guint32 res_id, guint32 lang_id,
80 guint32 *size)
82 WapiImageResourceDirectoryEntry swapped_entry;
83 gboolean is_string, is_dir;
84 guint32 name_offset, dir_offset, data_offset;
86 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
87 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
89 is_string = swapped_entry.NameIsString;
90 is_dir = swapped_entry.DataIsDirectory;
91 name_offset = swapped_entry.NameOffset;
92 dir_offset = swapped_entry.OffsetToDirectory;
93 data_offset = swapped_entry.OffsetToData;
95 if (level == 0) {
96 /* Normally holds a directory entry for each type of
97 * resource
99 if ((is_string == FALSE &&
100 name_offset != res_id) ||
101 (is_string == TRUE)) {
102 return(NULL);
104 } else if (level == 1) {
105 /* Normally holds a directory entry for each resource
106 * item
108 } else if (level == 2) {
109 /* Normally holds a directory entry for each language
111 if ((is_string == FALSE &&
112 name_offset != lang_id &&
113 lang_id != 0) ||
114 (is_string == TRUE)) {
115 return(NULL);
117 } else {
118 g_assert_not_reached ();
121 if (is_dir == TRUE) {
122 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
123 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
124 guint32 entries, i;
126 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
128 for (i = 0; i < entries; i++) {
129 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
130 gpointer ret;
132 ret = scan_resource_dir (root, nt_headers, file_map,
133 sub_entry, level + 1, res_id,
134 lang_id, size);
135 if (ret != NULL) {
136 return(ret);
140 return(NULL);
141 } else {
142 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
143 *size = GUINT32_FROM_LE (data_entry->Size);
145 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
149 static gpointer
150 find_pe_file_resources32 (gpointer file_map, guint32 map_size,
151 guint32 res_id, guint32 lang_id,
152 guint32 *size)
154 WapiImageDosHeader *dos_header;
155 WapiImageNTHeaders32 *nt_headers;
156 WapiImageResourceDirectory *resource_dir;
157 WapiImageResourceDirectoryEntry *resource_dir_entry;
158 guint32 resource_rva, entries, i;
159 gpointer ret = NULL;
161 dos_header = (WapiImageDosHeader *)file_map;
162 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
163 LOGDEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
165 SetLastError (ERROR_INVALID_DATA);
166 return(NULL);
169 if (map_size < sizeof(WapiImageNTHeaders32) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
170 LOGDEBUG ("%s: File is too small: %d", __func__, map_size);
172 SetLastError (ERROR_BAD_LENGTH);
173 return(NULL);
176 nt_headers = (WapiImageNTHeaders32 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
177 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
178 LOGDEBUG ("%s: Bad NT signature 0x%x", __func__, nt_headers->Signature);
180 SetLastError (ERROR_INVALID_DATA);
181 return(NULL);
184 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
185 /* Do 64-bit stuff */
186 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
187 } else {
188 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
191 if (resource_rva == 0) {
192 LOGDEBUG ("%s: No resources in file!", __func__);
194 SetLastError (ERROR_INVALID_DATA);
195 return(NULL);
198 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
199 if (resource_dir == NULL) {
200 LOGDEBUG ("%s: Can't find resource directory", __func__);
202 SetLastError (ERROR_INVALID_DATA);
203 return(NULL);
206 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
207 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
209 for (i = 0; i < entries; i++) {
210 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
211 ret = scan_resource_dir (resource_dir,
212 (WapiImageNTHeaders32 *)nt_headers,
213 file_map, direntry, 0, res_id,
214 lang_id, size);
215 if (ret != NULL) {
216 return(ret);
220 return(NULL);
223 static gpointer
224 find_pe_file_resources64 (gpointer file_map, guint32 map_size,
225 guint32 res_id, guint32 lang_id,
226 guint32 *size)
228 WapiImageDosHeader *dos_header;
229 WapiImageNTHeaders64 *nt_headers;
230 WapiImageResourceDirectory *resource_dir;
231 WapiImageResourceDirectoryEntry *resource_dir_entry;
232 guint32 resource_rva, entries, i;
233 gpointer ret = NULL;
235 dos_header = (WapiImageDosHeader *)file_map;
236 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
237 LOGDEBUG ("%s: Bad dos signature 0x%x", __func__, dos_header->e_magic);
239 SetLastError (ERROR_INVALID_DATA);
240 return(NULL);
243 if (map_size < sizeof(WapiImageNTHeaders64) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
244 LOGDEBUG ("%s: File is too small: %d", __func__, map_size);
246 SetLastError (ERROR_BAD_LENGTH);
247 return(NULL);
250 nt_headers = (WapiImageNTHeaders64 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
251 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
252 LOGDEBUG ("%s: Bad NT signature 0x%x", __func__,
253 nt_headers->Signature);
255 SetLastError (ERROR_INVALID_DATA);
256 return(NULL);
259 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
260 /* Do 64-bit stuff */
261 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
262 } else {
263 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
266 if (resource_rva == 0) {
267 LOGDEBUG ("%s: No resources in file!", __func__);
269 SetLastError (ERROR_INVALID_DATA);
270 return(NULL);
273 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
274 if (resource_dir == NULL) {
275 LOGDEBUG ("%s: Can't find resource directory", __func__);
277 SetLastError (ERROR_INVALID_DATA);
278 return(NULL);
281 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
282 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
284 for (i = 0; i < entries; i++) {
285 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
286 ret = scan_resource_dir (resource_dir,
287 (WapiImageNTHeaders32 *)nt_headers,
288 file_map, direntry, 0, res_id,
289 lang_id, size);
290 if (ret != NULL) {
291 return(ret);
295 return(NULL);
298 static gpointer
299 find_pe_file_resources (gpointer file_map, guint32 map_size,
300 guint32 res_id, guint32 lang_id,
301 guint32 *size)
303 /* Figure this out when we support 64bit PE files */
304 if (1) {
305 return find_pe_file_resources32 (file_map, map_size, res_id,
306 lang_id, size);
307 } else {
308 return find_pe_file_resources64 (file_map, map_size, res_id,
309 lang_id, size);
313 static gpointer
314 map_pe_file (gunichar2 *filename, gint32 *map_size, void **handle)
316 gchar *filename_ext;
317 int fd;
318 struct stat statbuf;
319 gpointer file_map;
321 /* According to the MSDN docs, a search path is applied to
322 * filename. FIXME: implement this, for now just pass it
323 * straight to fopen
326 filename_ext = mono_unicode_to_external (filename);
327 if (filename_ext == NULL) {
328 LOGDEBUG ("%s: unicode conversion returned NULL", __func__);
330 SetLastError (ERROR_INVALID_NAME);
331 return(NULL);
334 fd = _wapi_open (filename_ext, O_RDONLY, 0);
335 if (fd == -1) {
336 LOGDEBUG ("%s: Error opening file %s: %s", __func__, filename_ext, strerror (errno));
338 SetLastError (_wapi_get_win32_file_error (errno));
339 g_free (filename_ext);
341 return(NULL);
344 if (fstat (fd, &statbuf) == -1) {
345 LOGDEBUG ("%s: Error stat()ing file %s: %s", __func__, filename_ext, strerror (errno));
347 SetLastError (_wapi_get_win32_file_error (errno));
348 g_free (filename_ext);
349 close (fd);
350 return(NULL);
352 *map_size = statbuf.st_size;
354 /* Check basic file size */
355 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
356 LOGDEBUG ("%s: File %s is too small: %lld", __func__, filename_ext, statbuf.st_size);
358 SetLastError (ERROR_BAD_LENGTH);
359 g_free (filename_ext);
360 close (fd);
361 return(NULL);
364 file_map = mono_file_map (statbuf.st_size, MONO_MMAP_READ | MONO_MMAP_PRIVATE, fd, 0, handle);
365 if (file_map == NULL) {
366 LOGDEBUG ("%s: Error mmap()int file %s: %s", __func__, filename_ext, strerror (errno));
368 SetLastError (_wapi_get_win32_file_error (errno));
369 g_free (filename_ext);
370 close (fd);
371 return(NULL);
374 /* Don't need the fd any more */
375 close (fd);
376 g_free (filename_ext);
378 return(file_map);
381 static void
382 unmap_pe_file (gpointer file_map, void *handle)
384 mono_file_unmap (file_map, handle);
387 static guint32
388 unicode_chars (const gunichar2 *str)
390 guint32 len = 0;
392 do {
393 if (str[len] == '\0') {
394 return(len);
396 len++;
397 } while(1);
400 static gboolean
401 unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
403 while (*str1 && *str2) {
404 if (*str1 != *str2) {
405 return(FALSE);
407 ++str1;
408 ++str2;
411 return(*str1 == *str2);
414 /* compare a little-endian null-terminated utf16 string and a normal string.
415 * Can be used only for ascii or latin1 chars.
417 static gboolean
418 unicode_string_equals (const gunichar2 *str1, const gchar *str2)
420 while (*str1 && *str2) {
421 if (GUINT16_TO_LE (*str1) != *str2) {
422 return(FALSE);
424 ++str1;
425 ++str2;
428 return(*str1 == *str2);
431 typedef struct
433 guint16 data_len;
434 guint16 value_len;
435 guint16 type;
436 gunichar2 *key;
437 } version_data;
439 /* Returns a pointer to the value data, because there's no way to know
440 * how big that data is (value_len is set to zero for most blocks :-( )
442 static gconstpointer
443 get_versioninfo_block (gconstpointer data, version_data *block)
445 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
446 data = (char *)data + sizeof(guint16);
447 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
448 data = (char *)data + sizeof(guint16);
450 /* No idea what the type is supposed to indicate */
451 block->type = GUINT16_FROM_LE (*((guint16 *)data));
452 data = (char *)data + sizeof(guint16);
453 block->key = ((gunichar2 *)data);
455 /* Skip over the key (including the terminator) */
456 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
458 /* align on a 32-bit boundary */
459 ALIGN32 (data);
461 return(data);
464 static gconstpointer
465 get_fixedfileinfo_block (gconstpointer data, version_data *block)
467 gconstpointer data_ptr;
468 gint32 data_len; /* signed to guard against underflow */
469 WapiFixedFileInfo *ffi;
471 data_ptr = get_versioninfo_block (data, block);
472 data_len = block->data_len;
474 if (block->value_len != sizeof(WapiFixedFileInfo)) {
475 LOGDEBUG ("%s: FIXEDFILEINFO size mismatch", __func__);
476 return(NULL);
479 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
480 LOGDEBUG ("%s: VS_VERSION_INFO mismatch", __func__);
482 return(NULL);
485 ffi = ((WapiFixedFileInfo *)data_ptr);
486 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
487 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
488 LOGDEBUG ("%s: FIXEDFILEINFO bad signature", __func__);
490 return(NULL);
493 return(data_ptr);
496 static gconstpointer
497 get_varfileinfo_block (gconstpointer data_ptr, version_data *block)
499 /* data is pointing at a Var block
501 data_ptr = get_versioninfo_block (data_ptr, block);
503 return(data_ptr);
506 static gconstpointer
507 get_string_block (gconstpointer data_ptr,
508 const gunichar2 *string_key,
509 gpointer *string_value,
510 guint32 *string_value_len,
511 version_data *block)
513 guint16 data_len = block->data_len;
514 guint16 string_len = 28; /* Length of the StringTable block */
515 char *orig_data_ptr = (char *)data_ptr - 28;
517 /* data_ptr is pointing at an array of one or more String blocks
518 * with total length (not including alignment padding) of
519 * data_len
521 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
522 /* align on a 32-bit boundary */
523 ALIGN32 (data_ptr);
525 data_ptr = get_versioninfo_block (data_ptr, block);
526 if (block->data_len == 0) {
527 /* We must have hit padding, so give up
528 * processing now
530 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
532 return(NULL);
535 string_len = string_len + block->data_len;
537 if (string_key != NULL &&
538 string_value != NULL &&
539 string_value_len != NULL &&
540 unicode_compare (string_key, block->key) == TRUE) {
541 *string_value = (gpointer)data_ptr;
542 *string_value_len = block->value_len;
545 /* Skip over the value */
546 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
549 return(data_ptr);
552 /* Returns a pointer to the byte following the Stringtable block, or
553 * NULL if the data read hits padding. We can't recover from this
554 * because the data length does not include padding bytes, so it's not
555 * possible to just return the start position + length
557 * If lang == NULL it means we're just stepping through this block
559 static gconstpointer
560 get_stringtable_block (gconstpointer data_ptr,
561 gchar *lang,
562 const gunichar2 *string_key,
563 gpointer *string_value,
564 guint32 *string_value_len,
565 version_data *block)
567 guint16 data_len = block->data_len;
568 guint16 string_len = 36; /* length of the StringFileInfo block */
569 gchar *found_lang;
570 gchar *lowercase_lang;
572 /* data_ptr is pointing at an array of StringTable blocks,
573 * with total length (not including alignment padding) of
574 * data_len
577 while(string_len < data_len) {
578 /* align on a 32-bit boundary */
579 ALIGN32 (data_ptr);
581 data_ptr = get_versioninfo_block (data_ptr, block);
582 if (block->data_len == 0) {
583 /* We must have hit padding, so give up
584 * processing now
586 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
587 return(NULL);
590 string_len = string_len + block->data_len;
592 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
593 if (found_lang == NULL) {
594 LOGDEBUG ("%s: Didn't find a valid language key, giving up", __func__);
595 return(NULL);
598 lowercase_lang = g_utf8_strdown (found_lang, -1);
599 g_free (found_lang);
600 found_lang = lowercase_lang;
601 lowercase_lang = NULL;
603 if (lang != NULL && !strcmp (found_lang, lang)) {
604 /* Got the one we're interested in */
605 data_ptr = get_string_block (data_ptr, string_key,
606 string_value,
607 string_value_len, block);
608 } else {
609 data_ptr = get_string_block (data_ptr, NULL, NULL,
610 NULL, block);
613 g_free (found_lang);
615 if (data_ptr == NULL) {
616 /* Child block hit padding */
617 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
618 return(NULL);
622 return(data_ptr);
625 #if G_BYTE_ORDER == G_BIG_ENDIAN
626 static gconstpointer
627 big_up_string_block (gconstpointer data_ptr, version_data *block)
629 guint16 data_len = block->data_len;
630 guint16 string_len = 28; /* Length of the StringTable block */
631 gchar *big_value;
632 char *orig_data_ptr = (char *)data_ptr - 28;
634 /* data_ptr is pointing at an array of one or more String
635 * blocks with total length (not including alignment padding)
636 * of data_len
638 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
639 /* align on a 32-bit boundary */
640 ALIGN32 (data_ptr);
642 data_ptr = get_versioninfo_block (data_ptr, block);
643 if (block->data_len == 0) {
644 /* We must have hit padding, so give up
645 * processing now
647 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
648 return(NULL);
651 string_len = string_len + block->data_len;
653 big_value = g_convert ((gchar *)block->key,
654 unicode_chars (block->key) * 2,
655 "UTF-16BE", "UTF-16LE", NULL, NULL,
656 NULL);
657 if (big_value == NULL) {
658 LOGDEBUG ("%s: Didn't find a valid string, giving up", __func__);
659 return(NULL);
662 /* The swapped string should be exactly the same
663 * length as the original little-endian one, but only
664 * copy the number of original chars just to be on the
665 * safe side
667 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
668 g_free (big_value);
670 big_value = g_convert ((gchar *)data_ptr,
671 unicode_chars (data_ptr) * 2,
672 "UTF-16BE", "UTF-16LE", NULL, NULL,
673 NULL);
674 if (big_value == NULL) {
675 LOGDEBUG ("%s: Didn't find a valid data string, giving up", __func__);
676 return(NULL);
678 memcpy ((gpointer)data_ptr, big_value,
679 unicode_chars (data_ptr) * 2);
680 g_free (big_value);
682 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
685 return(data_ptr);
688 /* Returns a pointer to the byte following the Stringtable block, or
689 * NULL if the data read hits padding. We can't recover from this
690 * because the data length does not include padding bytes, so it's not
691 * possible to just return the start position + length
693 static gconstpointer
694 big_up_stringtable_block (gconstpointer data_ptr, version_data *block)
696 guint16 data_len = block->data_len;
697 guint16 string_len = 36; /* length of the StringFileInfo block */
698 gchar *big_value;
700 /* data_ptr is pointing at an array of StringTable blocks,
701 * with total length (not including alignment padding) of
702 * data_len
705 while(string_len < data_len) {
706 /* align on a 32-bit boundary */
707 ALIGN32 (data_ptr);
709 data_ptr = get_versioninfo_block (data_ptr, block);
710 if (block->data_len == 0) {
711 /* We must have hit padding, so give up
712 * processing now
714 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
715 return(NULL);
718 string_len = string_len + block->data_len;
720 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
721 "UTF-16LE", NULL, NULL, NULL);
722 if (big_value == NULL) {
723 LOGDEBUG ("%s: Didn't find a valid string, giving up", __func__);
724 return(NULL);
727 memcpy (block->key, big_value, 16);
728 g_free (big_value);
730 data_ptr = big_up_string_block (data_ptr, block);
732 if (data_ptr == NULL) {
733 /* Child block hit padding */
734 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
735 return(NULL);
739 return(data_ptr);
742 /* Follows the data structures and turns all UTF-16 strings from the
743 * LE found in the resource section into UTF-16BE
745 static void
746 big_up (gconstpointer datablock, guint32 size)
748 gconstpointer data_ptr;
749 gint32 data_len; /* signed to guard against underflow */
750 version_data block;
752 data_ptr = get_fixedfileinfo_block (datablock, &block);
753 if (data_ptr != NULL) {
754 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
756 /* Byteswap all the fields */
757 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
758 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
759 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
760 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
761 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
762 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
763 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
764 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
765 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
766 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
767 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
769 /* The FFI and header occupies the first 92 bytes
771 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
772 data_len = block.data_len - 92;
774 /* There now follow zero or one StringFileInfo blocks
775 * and zero or one VarFileInfo blocks
777 while (data_len > 0) {
778 /* align on a 32-bit boundary */
779 ALIGN32 (data_ptr);
781 data_ptr = get_versioninfo_block (data_ptr, &block);
782 if (block.data_len == 0) {
783 /* We must have hit padding, so give
784 * up processing now
786 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
787 return;
790 data_len = data_len - block.data_len;
792 if (unicode_string_equals (block.key, "VarFileInfo")) {
793 data_ptr = get_varfileinfo_block (data_ptr,
794 &block);
795 data_ptr = ((guchar *)data_ptr) + block.value_len;
796 } else if (unicode_string_equals (block.key,
797 "StringFileInfo")) {
798 data_ptr = big_up_stringtable_block (data_ptr,
799 &block);
800 } else {
801 /* Bogus data */
802 LOGDEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
803 return;
806 if (data_ptr == NULL) {
807 /* Child block hit padding */
808 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
809 return;
814 #endif
816 gboolean
817 VerQueryValue (gconstpointer datablock, const gunichar2 *subblock, gpointer *buffer, guint32 *len)
819 gchar *subblock_utf8, *lang_utf8 = NULL;
820 gboolean ret = FALSE;
821 version_data block;
822 gconstpointer data_ptr;
823 gint32 data_len; /* signed to guard against underflow */
824 gboolean want_var = FALSE;
825 gboolean want_string = FALSE;
826 gunichar2 lang[8];
827 const gunichar2 *string_key = NULL;
828 gpointer string_value = NULL;
829 guint32 string_value_len = 0;
830 gchar *lowercase_lang;
832 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
833 if (subblock_utf8 == NULL) {
834 return(FALSE);
837 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
838 want_var = TRUE;
839 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
840 want_string = TRUE;
841 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
842 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
843 lowercase_lang = g_utf8_strdown (lang_utf8, -1);
844 g_free (lang_utf8);
845 lang_utf8 = lowercase_lang;
846 lowercase_lang = NULL;
847 string_key = subblock + 25;
850 if (!strcmp (subblock_utf8, "\\")) {
851 data_ptr = get_fixedfileinfo_block (datablock, &block);
852 if (data_ptr != NULL) {
853 *buffer = (gpointer)data_ptr;
854 *len = block.value_len;
856 ret = TRUE;
858 } else if (want_var || want_string) {
859 data_ptr = get_fixedfileinfo_block (datablock, &block);
860 if (data_ptr != NULL) {
861 /* The FFI and header occupies the first 92
862 * bytes
864 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
865 data_len = block.data_len - 92;
867 /* There now follow zero or one StringFileInfo
868 * blocks and zero or one VarFileInfo blocks
870 while (data_len > 0) {
871 /* align on a 32-bit boundary */
872 ALIGN32 (data_ptr);
874 data_ptr = get_versioninfo_block (data_ptr,
875 &block);
876 if (block.data_len == 0) {
877 /* We must have hit padding,
878 * so give up processing now
880 LOGDEBUG ("%s: Hit 0-length block, giving up", __func__);
881 goto done;
884 data_len = data_len - block.data_len;
886 if (unicode_string_equals (block.key, "VarFileInfo")) {
887 data_ptr = get_varfileinfo_block (data_ptr, &block);
888 if (want_var) {
889 *buffer = (gpointer)data_ptr;
890 *len = block.value_len;
891 ret = TRUE;
892 goto done;
893 } else {
894 /* Skip over the Var block */
895 data_ptr = ((guchar *)data_ptr) + block.value_len;
897 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
898 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
899 if (want_string &&
900 string_value != NULL &&
901 string_value_len != 0) {
902 *buffer = string_value;
903 *len = unicode_chars (string_value) + 1; /* Include trailing null */
904 ret = TRUE;
905 goto done;
907 } else {
908 /* Bogus data */
909 LOGDEBUG ("%s: Not a valid VERSIONINFO child block", __func__);
910 goto done;
913 if (data_ptr == NULL) {
914 /* Child block hit padding */
915 LOGDEBUG ("%s: Child block hit 0-length block, giving up", __func__);
916 goto done;
922 done:
923 if (lang_utf8) {
924 g_free (lang_utf8);
927 g_free (subblock_utf8);
928 return(ret);
931 guint32
932 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
934 gpointer file_map;
935 gpointer versioninfo;
936 void *map_handle;
937 gint32 map_size;
938 guint32 size;
940 /* This value is unused, but set to zero */
941 *handle = 0;
943 file_map = map_pe_file (filename, &map_size, &map_handle);
944 if (file_map == NULL) {
945 return(0);
948 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION, 0, &size);
949 if (versioninfo == NULL) {
950 /* Didn't find the resource, so set the return value
951 * to 0
953 size = 0;
956 unmap_pe_file (file_map, map_handle);
958 return(size);
961 gboolean
962 GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED, guint32 len, gpointer data)
964 gpointer file_map;
965 gpointer versioninfo;
966 void *map_handle;
967 gint32 map_size;
968 guint32 size;
969 gboolean ret = FALSE;
971 file_map = map_pe_file (filename, &map_size, &map_handle);
972 if (file_map == NULL) {
973 return(FALSE);
976 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
977 0, &size);
978 if (versioninfo != NULL) {
979 /* This could probably process the data so that
980 * VerQueryValue() doesn't have to follow the data
981 * blocks every time. But hey, these functions aren't
982 * likely to appear in many profiles.
984 memcpy (data, versioninfo, len < size?len:size);
985 ret = TRUE;
987 #if G_BYTE_ORDER == G_BIG_ENDIAN
988 big_up (data, size);
989 #endif
992 unmap_pe_file (file_map, map_handle);
994 return(ret);
997 static guint32
998 copy_lang (gunichar2 *lang_out, guint32 lang_len, const gchar *text)
1000 gunichar2 *unitext;
1001 int chars = strlen (text);
1002 int ret;
1004 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1005 g_assert (unitext != NULL);
1007 if (chars < (lang_len - 1)) {
1008 memcpy (lang_out, (gpointer)unitext, chars * 2);
1009 lang_out[chars] = '\0';
1010 ret = chars;
1011 } else {
1012 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1013 lang_out[lang_len] = '\0';
1014 ret = lang_len;
1017 g_free (unitext);
1019 return(ret);
1022 guint32
1023 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1025 int primary, secondary;
1026 const char *name = NULL;
1028 primary = lang & 0x3FF;
1029 secondary = (lang >> 10) & 0x3F;
1031 switch(primary) {
1032 case 0x00:
1033 switch(secondary) {
1034 case 0x01:
1035 name = "Process Default Language";
1036 break;
1038 break;
1039 case 0x01:
1040 switch(secondary) {
1041 case 0x00:
1042 case 0x01:
1043 name = "Arabic (Saudi Arabia)";
1044 break;
1045 case 0x02:
1046 name = "Arabic (Iraq)";
1047 break;
1048 case 0x03:
1049 name = "Arabic (Egypt)";
1050 break;
1051 case 0x04:
1052 name = "Arabic (Libya)";
1053 break;
1054 case 0x05:
1055 name = "Arabic (Algeria)";
1056 break;
1057 case 0x06:
1058 name = "Arabic (Morocco)";
1059 break;
1060 case 0x07:
1061 name = "Arabic (Tunisia)";
1062 break;
1063 case 0x08:
1064 name = "Arabic (Oman)";
1065 break;
1066 case 0x09:
1067 name = "Arabic (Yemen)";
1068 break;
1069 case 0x0a:
1070 name = "Arabic (Syria)";
1071 break;
1072 case 0x0b:
1073 name = "Arabic (Jordan)";
1074 break;
1075 case 0x0c:
1076 name = "Arabic (Lebanon)";
1077 break;
1078 case 0x0d:
1079 name = "Arabic (Kuwait)";
1080 break;
1081 case 0x0e:
1082 name = "Arabic (U.A.E.)";
1083 break;
1084 case 0x0f:
1085 name = "Arabic (Bahrain)";
1086 break;
1087 case 0x10:
1088 name = "Arabic (Qatar)";
1089 break;
1091 break;
1092 case 0x02:
1093 switch(secondary) {
1094 case 0x00:
1095 name = "Bulgarian (Bulgaria)";
1096 break;
1097 case 0x01:
1098 name = "Bulgarian";
1099 break;
1101 break;
1102 case 0x03:
1103 switch(secondary) {
1104 case 0x00:
1105 name = "Catalan (Spain)";
1106 break;
1107 case 0x01:
1108 name = "Catalan";
1109 break;
1111 break;
1112 case 0x04:
1113 switch(secondary) {
1114 case 0x00:
1115 case 0x01:
1116 name = "Chinese (Taiwan)";
1117 break;
1118 case 0x02:
1119 name = "Chinese (PRC)";
1120 break;
1121 case 0x03:
1122 name = "Chinese (Hong Kong S.A.R.)";
1123 break;
1124 case 0x04:
1125 name = "Chinese (Singapore)";
1126 break;
1127 case 0x05:
1128 name = "Chinese (Macau S.A.R.)";
1129 break;
1131 break;
1132 case 0x05:
1133 switch(secondary) {
1134 case 0x00:
1135 name = "Czech (Czech Republic)";
1136 break;
1137 case 0x01:
1138 name = "Czech";
1139 break;
1141 break;
1142 case 0x06:
1143 switch(secondary) {
1144 case 0x00:
1145 name = "Danish (Denmark)";
1146 break;
1147 case 0x01:
1148 name = "Danish";
1149 break;
1151 break;
1152 case 0x07:
1153 switch(secondary) {
1154 case 0x00:
1155 case 0x01:
1156 name = "German (Germany)";
1157 break;
1158 case 0x02:
1159 name = "German (Switzerland)";
1160 break;
1161 case 0x03:
1162 name = "German (Austria)";
1163 break;
1164 case 0x04:
1165 name = "German (Luxembourg)";
1166 break;
1167 case 0x05:
1168 name = "German (Liechtenstein)";
1169 break;
1171 break;
1172 case 0x08:
1173 switch(secondary) {
1174 case 0x00:
1175 name = "Greek (Greece)";
1176 break;
1177 case 0x01:
1178 name = "Greek";
1179 break;
1181 break;
1182 case 0x09:
1183 switch(secondary) {
1184 case 0x00:
1185 case 0x01:
1186 name = "English (United States)";
1187 break;
1188 case 0x02:
1189 name = "English (United Kingdom)";
1190 break;
1191 case 0x03:
1192 name = "English (Australia)";
1193 break;
1194 case 0x04:
1195 name = "English (Canada)";
1196 break;
1197 case 0x05:
1198 name = "English (New Zealand)";
1199 break;
1200 case 0x06:
1201 name = "English (Ireland)";
1202 break;
1203 case 0x07:
1204 name = "English (South Africa)";
1205 break;
1206 case 0x08:
1207 name = "English (Jamaica)";
1208 break;
1209 case 0x09:
1210 name = "English (Caribbean)";
1211 break;
1212 case 0x0a:
1213 name = "English (Belize)";
1214 break;
1215 case 0x0b:
1216 name = "English (Trinidad and Tobago)";
1217 break;
1218 case 0x0c:
1219 name = "English (Zimbabwe)";
1220 break;
1221 case 0x0d:
1222 name = "English (Philippines)";
1223 break;
1224 case 0x10:
1225 name = "English (India)";
1226 break;
1227 case 0x11:
1228 name = "English (Malaysia)";
1229 break;
1230 case 0x12:
1231 name = "English (Singapore)";
1232 break;
1234 break;
1235 case 0x0a:
1236 switch(secondary) {
1237 case 0x00:
1238 name = "Spanish (Spain)";
1239 break;
1240 case 0x01:
1241 name = "Spanish (Traditional Sort)";
1242 break;
1243 case 0x02:
1244 name = "Spanish (Mexico)";
1245 break;
1246 case 0x03:
1247 name = "Spanish (International Sort)";
1248 break;
1249 case 0x04:
1250 name = "Spanish (Guatemala)";
1251 break;
1252 case 0x05:
1253 name = "Spanish (Costa Rica)";
1254 break;
1255 case 0x06:
1256 name = "Spanish (Panama)";
1257 break;
1258 case 0x07:
1259 name = "Spanish (Dominican Republic)";
1260 break;
1261 case 0x08:
1262 name = "Spanish (Venezuela)";
1263 break;
1264 case 0x09:
1265 name = "Spanish (Colombia)";
1266 break;
1267 case 0x0a:
1268 name = "Spanish (Peru)";
1269 break;
1270 case 0x0b:
1271 name = "Spanish (Argentina)";
1272 break;
1273 case 0x0c:
1274 name = "Spanish (Ecuador)";
1275 break;
1276 case 0x0d:
1277 name = "Spanish (Chile)";
1278 break;
1279 case 0x0e:
1280 name = "Spanish (Uruguay)";
1281 break;
1282 case 0x0f:
1283 name = "Spanish (Paraguay)";
1284 break;
1285 case 0x10:
1286 name = "Spanish (Bolivia)";
1287 break;
1288 case 0x11:
1289 name = "Spanish (El Salvador)";
1290 break;
1291 case 0x12:
1292 name = "Spanish (Honduras)";
1293 break;
1294 case 0x13:
1295 name = "Spanish (Nicaragua)";
1296 break;
1297 case 0x14:
1298 name = "Spanish (Puerto Rico)";
1299 break;
1300 case 0x15:
1301 name = "Spanish (United States)";
1302 break;
1304 break;
1305 case 0x0b:
1306 switch(secondary) {
1307 case 0x00:
1308 name = "Finnish (Finland)";
1309 break;
1310 case 0x01:
1311 name = "Finnish";
1312 break;
1314 break;
1315 case 0x0c:
1316 switch(secondary) {
1317 case 0x00:
1318 case 0x01:
1319 name = "French (France)";
1320 break;
1321 case 0x02:
1322 name = "French (Belgium)";
1323 break;
1324 case 0x03:
1325 name = "French (Canada)";
1326 break;
1327 case 0x04:
1328 name = "French (Switzerland)";
1329 break;
1330 case 0x05:
1331 name = "French (Luxembourg)";
1332 break;
1333 case 0x06:
1334 name = "French (Monaco)";
1335 break;
1337 break;
1338 case 0x0d:
1339 switch(secondary) {
1340 case 0x00:
1341 name = "Hebrew (Israel)";
1342 break;
1343 case 0x01:
1344 name = "Hebrew";
1345 break;
1347 break;
1348 case 0x0e:
1349 switch(secondary) {
1350 case 0x00:
1351 name = "Hungarian (Hungary)";
1352 break;
1353 case 0x01:
1354 name = "Hungarian";
1355 break;
1357 break;
1358 case 0x0f:
1359 switch(secondary) {
1360 case 0x00:
1361 name = "Icelandic (Iceland)";
1362 break;
1363 case 0x01:
1364 name = "Icelandic";
1365 break;
1367 break;
1368 case 0x10:
1369 switch(secondary) {
1370 case 0x00:
1371 case 0x01:
1372 name = "Italian (Italy)";
1373 break;
1374 case 0x02:
1375 name = "Italian (Switzerland)";
1376 break;
1378 break;
1379 case 0x11:
1380 switch(secondary) {
1381 case 0x00:
1382 name = "Japanese (Japan)";
1383 break;
1384 case 0x01:
1385 name = "Japanese";
1386 break;
1388 break;
1389 case 0x12:
1390 switch(secondary) {
1391 case 0x00:
1392 name = "Korean (Korea)";
1393 break;
1394 case 0x01:
1395 name = "Korean";
1396 break;
1398 break;
1399 case 0x13:
1400 switch(secondary) {
1401 case 0x00:
1402 case 0x01:
1403 name = "Dutch (Netherlands)";
1404 break;
1405 case 0x02:
1406 name = "Dutch (Belgium)";
1407 break;
1409 break;
1410 case 0x14:
1411 switch(secondary) {
1412 case 0x00:
1413 case 0x01:
1414 name = "Norwegian (Bokmal)";
1415 break;
1416 case 0x02:
1417 name = "Norwegian (Nynorsk)";
1418 break;
1420 break;
1421 case 0x15:
1422 switch(secondary) {
1423 case 0x00:
1424 name = "Polish (Poland)";
1425 break;
1426 case 0x01:
1427 name = "Polish";
1428 break;
1430 break;
1431 case 0x16:
1432 switch(secondary) {
1433 case 0x00:
1434 case 0x01:
1435 name = "Portuguese (Brazil)";
1436 break;
1437 case 0x02:
1438 name = "Portuguese (Portugal)";
1439 break;
1441 break;
1442 case 0x17:
1443 switch(secondary) {
1444 case 0x01:
1445 name = "Romansh (Switzerland)";
1446 break;
1448 break;
1449 case 0x18:
1450 switch(secondary) {
1451 case 0x00:
1452 name = "Romanian (Romania)";
1453 break;
1454 case 0x01:
1455 name = "Romanian";
1456 break;
1458 break;
1459 case 0x19:
1460 switch(secondary) {
1461 case 0x00:
1462 name = "Russian (Russia)";
1463 break;
1464 case 0x01:
1465 name = "Russian";
1466 break;
1468 break;
1469 case 0x1a:
1470 switch(secondary) {
1471 case 0x00:
1472 name = "Croatian (Croatia)";
1473 break;
1474 case 0x01:
1475 name = "Croatian";
1476 break;
1477 case 0x02:
1478 name = "Serbian (Latin)";
1479 break;
1480 case 0x03:
1481 name = "Serbian (Cyrillic)";
1482 break;
1483 case 0x04:
1484 name = "Croatian (Bosnia and Herzegovina)";
1485 break;
1486 case 0x05:
1487 name = "Bosnian (Latin, Bosnia and Herzegovina)";
1488 break;
1489 case 0x06:
1490 name = "Serbian (Latin, Bosnia and Herzegovina)";
1491 break;
1492 case 0x07:
1493 name = "Serbian (Cyrillic, Bosnia and Herzegovina)";
1494 break;
1495 case 0x08:
1496 name = "Bosnian (Cyrillic, Bosnia and Herzegovina)";
1497 break;
1499 break;
1500 case 0x1b:
1501 switch(secondary) {
1502 case 0x00:
1503 name = "Slovak (Slovakia)";
1504 break;
1505 case 0x01:
1506 name = "Slovak";
1507 break;
1509 break;
1510 case 0x1c:
1511 switch(secondary) {
1512 case 0x00:
1513 name = "Albanian (Albania)";
1514 break;
1515 case 0x01:
1516 name = "Albanian";
1517 break;
1519 break;
1520 case 0x1d:
1521 switch(secondary) {
1522 case 0x00:
1523 name = "Swedish (Sweden)";
1524 break;
1525 case 0x01:
1526 name = "Swedish";
1527 break;
1528 case 0x02:
1529 name = "Swedish (Finland)";
1530 break;
1532 break;
1533 case 0x1e:
1534 switch(secondary) {
1535 case 0x00:
1536 name = "Thai (Thailand)";
1537 break;
1538 case 0x01:
1539 name = "Thai";
1540 break;
1542 break;
1543 case 0x1f:
1544 switch(secondary) {
1545 case 0x00:
1546 name = "Turkish (Turkey)";
1547 break;
1548 case 0x01:
1549 name = "Turkish";
1550 break;
1552 break;
1553 case 0x20:
1554 switch(secondary) {
1555 case 0x00:
1556 name = "Urdu (Islamic Republic of Pakistan)";
1557 break;
1558 case 0x01:
1559 name = "Urdu";
1560 break;
1562 break;
1563 case 0x21:
1564 switch(secondary) {
1565 case 0x00:
1566 name = "Indonesian (Indonesia)";
1567 break;
1568 case 0x01:
1569 name = "Indonesian";
1570 break;
1572 break;
1573 case 0x22:
1574 switch(secondary) {
1575 case 0x00:
1576 name = "Ukrainian (Ukraine)";
1577 break;
1578 case 0x01:
1579 name = "Ukrainian";
1580 break;
1582 break;
1583 case 0x23:
1584 switch(secondary) {
1585 case 0x00:
1586 name = "Belarusian (Belarus)";
1587 break;
1588 case 0x01:
1589 name = "Belarusian";
1590 break;
1592 break;
1593 case 0x24:
1594 switch(secondary) {
1595 case 0x00:
1596 name = "Slovenian (Slovenia)";
1597 break;
1598 case 0x01:
1599 name = "Slovenian";
1600 break;
1602 break;
1603 case 0x25:
1604 switch(secondary) {
1605 case 0x00:
1606 name = "Estonian (Estonia)";
1607 break;
1608 case 0x01:
1609 name = "Estonian";
1610 break;
1612 break;
1613 case 0x26:
1614 switch(secondary) {
1615 case 0x00:
1616 name = "Latvian (Latvia)";
1617 break;
1618 case 0x01:
1619 name = "Latvian";
1620 break;
1622 break;
1623 case 0x27:
1624 switch(secondary) {
1625 case 0x00:
1626 name = "Lithuanian (Lithuania)";
1627 break;
1628 case 0x01:
1629 name = "Lithuanian";
1630 break;
1632 break;
1633 case 0x28:
1634 switch(secondary) {
1635 case 0x01:
1636 name = "Tajik (Tajikistan)";
1637 break;
1639 break;
1640 case 0x29:
1641 switch(secondary) {
1642 case 0x00:
1643 name = "Farsi (Iran)";
1644 break;
1645 case 0x01:
1646 name = "Farsi";
1647 break;
1649 break;
1650 case 0x2a:
1651 switch(secondary) {
1652 case 0x00:
1653 name = "Vietnamese (Viet Nam)";
1654 break;
1655 case 0x01:
1656 name = "Vietnamese";
1657 break;
1659 break;
1660 case 0x2b:
1661 switch(secondary) {
1662 case 0x00:
1663 name = "Armenian (Armenia)";
1664 break;
1665 case 0x01:
1666 name = "Armenian";
1667 break;
1669 break;
1670 case 0x2c:
1671 switch(secondary) {
1672 case 0x00:
1673 name = "Azeri (Latin) (Azerbaijan)";
1674 break;
1675 case 0x01:
1676 name = "Azeri (Latin)";
1677 break;
1678 case 0x02:
1679 name = "Azeri (Cyrillic)";
1680 break;
1682 break;
1683 case 0x2d:
1684 switch(secondary) {
1685 case 0x00:
1686 name = "Basque (Spain)";
1687 break;
1688 case 0x01:
1689 name = "Basque";
1690 break;
1692 break;
1693 case 0x2e:
1694 switch(secondary) {
1695 case 0x01:
1696 name = "Upper Sorbian (Germany)";
1697 break;
1698 case 0x02:
1699 name = "Lower Sorbian (Germany)";
1700 break;
1702 break;
1703 case 0x2f:
1704 switch(secondary) {
1705 case 0x00:
1706 name = "FYRO Macedonian (Former Yugoslav Republic of Macedonia)";
1707 break;
1708 case 0x01:
1709 name = "FYRO Macedonian";
1710 break;
1712 break;
1713 case 0x32:
1714 switch(secondary) {
1715 case 0x00:
1716 name = "Tswana (South Africa)";
1717 break;
1718 case 0x01:
1719 name = "Tswana";
1720 break;
1722 break;
1723 case 0x34:
1724 switch(secondary) {
1725 case 0x00:
1726 name = "Xhosa (South Africa)";
1727 break;
1728 case 0x01:
1729 name = "Xhosa";
1730 break;
1732 break;
1733 case 0x35:
1734 switch(secondary) {
1735 case 0x00:
1736 name = "Zulu (South Africa)";
1737 break;
1738 case 0x01:
1739 name = "Zulu";
1740 break;
1742 break;
1743 case 0x36:
1744 switch(secondary) {
1745 case 0x00:
1746 name = "Afrikaans (South Africa)";
1747 break;
1748 case 0x01:
1749 name = "Afrikaans";
1750 break;
1752 break;
1753 case 0x37:
1754 switch(secondary) {
1755 case 0x00:
1756 name = "Georgian (Georgia)";
1757 break;
1758 case 0x01:
1759 name = "Georgian";
1760 break;
1762 break;
1763 case 0x38:
1764 switch(secondary) {
1765 case 0x00:
1766 name = "Faroese (Faroe Islands)";
1767 break;
1768 case 0x01:
1769 name = "Faroese";
1770 break;
1772 break;
1773 case 0x39:
1774 switch(secondary) {
1775 case 0x00:
1776 name = "Hindi (India)";
1777 break;
1778 case 0x01:
1779 name = "Hindi";
1780 break;
1782 break;
1783 case 0x3a:
1784 switch(secondary) {
1785 case 0x00:
1786 name = "Maltese (Malta)";
1787 break;
1788 case 0x01:
1789 name = "Maltese";
1790 break;
1792 break;
1793 case 0x3b:
1794 switch(secondary) {
1795 case 0x00:
1796 name = "Sami (Northern) (Norway)";
1797 break;
1798 case 0x01:
1799 name = "Sami, Northern (Norway)";
1800 break;
1801 case 0x02:
1802 name = "Sami, Northern (Sweden)";
1803 break;
1804 case 0x03:
1805 name = "Sami, Northern (Finland)";
1806 break;
1807 case 0x04:
1808 name = "Sami, Lule (Norway)";
1809 break;
1810 case 0x05:
1811 name = "Sami, Lule (Sweden)";
1812 break;
1813 case 0x06:
1814 name = "Sami, Southern (Norway)";
1815 break;
1816 case 0x07:
1817 name = "Sami, Southern (Sweden)";
1818 break;
1819 case 0x08:
1820 name = "Sami, Skolt (Finland)";
1821 break;
1822 case 0x09:
1823 name = "Sami, Inari (Finland)";
1824 break;
1826 break;
1827 case 0x3c:
1828 switch(secondary) {
1829 case 0x02:
1830 name = "Irish (Ireland)";
1831 break;
1833 break;
1834 case 0x3e:
1835 switch(secondary) {
1836 case 0x00:
1837 case 0x01:
1838 name = "Malay (Malaysia)";
1839 break;
1840 case 0x02:
1841 name = "Malay (Brunei Darussalam)";
1842 break;
1844 break;
1845 case 0x3f:
1846 switch(secondary) {
1847 case 0x00:
1848 name = "Kazakh (Kazakhstan)";
1849 break;
1850 case 0x01:
1851 name = "Kazakh";
1852 break;
1854 break;
1855 case 0x40:
1856 switch(secondary) {
1857 case 0x00:
1858 name = "Kyrgyz (Kyrgyzstan)";
1859 break;
1860 case 0x01:
1861 name = "Kyrgyz (Cyrillic)";
1862 break;
1864 break;
1865 case 0x41:
1866 switch(secondary) {
1867 case 0x00:
1868 name = "Swahili (Kenya)";
1869 break;
1870 case 0x01:
1871 name = "Swahili";
1872 break;
1874 break;
1875 case 0x42:
1876 switch(secondary) {
1877 case 0x01:
1878 name = "Turkmen (Turkmenistan)";
1879 break;
1881 break;
1882 case 0x43:
1883 switch(secondary) {
1884 case 0x00:
1885 name = "Uzbek (Latin) (Uzbekistan)";
1886 break;
1887 case 0x01:
1888 name = "Uzbek (Latin)";
1889 break;
1890 case 0x02:
1891 name = "Uzbek (Cyrillic)";
1892 break;
1894 break;
1895 case 0x44:
1896 switch(secondary) {
1897 case 0x00:
1898 name = "Tatar (Russia)";
1899 break;
1900 case 0x01:
1901 name = "Tatar";
1902 break;
1904 break;
1905 case 0x45:
1906 switch(secondary) {
1907 case 0x00:
1908 case 0x01:
1909 name = "Bengali (India)";
1910 break;
1912 break;
1913 case 0x46:
1914 switch(secondary) {
1915 case 0x00:
1916 name = "Punjabi (India)";
1917 break;
1918 case 0x01:
1919 name = "Punjabi";
1920 break;
1922 break;
1923 case 0x47:
1924 switch(secondary) {
1925 case 0x00:
1926 name = "Gujarati (India)";
1927 break;
1928 case 0x01:
1929 name = "Gujarati";
1930 break;
1932 break;
1933 case 0x49:
1934 switch(secondary) {
1935 case 0x00:
1936 name = "Tamil (India)";
1937 break;
1938 case 0x01:
1939 name = "Tamil";
1940 break;
1942 break;
1943 case 0x4a:
1944 switch(secondary) {
1945 case 0x00:
1946 name = "Telugu (India)";
1947 break;
1948 case 0x01:
1949 name = "Telugu";
1950 break;
1952 break;
1953 case 0x4b:
1954 switch(secondary) {
1955 case 0x00:
1956 name = "Kannada (India)";
1957 break;
1958 case 0x01:
1959 name = "Kannada";
1960 break;
1962 break;
1963 case 0x4c:
1964 switch(secondary) {
1965 case 0x00:
1966 case 0x01:
1967 name = "Malayalam (India)";
1968 break;
1970 break;
1971 case 0x4d:
1972 switch(secondary) {
1973 case 0x01:
1974 name = "Assamese (India)";
1975 break;
1977 break;
1978 case 0x4e:
1979 switch(secondary) {
1980 case 0x00:
1981 name = "Marathi (India)";
1982 break;
1983 case 0x01:
1984 name = "Marathi";
1985 break;
1987 break;
1988 case 0x4f:
1989 switch(secondary) {
1990 case 0x00:
1991 name = "Sanskrit (India)";
1992 break;
1993 case 0x01:
1994 name = "Sanskrit";
1995 break;
1997 break;
1998 case 0x50:
1999 switch(secondary) {
2000 case 0x00:
2001 name = "Mongolian (Mongolia)";
2002 break;
2003 case 0x01:
2004 name = "Mongolian (Cyrillic)";
2005 break;
2006 case 0x02:
2007 name = "Mongolian (PRC)";
2008 break;
2010 break;
2011 case 0x51:
2012 switch(secondary) {
2013 case 0x01:
2014 name = "Tibetan (PRC)";
2015 break;
2016 case 0x02:
2017 name = "Tibetan (Bhutan)";
2018 break;
2020 break;
2021 case 0x52:
2022 switch(secondary) {
2023 case 0x00:
2024 name = "Welsh (United Kingdom)";
2025 break;
2026 case 0x01:
2027 name = "Welsh";
2028 break;
2030 break;
2031 case 0x53:
2032 switch(secondary) {
2033 case 0x01:
2034 name = "Khmer (Cambodia)";
2035 break;
2037 break;
2038 case 0x54:
2039 switch(secondary) {
2040 case 0x01:
2041 name = "Lao (Lao PDR)";
2042 break;
2044 break;
2045 case 0x56:
2046 switch(secondary) {
2047 case 0x00:
2048 name = "Galician (Spain)";
2049 break;
2050 case 0x01:
2051 name = "Galician";
2052 break;
2054 break;
2055 case 0x57:
2056 switch(secondary) {
2057 case 0x00:
2058 name = "Konkani (India)";
2059 break;
2060 case 0x01:
2061 name = "Konkani";
2062 break;
2064 break;
2065 case 0x5a:
2066 switch(secondary) {
2067 case 0x00:
2068 name = "Syriac (Syria)";
2069 break;
2070 case 0x01:
2071 name = "Syriac";
2072 break;
2074 break;
2075 case 0x5b:
2076 switch(secondary) {
2077 case 0x01:
2078 name = "Sinhala (Sri Lanka)";
2079 break;
2081 break;
2082 case 0x5d:
2083 switch(secondary) {
2084 case 0x01:
2085 name = "Inuktitut (Syllabics, Canada)";
2086 break;
2087 case 0x02:
2088 name = "Inuktitut (Latin, Canada)";
2089 break;
2091 break;
2092 case 0x5e:
2093 switch(secondary) {
2094 case 0x01:
2095 name = "Amharic (Ethiopia)";
2096 break;
2098 break;
2099 case 0x5f:
2100 switch(secondary) {
2101 case 0x02:
2102 name = "Tamazight (Algeria, Latin)";
2103 break;
2105 break;
2106 case 0x61:
2107 switch(secondary) {
2108 case 0x01:
2109 name = "Nepali (Nepal)";
2110 break;
2112 break;
2113 case 0x62:
2114 switch(secondary) {
2115 case 0x01:
2116 name = "Frisian (Netherlands)";
2117 break;
2119 break;
2120 case 0x63:
2121 switch(secondary) {
2122 case 0x01:
2123 name = "Pashto (Afghanistan)";
2124 break;
2126 break;
2127 case 0x64:
2128 switch(secondary) {
2129 case 0x01:
2130 name = "Filipino (Philippines)";
2131 break;
2133 break;
2134 case 0x65:
2135 switch(secondary) {
2136 case 0x00:
2137 name = "Divehi (Maldives)";
2138 break;
2139 case 0x01:
2140 name = "Divehi";
2141 break;
2143 break;
2144 case 0x68:
2145 switch(secondary) {
2146 case 0x01:
2147 name = "Hausa (Nigeria, Latin)";
2148 break;
2150 break;
2151 case 0x6a:
2152 switch(secondary) {
2153 case 0x01:
2154 name = "Yoruba (Nigeria)";
2155 break;
2157 break;
2158 case 0x6b:
2159 switch(secondary) {
2160 case 0x00:
2161 case 0x01:
2162 name = "Quechua (Bolivia)";
2163 break;
2164 case 0x02:
2165 name = "Quechua (Ecuador)";
2166 break;
2167 case 0x03:
2168 name = "Quechua (Peru)";
2169 break;
2171 break;
2172 case 0x6c:
2173 switch(secondary) {
2174 case 0x00:
2175 name = "Northern Sotho (South Africa)";
2176 break;
2177 case 0x01:
2178 name = "Northern Sotho";
2179 break;
2181 break;
2182 case 0x6d:
2183 switch(secondary) {
2184 case 0x01:
2185 name = "Bashkir (Russia)";
2186 break;
2188 break;
2189 case 0x6e:
2190 switch(secondary) {
2191 case 0x01:
2192 name = "Luxembourgish (Luxembourg)";
2193 break;
2195 break;
2196 case 0x6f:
2197 switch(secondary) {
2198 case 0x01:
2199 name = "Greenlandic (Greenland)";
2200 break;
2202 break;
2203 case 0x78:
2204 switch(secondary) {
2205 case 0x01:
2206 name = "Yi (PRC)";
2207 break;
2209 break;
2210 case 0x7a:
2211 switch(secondary) {
2212 case 0x01:
2213 name = "Mapudungun (Chile)";
2214 break;
2216 break;
2217 case 0x7c:
2218 switch(secondary) {
2219 case 0x01:
2220 name = "Mohawk (Mohawk)";
2221 break;
2223 break;
2224 case 0x7e:
2225 switch(secondary) {
2226 case 0x01:
2227 name = "Breton (France)";
2228 break;
2230 break;
2231 case 0x7f:
2232 switch(secondary) {
2233 case 0x00:
2234 name = "Invariant Language (Invariant Country)";
2235 break;
2237 break;
2238 case 0x80:
2239 switch(secondary) {
2240 case 0x01:
2241 name = "Uighur (PRC)";
2242 break;
2244 break;
2245 case 0x81:
2246 switch(secondary) {
2247 case 0x00:
2248 name = "Maori (New Zealand)";
2249 break;
2250 case 0x01:
2251 name = "Maori";
2252 break;
2254 break;
2255 case 0x83:
2256 switch(secondary) {
2257 case 0x01:
2258 name = "Corsican (France)";
2259 break;
2261 break;
2262 case 0x84:
2263 switch(secondary) {
2264 case 0x01:
2265 name = "Alsatian (France)";
2266 break;
2268 break;
2269 case 0x85:
2270 switch(secondary) {
2271 case 0x01:
2272 name = "Yakut (Russia)";
2273 break;
2275 break;
2276 case 0x86:
2277 switch(secondary) {
2278 case 0x01:
2279 name = "K'iche (Guatemala)";
2280 break;
2282 break;
2283 case 0x87:
2284 switch(secondary) {
2285 case 0x01:
2286 name = "Kinyarwanda (Rwanda)";
2287 break;
2289 break;
2290 case 0x88:
2291 switch(secondary) {
2292 case 0x01:
2293 name = "Wolof (Senegal)";
2294 break;
2296 break;
2297 case 0x8c:
2298 switch(secondary) {
2299 case 0x01:
2300 name = "Dari (Afghanistan)";
2301 break;
2303 break;
2305 default:
2306 name = "Language Neutral";
2310 if (!name)
2311 name = "Language Neutral";
2313 return copy_lang (lang_out, lang_len, name);