2009-05-15 Geoff Norton <gnorton@novell.com>
[mono-project.git] / mono / io-layer / versioninfo.c
blobd77c1cb2fdaef67a5de6f369a2cfc0cbf30ac182
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/mman.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18 #include <fcntl.h>
19 #include <errno.h>
21 #include <mono/io-layer/wapi.h>
22 #include <mono/io-layer/wapi-private.h>
23 #include <mono/io-layer/versioninfo.h>
24 #include <mono/io-layer/io-portability.h>
25 #include <mono/io-layer/error.h>
26 #include <mono/utils/strenc.h>
28 #undef DEBUG
30 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
32 static WapiImageSectionHeader *get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
34 WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
35 guint32 i;
37 for (i = 0; i < GUINT16_FROM_LE (nt_headers->FileHeader.NumberOfSections); i++, section++) {
38 guint32 size = GUINT32_FROM_LE (section->Misc.VirtualSize);
39 if (size == 0) {
40 size = GUINT32_FROM_LE (section->SizeOfRawData);
43 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
44 (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
45 return(section);
49 return(NULL);
52 /* This works for both 32bit and 64bit files, as the differences are
53 * all after the section header block
55 static gpointer get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders,
56 gpointer file_map)
58 WapiImageSectionHeader *section_header;
59 guint32 delta;
61 section_header = get_enclosing_section_header (rva, ntheaders);
62 if (section_header == NULL) {
63 return(NULL);
66 delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
67 GUINT32_FROM_LE (section_header->PointerToRawData));
69 return((guint8 *)file_map + rva - delta);
72 static gpointer scan_resource_dir (WapiImageResourceDirectory *root,
73 WapiImageNTHeaders32 *nt_headers,
74 gpointer file_map,
75 WapiImageResourceDirectoryEntry *entry,
76 int level, guint32 res_id, guint32 lang_id,
77 guint32 *size)
79 WapiImageResourceDirectoryEntry swapped_entry;
80 gboolean is_string, is_dir;
81 guint32 name_offset, dir_offset, data_offset;
83 swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
84 swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
86 is_string = swapped_entry.NameIsString;
87 is_dir = swapped_entry.DataIsDirectory;
88 name_offset = swapped_entry.NameOffset;
89 dir_offset = swapped_entry.OffsetToDirectory;
90 data_offset = swapped_entry.OffsetToData;
92 if (level == 0) {
93 /* Normally holds a directory entry for each type of
94 * resource
96 if ((is_string == FALSE &&
97 name_offset != res_id) ||
98 (is_string == TRUE)) {
99 return(NULL);
101 } else if (level == 1) {
102 /* Normally holds a directory entry for each resource
103 * item
105 } else if (level == 2) {
106 /* Normally holds a directory entry for each language
108 if ((is_string == FALSE &&
109 name_offset != lang_id &&
110 lang_id != 0) ||
111 (is_string == TRUE)) {
112 return(NULL);
114 } else {
115 g_assert_not_reached ();
118 if (is_dir == TRUE) {
119 WapiImageResourceDirectory *res_dir = (WapiImageResourceDirectory *)((guint8 *)root + dir_offset);
120 WapiImageResourceDirectoryEntry *sub_entries = (WapiImageResourceDirectoryEntry *)(res_dir + 1);
121 guint32 entries, i;
123 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
125 for (i = 0; i < entries; i++) {
126 WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
127 gpointer ret;
129 ret = scan_resource_dir (root, nt_headers, file_map,
130 sub_entry, level + 1, res_id,
131 lang_id, size);
132 if (ret != NULL) {
133 return(ret);
137 return(NULL);
138 } else {
139 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
140 *size = GUINT32_FROM_LE (data_entry->Size);
142 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
146 static gpointer find_pe_file_resources32 (gpointer file_map, guint32 map_size,
147 guint32 res_id, guint32 lang_id,
148 guint32 *size)
150 WapiImageDosHeader *dos_header;
151 WapiImageNTHeaders32 *nt_headers;
152 WapiImageResourceDirectory *resource_dir;
153 WapiImageResourceDirectoryEntry *resource_dir_entry;
154 guint32 resource_rva, entries, i;
155 gpointer ret = NULL;
157 dos_header = (WapiImageDosHeader *)file_map;
158 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
159 #ifdef DEBUG
160 g_message ("%s: Bad dos signature 0x%x", __func__,
161 dos_header->e_magic);
162 #endif
164 SetLastError (ERROR_INVALID_DATA);
165 return(NULL);
168 if (map_size < sizeof(WapiImageNTHeaders32) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
169 #ifdef DEBUG
170 g_message ("%s: File is too small: %d", __func__, map_size);
171 #endif
173 SetLastError (ERROR_BAD_LENGTH);
174 return(NULL);
177 nt_headers = (WapiImageNTHeaders32 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
178 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
179 #ifdef DEBUG
180 g_message ("%s: Bad NT signature 0x%x", __func__,
181 nt_headers->Signature);
182 #endif
184 SetLastError (ERROR_INVALID_DATA);
185 return(NULL);
188 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
189 /* Do 64-bit stuff */
190 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
191 } else {
192 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
195 if (resource_rva == 0) {
196 #ifdef DEBUG
197 g_message ("%s: No resources in file!", __func__);
198 #endif
199 SetLastError (ERROR_INVALID_DATA);
200 return(NULL);
203 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
204 if (resource_dir == NULL) {
205 #ifdef DEBUG
206 g_message ("%s: Can't find resource directory", __func__);
207 #endif
208 SetLastError (ERROR_INVALID_DATA);
209 return(NULL);
212 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
213 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
215 for (i = 0; i < entries; i++) {
216 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
217 ret = scan_resource_dir (resource_dir,
218 (WapiImageNTHeaders32 *)nt_headers,
219 file_map, direntry, 0, res_id,
220 lang_id, size);
221 if (ret != NULL) {
222 return(ret);
226 return(NULL);
229 static gpointer find_pe_file_resources64 (gpointer file_map, guint32 map_size,
230 guint32 res_id, guint32 lang_id,
231 guint32 *size)
233 WapiImageDosHeader *dos_header;
234 WapiImageNTHeaders64 *nt_headers;
235 WapiImageResourceDirectory *resource_dir;
236 WapiImageResourceDirectoryEntry *resource_dir_entry;
237 guint32 resource_rva, entries, i;
238 gpointer ret = NULL;
240 dos_header = (WapiImageDosHeader *)file_map;
241 if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
242 #ifdef DEBUG
243 g_message ("%s: Bad dos signature 0x%x", __func__,
244 dos_header->e_magic);
245 #endif
247 SetLastError (ERROR_INVALID_DATA);
248 return(NULL);
251 if (map_size < sizeof(WapiImageNTHeaders64) + GUINT32_FROM_LE (dos_header->e_lfanew)) {
252 #ifdef DEBUG
253 g_message ("%s: File is too small: %d", __func__, map_size);
254 #endif
256 SetLastError (ERROR_BAD_LENGTH);
257 return(NULL);
260 nt_headers = (WapiImageNTHeaders64 *)((guint8 *)file_map + GUINT32_FROM_LE (dos_header->e_lfanew));
261 if (nt_headers->Signature != IMAGE_NT_SIGNATURE) {
262 #ifdef DEBUG
263 g_message ("%s: Bad NT signature 0x%x", __func__,
264 nt_headers->Signature);
265 #endif
267 SetLastError (ERROR_INVALID_DATA);
268 return(NULL);
271 if (nt_headers->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
272 /* Do 64-bit stuff */
273 resource_rva = GUINT32_FROM_LE (((WapiImageNTHeaders64 *)nt_headers)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
274 } else {
275 resource_rva = GUINT32_FROM_LE (nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress);
278 if (resource_rva == 0) {
279 #ifdef DEBUG
280 g_message ("%s: No resources in file!", __func__);
281 #endif
282 SetLastError (ERROR_INVALID_DATA);
283 return(NULL);
286 resource_dir = (WapiImageResourceDirectory *)get_ptr_from_rva (resource_rva, (WapiImageNTHeaders32 *)nt_headers, file_map);
287 if (resource_dir == NULL) {
288 #ifdef DEBUG
289 g_message ("%s: Can't find resource directory", __func__);
290 #endif
291 SetLastError (ERROR_INVALID_DATA);
292 return(NULL);
295 entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
296 resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
298 for (i = 0; i < entries; i++) {
299 WapiImageResourceDirectoryEntry *direntry = &resource_dir_entry[i];
300 ret = scan_resource_dir (resource_dir,
301 (WapiImageNTHeaders32 *)nt_headers,
302 file_map, direntry, 0, res_id,
303 lang_id, size);
304 if (ret != NULL) {
305 return(ret);
309 return(NULL);
312 static gpointer find_pe_file_resources (gpointer file_map, guint32 map_size,
313 guint32 res_id, guint32 lang_id,
314 guint32 *size)
316 /* Figure this out when we support 64bit PE files */
317 if (1) {
318 return find_pe_file_resources32 (file_map, map_size, res_id,
319 lang_id, size);
320 } else {
321 return find_pe_file_resources64 (file_map, map_size, res_id,
322 lang_id, size);
326 static gpointer map_pe_file (gunichar2 *filename, guint32 *map_size)
328 gchar *filename_ext;
329 int fd;
330 struct stat statbuf;
331 gpointer file_map;
333 /* According to the MSDN docs, a search path is applied to
334 * filename. FIXME: implement this, for now just pass it
335 * straight to fopen
338 filename_ext = mono_unicode_to_external (filename);
339 if (filename_ext == NULL) {
340 #ifdef DEBUG
341 g_message ("%s: unicode conversion returned NULL", __func__);
342 #endif
344 SetLastError (ERROR_INVALID_NAME);
345 return(NULL);
348 fd = _wapi_open (filename_ext, O_RDONLY, 0);
349 if (fd == -1) {
350 #ifdef DEBUG
351 g_message ("%s: Error opening file %s: %s", __func__,
352 filename_ext, strerror (errno));
353 #endif
355 SetLastError (_wapi_get_win32_file_error (errno));
356 g_free (filename_ext);
358 return(NULL);
361 if (fstat (fd, &statbuf) == -1) {
362 #ifdef DEBUG
363 g_message ("%s: Error stat()ing file %s: %s", __func__,
364 filename_ext, strerror (errno));
365 #endif
367 SetLastError (_wapi_get_win32_file_error (errno));
368 g_free (filename_ext);
369 close (fd);
370 return(NULL);
372 *map_size = statbuf.st_size;
374 /* Check basic file size */
375 if (statbuf.st_size < sizeof(WapiImageDosHeader)) {
376 #ifdef DEBUG
377 g_message ("%s: File %s is too small: %lld", __func__,
378 filename_ext, statbuf.st_size);
379 #endif
381 SetLastError (ERROR_BAD_LENGTH);
382 g_free (filename_ext);
383 close (fd);
384 return(NULL);
387 file_map = mmap (NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
388 if (file_map == MAP_FAILED) {
389 #ifdef DEBUG
390 g_message ("%s: Error mmap()int file %s: %s", __func__,
391 filename_ext, strerror (errno));
392 #endif
394 SetLastError (_wapi_get_win32_file_error (errno));
395 g_free (filename_ext);
396 close (fd);
397 return(NULL);
400 /* Don't need the fd any more */
401 close (fd);
402 g_free (filename_ext);
404 return(file_map);
407 static void unmap_pe_file (gpointer file_map, guint32 map_size)
409 munmap (file_map, map_size);
412 static guint32 unicode_chars (const gunichar2 *str)
414 guint32 len = 0;
416 do {
417 if (str[len] == '\0') {
418 return(len);
420 len++;
421 } while(1);
424 static gboolean unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
426 while (*str1 && *str2) {
427 if (*str1 != *str2) {
428 return(FALSE);
430 ++str1;
431 ++str2;
434 return(*str1 == *str2);
437 /* compare a little-endian null-terminated utf16 string and a normal string.
438 * Can be used only for ascii or latin1 chars.
440 static gboolean unicode_string_equals (const gunichar2 *str1, const gchar *str2)
442 while (*str1 && *str2) {
443 if (GUINT16_TO_LE (*str1) != *str2) {
444 return(FALSE);
446 ++str1;
447 ++str2;
450 return(*str1 == *str2);
453 typedef struct
455 guint16 data_len;
456 guint16 value_len;
457 guint16 type;
458 gunichar2 *key;
459 } version_data;
461 /* Returns a pointer to the value data, because there's no way to know
462 * how big that data is (value_len is set to zero for most blocks :-( )
464 static gconstpointer get_versioninfo_block (gconstpointer data,
465 version_data *block)
467 block->data_len = GUINT16_FROM_LE (*((guint16 *)data));
468 data = (char *)data + sizeof(guint16);
469 block->value_len = GUINT16_FROM_LE (*((guint16 *)data));
470 data = (char *)data + sizeof(guint16);
472 /* No idea what the type is supposed to indicate */
473 block->type = GUINT16_FROM_LE (*((guint16 *)data));
474 data = (char *)data + sizeof(guint16);
475 block->key = ((gunichar2 *)data);
477 /* Skip over the key (including the terminator) */
478 data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
480 /* align on a 32-bit boundary */
481 ALIGN32 (data);
483 return(data);
486 static gconstpointer get_fixedfileinfo_block (gconstpointer data,
487 version_data *block)
489 gconstpointer data_ptr;
490 gint32 data_len; /* signed to guard against underflow */
491 WapiFixedFileInfo *ffi;
493 data_ptr = get_versioninfo_block (data, block);
494 data_len = block->data_len;
496 if (block->value_len != sizeof(WapiFixedFileInfo)) {
497 #ifdef DEBUG
498 g_message ("%s: FIXEDFILEINFO size mismatch", __func__);
499 #endif
500 return(NULL);
503 if (!unicode_string_equals (block->key, "VS_VERSION_INFO")) {
504 #ifdef DEBUG
505 g_message ("%s: VS_VERSION_INFO mismatch", __func__);
506 #endif
507 return(NULL);
510 ffi = ((WapiFixedFileInfo *)data_ptr);
511 if ((ffi->dwSignature != VS_FFI_SIGNATURE) ||
512 (ffi->dwStrucVersion != VS_FFI_STRUCVERSION)) {
513 #ifdef DEBUG
514 g_message ("%s: FIXEDFILEINFO bad signature", __func__);
515 #endif
516 return(NULL);
519 return(data_ptr);
522 static gconstpointer get_varfileinfo_block (gconstpointer data_ptr,
523 version_data *block)
525 /* data is pointing at a Var block
527 data_ptr = get_versioninfo_block (data_ptr, block);
529 return(data_ptr);
532 static gconstpointer get_string_block (gconstpointer data_ptr,
533 const gunichar2 *string_key,
534 gpointer *string_value,
535 guint32 *string_value_len,
536 version_data *block)
538 guint16 data_len = block->data_len;
539 guint16 string_len = 28; /* Length of the StringTable block */
540 char *orig_data_ptr = (char *)data_ptr - 28;
542 /* data_ptr is pointing at an array of one or more String blocks
543 * with total length (not including alignment padding) of
544 * data_len
546 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
547 /* align on a 32-bit boundary */
548 ALIGN32 (data_ptr);
550 data_ptr = get_versioninfo_block (data_ptr, block);
551 if (block->data_len == 0) {
552 /* We must have hit padding, so give up
553 * processing now
555 #ifdef DEBUG
556 g_message ("%s: Hit 0-length block, giving up",
557 __func__);
558 #endif
559 return(NULL);
562 string_len = string_len + block->data_len;
564 if (string_key != NULL &&
565 string_value != NULL &&
566 string_value_len != NULL &&
567 unicode_compare (string_key, block->key) == TRUE) {
568 *string_value = (gpointer)data_ptr;
569 *string_value_len = block->value_len;
572 /* Skip over the value */
573 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
576 return(data_ptr);
579 /* Returns a pointer to the byte following the Stringtable block, or
580 * NULL if the data read hits padding. We can't recover from this
581 * because the data length does not include padding bytes, so it's not
582 * possible to just return the start position + length
584 * If lang == NULL it means we're just stepping through this block
586 static gconstpointer get_stringtable_block (gconstpointer data_ptr,
587 gchar *lang,
588 const gunichar2 *string_key,
589 gpointer *string_value,
590 guint32 *string_value_len,
591 version_data *block)
593 guint16 data_len = block->data_len;
594 guint16 string_len = 36; /* length of the StringFileInfo block */
595 gchar *found_lang;
596 gchar *lowercase_lang;
598 /* data_ptr is pointing at an array of StringTable blocks,
599 * with total length (not including alignment padding) of
600 * data_len
603 while(string_len < data_len) {
604 /* align on a 32-bit boundary */
605 ALIGN32 (data_ptr);
607 data_ptr = get_versioninfo_block (data_ptr, block);
608 if (block->data_len == 0) {
609 /* We must have hit padding, so give up
610 * processing now
612 #ifdef DEBUG
613 g_message ("%s: Hit 0-length block, giving up",
614 __func__);
615 #endif
616 return(NULL);
619 string_len = string_len + block->data_len;
621 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
622 if (found_lang == NULL) {
623 #ifdef DEBUG
624 g_message ("%s: Didn't find a valid language key, giving up", __func__);
625 #endif
626 return(NULL);
629 lowercase_lang = g_utf8_strdown (found_lang, -1);
630 g_free (found_lang);
631 found_lang = lowercase_lang;
632 lowercase_lang = NULL;
634 if (lang != NULL && !strcmp (found_lang, lang)) {
635 /* Got the one we're interested in */
636 data_ptr = get_string_block (data_ptr, string_key,
637 string_value,
638 string_value_len, block);
639 } else {
640 data_ptr = get_string_block (data_ptr, NULL, NULL,
641 NULL, block);
644 g_free (found_lang);
646 if (data_ptr == NULL) {
647 /* Child block hit padding */
648 #ifdef DEBUG
649 g_message ("%s: Child block hit 0-length block, giving up", __func__);
650 #endif
651 return(NULL);
655 return(data_ptr);
658 #if G_BYTE_ORDER == G_BIG_ENDIAN
659 static gconstpointer big_up_string_block (gconstpointer data_ptr,
660 version_data *block)
662 guint16 data_len = block->data_len;
663 guint16 string_len = 28; /* Length of the StringTable block */
664 gchar *big_value;
665 char *orig_data_ptr = (char *)data_ptr - 28;
667 /* data_ptr is pointing at an array of one or more String
668 * blocks with total length (not including alignment padding)
669 * of data_len
671 while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
672 /* align on a 32-bit boundary */
673 ALIGN32 (data_ptr);
675 data_ptr = get_versioninfo_block (data_ptr, block);
676 if (block->data_len == 0) {
677 /* We must have hit padding, so give up
678 * processing now
680 #ifdef DEBUG
681 g_message ("%s: Hit 0-length block, giving up",
682 __func__);
683 #endif
684 return(NULL);
687 string_len = string_len + block->data_len;
689 big_value = g_convert ((gchar *)block->key,
690 unicode_chars (block->key) * 2,
691 "UTF-16BE", "UTF-16LE", NULL, NULL,
692 NULL);
693 if (big_value == NULL) {
694 #ifdef DEBUG
695 g_message ("%s: Didn't find a valid string, giving up",
696 __func__);
697 #endif
698 return(NULL);
701 /* The swapped string should be exactly the same
702 * length as the original little-endian one, but only
703 * copy the number of original chars just to be on the
704 * safe side
706 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
707 g_free (big_value);
709 big_value = g_convert ((gchar *)data_ptr,
710 unicode_chars (data_ptr) * 2,
711 "UTF-16BE", "UTF-16LE", NULL, NULL,
712 NULL);
713 if (big_value == NULL) {
714 #ifdef DEBUG
715 g_message ("%s: Didn't find a valid data string, giving up", __func__);
716 #endif
717 return(NULL);
719 memcpy ((gpointer)data_ptr, big_value,
720 unicode_chars (data_ptr) * 2);
721 g_free (big_value);
723 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
726 return(data_ptr);
729 /* Returns a pointer to the byte following the Stringtable block, or
730 * NULL if the data read hits padding. We can't recover from this
731 * because the data length does not include padding bytes, so it's not
732 * possible to just return the start position + length
734 static gconstpointer big_up_stringtable_block (gconstpointer data_ptr,
735 version_data *block)
737 guint16 data_len = block->data_len;
738 guint16 string_len = 36; /* length of the StringFileInfo block */
739 gchar *big_value;
741 /* data_ptr is pointing at an array of StringTable blocks,
742 * with total length (not including alignment padding) of
743 * data_len
746 while(string_len < data_len) {
747 /* align on a 32-bit boundary */
748 ALIGN32 (data_ptr);
750 data_ptr = get_versioninfo_block (data_ptr, block);
751 if (block->data_len == 0) {
752 /* We must have hit padding, so give up
753 * processing now
755 #ifdef DEBUG
756 g_message ("%s: Hit 0-length block, giving up",
757 __func__);
758 #endif
759 return(NULL);
762 string_len = string_len + block->data_len;
764 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
765 "UTF-16LE", NULL, NULL, NULL);
766 if (big_value == NULL) {
767 #ifdef DEBUG
768 g_message ("%s: Didn't find a valid string, giving up",
769 __func__);
770 #endif
771 return(NULL);
774 memcpy (block->key, big_value, 16);
775 g_free (big_value);
777 data_ptr = big_up_string_block (data_ptr, block);
779 if (data_ptr == NULL) {
780 /* Child block hit padding */
781 #ifdef DEBUG
782 g_message ("%s: Child block hit 0-length block, giving up", __func__);
783 #endif
784 return(NULL);
788 return(data_ptr);
791 /* Follows the data structures and turns all UTF-16 strings from the
792 * LE found in the resource section into UTF-16BE
794 static void big_up (gconstpointer datablock, guint32 size)
796 gconstpointer data_ptr;
797 gint32 data_len; /* signed to guard against underflow */
798 version_data block;
800 data_ptr = get_fixedfileinfo_block (datablock, &block);
801 if (data_ptr != NULL) {
802 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
804 /* Byteswap all the fields */
805 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
806 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
807 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
808 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
809 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
810 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
811 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
812 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
813 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
814 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
815 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
817 /* The FFI and header occupies the first 92 bytes
819 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
820 data_len = block.data_len - 92;
822 /* There now follow zero or one StringFileInfo blocks
823 * and zero or one VarFileInfo blocks
825 while (data_len > 0) {
826 /* align on a 32-bit boundary */
827 ALIGN32 (data_ptr);
829 data_ptr = get_versioninfo_block (data_ptr, &block);
830 if (block.data_len == 0) {
831 /* We must have hit padding, so give
832 * up processing now
834 #ifdef DEBUG
835 g_message ("%s: Hit 0-length block, giving up",
836 __func__);
837 #endif
838 return;
841 data_len = data_len - block.data_len;
843 if (unicode_string_equals (block.key, "VarFileInfo")) {
844 data_ptr = get_varfileinfo_block (data_ptr,
845 &block);
846 data_ptr = ((guchar *)data_ptr) + block.value_len;
847 } else if (unicode_string_equals (block.key,
848 "StringFileInfo")) {
849 data_ptr = big_up_stringtable_block (data_ptr,
850 &block);
851 } else {
852 /* Bogus data */
853 #ifdef DEBUG
854 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
855 #endif
856 return;
859 if (data_ptr == NULL) {
860 /* Child block hit padding */
861 #ifdef DEBUG
862 g_message ("%s: Child block hit 0-length block, giving up", __func__);
863 #endif
864 return;
869 #endif
871 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
872 gpointer *buffer, guint32 *len)
874 gchar *subblock_utf8, *lang_utf8 = NULL;
875 gboolean ret = FALSE;
876 version_data block;
877 gconstpointer data_ptr;
878 gint32 data_len; /* signed to guard against underflow */
879 gboolean want_var = FALSE;
880 gboolean want_string = FALSE;
881 gunichar2 lang[8];
882 const gunichar2 *string_key = NULL;
883 gpointer string_value = NULL;
884 guint32 string_value_len = 0;
885 gchar *lowercase_lang;
887 subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
888 if (subblock_utf8 == NULL) {
889 return(FALSE);
892 if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
893 want_var = TRUE;
894 } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
895 want_string = TRUE;
896 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
897 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
898 lowercase_lang = g_utf8_strdown (lang_utf8, -1);
899 g_free (lang_utf8);
900 lang_utf8 = lowercase_lang;
901 lowercase_lang = NULL;
902 string_key = subblock + 25;
905 if (!strcmp (subblock_utf8, "\\")) {
906 data_ptr = get_fixedfileinfo_block (datablock, &block);
907 if (data_ptr != NULL) {
908 *buffer = (gpointer)data_ptr;
909 *len = block.value_len;
911 ret = TRUE;
913 } else if (want_var || want_string) {
914 data_ptr = get_fixedfileinfo_block (datablock, &block);
915 if (data_ptr != NULL) {
916 /* The FFI and header occupies the first 92
917 * bytes
919 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
920 data_len = block.data_len - 92;
922 /* There now follow zero or one StringFileInfo
923 * blocks and zero or one VarFileInfo blocks
925 while (data_len > 0) {
926 /* align on a 32-bit boundary */
927 ALIGN32 (data_ptr);
929 data_ptr = get_versioninfo_block (data_ptr,
930 &block);
931 if (block.data_len == 0) {
932 /* We must have hit padding,
933 * so give up processing now
935 #ifdef DEBUG
936 g_message ("%s: Hit 0-length block, giving up", __func__);
937 #endif
938 goto done;
941 data_len = data_len - block.data_len;
943 if (unicode_string_equals (block.key, "VarFileInfo")) {
944 data_ptr = get_varfileinfo_block (data_ptr, &block);
945 if (want_var) {
946 *buffer = (gpointer)data_ptr;
947 *len = block.value_len;
948 ret = TRUE;
949 goto done;
950 } else {
951 /* Skip over the Var block */
952 data_ptr = ((guchar *)data_ptr) + block.value_len;
954 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
955 data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
956 if (want_string &&
957 string_value != NULL &&
958 string_value_len != 0) {
959 *buffer = string_value;
960 *len = unicode_chars (string_value) + 1; /* Include trailing null */
961 ret = TRUE;
962 goto done;
964 } else {
965 /* Bogus data */
966 #ifdef DEBUG
967 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
968 #endif
969 goto done;
972 if (data_ptr == NULL) {
973 /* Child block hit padding */
974 #ifdef DEBUG
975 g_message ("%s: Child block hit 0-length block, giving up", __func__);
976 #endif
977 goto done;
983 done:
984 if (lang_utf8) {
985 g_free (lang_utf8);
988 g_free (subblock_utf8);
989 return(ret);
992 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
994 gpointer file_map;
995 gpointer versioninfo;
996 guint32 map_size;
997 guint32 size;
999 /* This value is unused, but set to zero */
1000 *handle = 0;
1002 file_map = map_pe_file (filename, &map_size);
1003 if (file_map == NULL) {
1004 return(0);
1007 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1008 0, &size);
1009 if (versioninfo == NULL) {
1010 /* Didn't find the resource, so set the return value
1011 * to 0
1013 size = 0;
1016 unmap_pe_file (file_map, map_size);
1018 return(size);
1021 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
1022 guint32 len, gpointer data)
1024 gpointer file_map;
1025 gpointer versioninfo;
1026 guint32 map_size;
1027 guint32 size;
1028 gboolean ret = FALSE;
1030 file_map = map_pe_file (filename, &map_size);
1031 if (file_map == NULL) {
1032 return(FALSE);
1035 versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1036 0, &size);
1037 if (versioninfo != NULL) {
1038 /* This could probably process the data so that
1039 * VerQueryValue() doesn't have to follow the data
1040 * blocks every time. But hey, these functions aren't
1041 * likely to appear in many profiles.
1043 memcpy (data, versioninfo, len < size?len:size);
1044 ret = TRUE;
1046 #if G_BYTE_ORDER == G_BIG_ENDIAN
1047 big_up (data, size);
1048 #endif
1051 unmap_pe_file (file_map, map_size);
1053 return(ret);
1056 static guint32 copy_lang (gunichar2 *lang_out, guint32 lang_len,
1057 const gchar *text)
1059 gunichar2 *unitext;
1060 int chars = strlen (text);
1061 int ret;
1063 unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1064 g_assert (unitext != NULL);
1066 if (chars < (lang_len - 1)) {
1067 memcpy (lang_out, (gpointer)unitext, chars * 2);
1068 lang_out[chars] = '\0';
1069 ret = chars;
1070 } else {
1071 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1072 lang_out[lang_len] = '\0';
1073 ret = lang_len;
1076 g_free (unitext);
1078 return(ret);
1081 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1083 int primary, secondary;
1085 primary = lang & 0x3FF;
1086 secondary = (lang >> 10) & 0x3F;
1088 switch(primary) {
1089 case 0x00:
1090 switch(secondary) {
1091 case 0x01:
1092 return(copy_lang (lang_out, lang_len, "Process Default Language"));
1093 break;
1095 break;
1096 case 0x01:
1097 switch(secondary) {
1098 case 0x00:
1099 case 0x01:
1100 return(copy_lang (lang_out, lang_len, "Arabic (Saudi Arabia)"));
1101 break;
1102 case 0x02:
1103 return(copy_lang (lang_out, lang_len, "Arabic (Iraq)"));
1104 break;
1105 case 0x03:
1106 return(copy_lang (lang_out, lang_len, "Arabic (Egypt)"));
1107 break;
1108 case 0x04:
1109 return(copy_lang (lang_out, lang_len, "Arabic (Libya)"));
1110 break;
1111 case 0x05:
1112 return(copy_lang (lang_out, lang_len, "Arabic (Algeria)"));
1113 break;
1114 case 0x06:
1115 return(copy_lang (lang_out, lang_len, "Arabic (Morocco)"));
1116 break;
1117 case 0x07:
1118 return(copy_lang (lang_out, lang_len, "Arabic (Tunisia)"));
1119 break;
1120 case 0x08:
1121 return(copy_lang (lang_out, lang_len, "Arabic (Oman)"));
1122 break;
1123 case 0x09:
1124 return(copy_lang (lang_out, lang_len, "Arabic (Yemen)"));
1125 break;
1126 case 0x0a:
1127 return(copy_lang (lang_out, lang_len, "Arabic (Syria)"));
1128 break;
1129 case 0x0b:
1130 return(copy_lang (lang_out, lang_len, "Arabic (Jordan)"));
1131 break;
1132 case 0x0c:
1133 return(copy_lang (lang_out, lang_len, "Arabic (Lebanon)"));
1134 break;
1135 case 0x0d:
1136 return(copy_lang (lang_out, lang_len, "Arabic (Kuwait)"));
1137 break;
1138 case 0x0e:
1139 return(copy_lang (lang_out, lang_len, "Arabic (U.A.E.)"));
1140 break;
1141 case 0x0f:
1142 return(copy_lang (lang_out, lang_len, "Arabic (Bahrain)"));
1143 break;
1144 case 0x10:
1145 return(copy_lang (lang_out, lang_len, "Arabic (Qatar)"));
1146 break;
1148 break;
1149 case 0x02:
1150 switch(secondary) {
1151 case 0x00:
1152 return(copy_lang (lang_out, lang_len, "Bulgarian (Bulgaria)"));
1153 break;
1154 case 0x01:
1155 return(copy_lang (lang_out, lang_len, "Bulgarian"));
1156 break;
1158 break;
1159 case 0x03:
1160 switch(secondary) {
1161 case 0x00:
1162 return(copy_lang (lang_out, lang_len, "Catalan (Spain)"));
1163 break;
1164 case 0x01:
1165 return(copy_lang (lang_out, lang_len, "Catalan"));
1166 break;
1168 break;
1169 case 0x04:
1170 switch(secondary) {
1171 case 0x00:
1172 case 0x01:
1173 return(copy_lang (lang_out, lang_len, "Chinese (Taiwan)"));
1174 break;
1175 case 0x02:
1176 return(copy_lang (lang_out, lang_len, "Chinese (PRC)"));
1177 break;
1178 case 0x03:
1179 return(copy_lang (lang_out, lang_len, "Chinese (Hong Kong S.A.R.)"));
1180 break;
1181 case 0x04:
1182 return(copy_lang (lang_out, lang_len, "Chinese (Singapore)"));
1183 break;
1184 case 0x05:
1185 return(copy_lang (lang_out, lang_len, "Chinese (Macau S.A.R.)"));
1186 break;
1188 break;
1189 case 0x05:
1190 switch(secondary) {
1191 case 0x00:
1192 return(copy_lang (lang_out, lang_len, "Czech (Czech Republic)"));
1193 break;
1194 case 0x01:
1195 return(copy_lang (lang_out, lang_len, "Czech"));
1196 break;
1198 break;
1199 case 0x06:
1200 switch(secondary) {
1201 case 0x00:
1202 return(copy_lang (lang_out, lang_len, "Danish (Denmark)"));
1203 break;
1204 case 0x01:
1205 return(copy_lang (lang_out, lang_len, "Danish"));
1206 break;
1208 break;
1209 case 0x07:
1210 switch(secondary) {
1211 case 0x00:
1212 case 0x01:
1213 return(copy_lang (lang_out, lang_len, "German (Germany)"));
1214 break;
1215 case 0x02:
1216 return(copy_lang (lang_out, lang_len, "German (Switzerland)"));
1217 break;
1218 case 0x03:
1219 return(copy_lang (lang_out, lang_len, "German (Austria)"));
1220 break;
1221 case 0x04:
1222 return(copy_lang (lang_out, lang_len, "German (Luxembourg)"));
1223 break;
1224 case 0x05:
1225 return(copy_lang (lang_out, lang_len, "German (Liechtenstein)"));
1226 break;
1228 break;
1229 case 0x08:
1230 switch(secondary) {
1231 case 0x00:
1232 return(copy_lang (lang_out, lang_len, "Greek (Greece)"));
1233 break;
1234 case 0x01:
1235 return(copy_lang (lang_out, lang_len, "Greek"));
1236 break;
1238 break;
1239 case 0x09:
1240 switch(secondary) {
1241 case 0x00:
1242 case 0x01:
1243 return(copy_lang (lang_out, lang_len, "English (United States)"));
1244 break;
1245 case 0x02:
1246 return(copy_lang (lang_out, lang_len, "English (United Kingdom)"));
1247 break;
1248 case 0x03:
1249 return(copy_lang (lang_out, lang_len, "English (Australia)"));
1250 break;
1251 case 0x04:
1252 return(copy_lang (lang_out, lang_len, "English (Canada)"));
1253 break;
1254 case 0x05:
1255 return(copy_lang (lang_out, lang_len, "English (New Zealand)"));
1256 break;
1257 case 0x06:
1258 return(copy_lang (lang_out, lang_len, "English (Ireland)"));
1259 break;
1260 case 0x07:
1261 return(copy_lang (lang_out, lang_len, "English (South Africa)"));
1262 break;
1263 case 0x08:
1264 return(copy_lang (lang_out, lang_len, "English (Jamaica)"));
1265 break;
1266 case 0x09:
1267 return(copy_lang (lang_out, lang_len, "English (Caribbean)"));
1268 break;
1269 case 0x0a:
1270 return(copy_lang (lang_out, lang_len, "English (Belize)"));
1271 break;
1272 case 0x0b:
1273 return(copy_lang (lang_out, lang_len, "English (Trinidad and Tobago)"));
1274 break;
1275 case 0x0c:
1276 return(copy_lang (lang_out, lang_len, "English (Zimbabwe)"));
1277 break;
1278 case 0x0d:
1279 return(copy_lang (lang_out, lang_len, "English (Philippines)"));
1280 break;
1281 case 0x10:
1282 return(copy_lang (lang_out, lang_len, "English (India)"));
1283 break;
1284 case 0x11:
1285 return(copy_lang (lang_out, lang_len, "English (Malaysia)"));
1286 break;
1287 case 0x12:
1288 return(copy_lang (lang_out, lang_len, "English (Singapore)"));
1289 break;
1291 break;
1292 case 0x0a:
1293 switch(secondary) {
1294 case 0x00:
1295 return(copy_lang (lang_out, lang_len, "Spanish (Spain)"));
1296 break;
1297 case 0x01:
1298 return(copy_lang (lang_out, lang_len, "Spanish (Traditional Sort)"));
1299 break;
1300 case 0x02:
1301 return(copy_lang (lang_out, lang_len, "Spanish (Mexico)"));
1302 break;
1303 case 0x03:
1304 return(copy_lang (lang_out, lang_len, "Spanish (International Sort)"));
1305 break;
1306 case 0x04:
1307 return(copy_lang (lang_out, lang_len, "Spanish (Guatemala)"));
1308 break;
1309 case 0x05:
1310 return(copy_lang (lang_out, lang_len, "Spanish (Costa Rica)"));
1311 break;
1312 case 0x06:
1313 return(copy_lang (lang_out, lang_len, "Spanish (Panama)"));
1314 break;
1315 case 0x07:
1316 return(copy_lang (lang_out, lang_len, "Spanish (Dominican Republic)"));
1317 break;
1318 case 0x08:
1319 return(copy_lang (lang_out, lang_len, "Spanish (Venezuela)"));
1320 break;
1321 case 0x09:
1322 return(copy_lang (lang_out, lang_len, "Spanish (Colombia)"));
1323 break;
1324 case 0x0a:
1325 return(copy_lang (lang_out, lang_len, "Spanish (Peru)"));
1326 break;
1327 case 0x0b:
1328 return(copy_lang (lang_out, lang_len, "Spanish (Argentina)"));
1329 break;
1330 case 0x0c:
1331 return(copy_lang (lang_out, lang_len, "Spanish (Ecuador)"));
1332 break;
1333 case 0x0d:
1334 return(copy_lang (lang_out, lang_len, "Spanish (Chile)"));
1335 break;
1336 case 0x0e:
1337 return(copy_lang (lang_out, lang_len, "Spanish (Uruguay)"));
1338 break;
1339 case 0x0f:
1340 return(copy_lang (lang_out, lang_len, "Spanish (Paraguay)"));
1341 break;
1342 case 0x10:
1343 return(copy_lang (lang_out, lang_len, "Spanish (Bolivia)"));
1344 break;
1345 case 0x11:
1346 return(copy_lang (lang_out, lang_len, "Spanish (El Salvador)"));
1347 break;
1348 case 0x12:
1349 return(copy_lang (lang_out, lang_len, "Spanish (Honduras)"));
1350 break;
1351 case 0x13:
1352 return(copy_lang (lang_out, lang_len, "Spanish (Nicaragua)"));
1353 break;
1354 case 0x14:
1355 return(copy_lang (lang_out, lang_len, "Spanish (Puerto Rico)"));
1356 break;
1357 case 0x15:
1358 return(copy_lang (lang_out, lang_len, "Spanish (United States)"));
1359 break;
1361 break;
1362 case 0x0b:
1363 switch(secondary) {
1364 case 0x00:
1365 return(copy_lang (lang_out, lang_len, "Finnish (Finland)"));
1366 break;
1367 case 0x01:
1368 return(copy_lang (lang_out, lang_len, "Finnish"));
1369 break;
1371 break;
1372 case 0x0c:
1373 switch(secondary) {
1374 case 0x00:
1375 case 0x01:
1376 return(copy_lang (lang_out, lang_len, "French (France)"));
1377 break;
1378 case 0x02:
1379 return(copy_lang (lang_out, lang_len, "French (Belgium)"));
1380 break;
1381 case 0x03:
1382 return(copy_lang (lang_out, lang_len, "French (Canada)"));
1383 break;
1384 case 0x04:
1385 return(copy_lang (lang_out, lang_len, "French (Switzerland)"));
1386 break;
1387 case 0x05:
1388 return(copy_lang (lang_out, lang_len, "French (Luxembourg)"));
1389 break;
1390 case 0x06:
1391 return(copy_lang (lang_out, lang_len, "French (Monaco)"));
1392 break;
1394 break;
1395 case 0x0d:
1396 switch(secondary) {
1397 case 0x00:
1398 return(copy_lang (lang_out, lang_len, "Hebrew (Israel)"));
1399 break;
1400 case 0x01:
1401 return(copy_lang (lang_out, lang_len, "Hebrew"));
1402 break;
1404 break;
1405 case 0x0e:
1406 switch(secondary) {
1407 case 0x00:
1408 return(copy_lang (lang_out, lang_len, "Hungarian (Hungary)"));
1409 break;
1410 case 0x01:
1411 return(copy_lang (lang_out, lang_len, "Hungarian"));
1412 break;
1414 break;
1415 case 0x0f:
1416 switch(secondary) {
1417 case 0x00:
1418 return(copy_lang (lang_out, lang_len, "Icelandic (Iceland)"));
1419 break;
1420 case 0x01:
1421 return(copy_lang (lang_out, lang_len, "Icelandic"));
1422 break;
1424 break;
1425 case 0x10:
1426 switch(secondary) {
1427 case 0x00:
1428 case 0x01:
1429 return(copy_lang (lang_out, lang_len, "Italian (Italy)"));
1430 break;
1431 case 0x02:
1432 return(copy_lang (lang_out, lang_len, "Italian (Switzerland)"));
1433 break;
1435 break;
1436 case 0x11:
1437 switch(secondary) {
1438 case 0x00:
1439 return(copy_lang (lang_out, lang_len, "Japanese (Japan)"));
1440 break;
1441 case 0x01:
1442 return(copy_lang (lang_out, lang_len, "Japanese"));
1443 break;
1445 break;
1446 case 0x12:
1447 switch(secondary) {
1448 case 0x00:
1449 return(copy_lang (lang_out, lang_len, "Korean (Korea)"));
1450 break;
1451 case 0x01:
1452 return(copy_lang (lang_out, lang_len, "Korean"));
1453 break;
1455 break;
1456 case 0x13:
1457 switch(secondary) {
1458 case 0x00:
1459 case 0x01:
1460 return(copy_lang (lang_out, lang_len, "Dutch (Netherlands)"));
1461 break;
1462 case 0x02:
1463 return(copy_lang (lang_out, lang_len, "Dutch (Belgium)"));
1464 break;
1466 break;
1467 case 0x14:
1468 switch(secondary) {
1469 case 0x00:
1470 case 0x01:
1471 return(copy_lang (lang_out, lang_len, "Norwegian (Bokmal)"));
1472 break;
1473 case 0x02:
1474 return(copy_lang (lang_out, lang_len, "Norwegian (Nynorsk)"));
1475 break;
1477 break;
1478 case 0x15:
1479 switch(secondary) {
1480 case 0x00:
1481 return(copy_lang (lang_out, lang_len, "Polish (Poland)"));
1482 break;
1483 case 0x01:
1484 return(copy_lang (lang_out, lang_len, "Polish"));
1485 break;
1487 break;
1488 case 0x16:
1489 switch(secondary) {
1490 case 0x00:
1491 case 0x01:
1492 return(copy_lang (lang_out, lang_len, "Portuguese (Brazil)"));
1493 break;
1494 case 0x02:
1495 return(copy_lang (lang_out, lang_len, "Portuguese (Portugal)"));
1496 break;
1498 break;
1499 case 0x17:
1500 switch(secondary) {
1501 case 0x01:
1502 return(copy_lang (lang_out, lang_len, "Romansh (Switzerland)"));
1503 break;
1505 break;
1506 case 0x18:
1507 switch(secondary) {
1508 case 0x00:
1509 return(copy_lang (lang_out, lang_len, "Romanian (Romania)"));
1510 break;
1511 case 0x01:
1512 return(copy_lang (lang_out, lang_len, "Romanian"));
1513 break;
1515 break;
1516 case 0x19:
1517 switch(secondary) {
1518 case 0x00:
1519 return(copy_lang (lang_out, lang_len, "Russian (Russia)"));
1520 break;
1521 case 0x01:
1522 return(copy_lang (lang_out, lang_len, "Russian"));
1523 break;
1525 break;
1526 case 0x1a:
1527 switch(secondary) {
1528 case 0x00:
1529 return(copy_lang (lang_out, lang_len, "Croatian (Croatia)"));
1530 break;
1531 case 0x01:
1532 return(copy_lang (lang_out, lang_len, "Croatian"));
1533 break;
1534 case 0x02:
1535 return(copy_lang (lang_out, lang_len, "Serbian (Latin)"));
1536 break;
1537 case 0x03:
1538 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic)"));
1539 break;
1540 case 0x04:
1541 return(copy_lang (lang_out, lang_len, "Croatian (Bosnia and Herzegovina)"));
1542 break;
1543 case 0x05:
1544 return(copy_lang (lang_out, lang_len, "Bosnian (Latin, Bosnia and Herzegovina)"));
1545 break;
1546 case 0x06:
1547 return(copy_lang (lang_out, lang_len, "Serbian (Latin, Bosnia and Herzegovina)"));
1548 break;
1549 case 0x07:
1550 return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic, Bosnia and Herzegovina)"));
1551 break;
1552 case 0x08:
1553 return(copy_lang (lang_out, lang_len, "Bosnian (Cyrillic, Bosnia and Herzegovina)"));
1554 break;
1556 break;
1557 case 0x1b:
1558 switch(secondary) {
1559 case 0x00:
1560 return(copy_lang (lang_out, lang_len, "Slovak (Slovakia)"));
1561 break;
1562 case 0x01:
1563 return(copy_lang (lang_out, lang_len, "Slovak"));
1564 break;
1566 break;
1567 case 0x1c:
1568 switch(secondary) {
1569 case 0x00:
1570 return(copy_lang (lang_out, lang_len, "Albanian (Albania)"));
1571 break;
1572 case 0x01:
1573 return(copy_lang (lang_out, lang_len, "Albanian"));
1574 break;
1576 break;
1577 case 0x1d:
1578 switch(secondary) {
1579 case 0x00:
1580 return(copy_lang (lang_out, lang_len, "Swedish (Sweden)"));
1581 break;
1582 case 0x01:
1583 return(copy_lang (lang_out, lang_len, "Swedish"));
1584 break;
1585 case 0x02:
1586 return(copy_lang (lang_out, lang_len, "Swedish (Finland)"));
1587 break;
1589 break;
1590 case 0x1e:
1591 switch(secondary) {
1592 case 0x00:
1593 return(copy_lang (lang_out, lang_len, "Thai (Thailand)"));
1594 break;
1595 case 0x01:
1596 return(copy_lang (lang_out, lang_len, "Thai"));
1597 break;
1599 break;
1600 case 0x1f:
1601 switch(secondary) {
1602 case 0x00:
1603 return(copy_lang (lang_out, lang_len, "Turkish (Turkey)"));
1604 break;
1605 case 0x01:
1606 return(copy_lang (lang_out, lang_len, "Turkish"));
1607 break;
1609 break;
1610 case 0x20:
1611 switch(secondary) {
1612 case 0x00:
1613 return(copy_lang (lang_out, lang_len, "Urdu (Islamic Republic of Pakistan)"));
1614 break;
1615 case 0x01:
1616 return(copy_lang (lang_out, lang_len, "Urdu"));
1617 break;
1619 break;
1620 case 0x21:
1621 switch(secondary) {
1622 case 0x00:
1623 return(copy_lang (lang_out, lang_len, "Indonesian (Indonesia)"));
1624 break;
1625 case 0x01:
1626 return(copy_lang (lang_out, lang_len, "Indonesian"));
1627 break;
1629 break;
1630 case 0x22:
1631 switch(secondary) {
1632 case 0x00:
1633 return(copy_lang (lang_out, lang_len, "Ukrainian (Ukraine)"));
1634 break;
1635 case 0x01:
1636 return(copy_lang (lang_out, lang_len, "Ukrainian"));
1637 break;
1639 break;
1640 case 0x23:
1641 switch(secondary) {
1642 case 0x00:
1643 return(copy_lang (lang_out, lang_len, "Belarusian (Belarus)"));
1644 break;
1645 case 0x01:
1646 return(copy_lang (lang_out, lang_len, "Belarusian"));
1647 break;
1649 break;
1650 case 0x24:
1651 switch(secondary) {
1652 case 0x00:
1653 return(copy_lang (lang_out, lang_len, "Slovenian (Slovenia)"));
1654 break;
1655 case 0x01:
1656 return(copy_lang (lang_out, lang_len, "Slovenian"));
1657 break;
1659 break;
1660 case 0x25:
1661 switch(secondary) {
1662 case 0x00:
1663 return(copy_lang (lang_out, lang_len, "Estonian (Estonia)"));
1664 break;
1665 case 0x01:
1666 return(copy_lang (lang_out, lang_len, "Estonian"));
1667 break;
1669 break;
1670 case 0x26:
1671 switch(secondary) {
1672 case 0x00:
1673 return(copy_lang (lang_out, lang_len, "Latvian (Latvia)"));
1674 break;
1675 case 0x01:
1676 return(copy_lang (lang_out, lang_len, "Latvian"));
1677 break;
1679 break;
1680 case 0x27:
1681 switch(secondary) {
1682 case 0x00:
1683 return(copy_lang (lang_out, lang_len, "Lithuanian (Lithuania)"));
1684 break;
1685 case 0x01:
1686 return(copy_lang (lang_out, lang_len, "Lithuanian"));
1687 break;
1689 break;
1690 case 0x28:
1691 switch(secondary) {
1692 case 0x01:
1693 return(copy_lang (lang_out, lang_len, "Tajik (Tajikistan)"));
1694 break;
1696 break;
1697 case 0x29:
1698 switch(secondary) {
1699 case 0x00:
1700 return(copy_lang (lang_out, lang_len, "Farsi (Iran)"));
1701 break;
1702 case 0x01:
1703 return(copy_lang (lang_out, lang_len, "Farsi"));
1704 break;
1706 break;
1707 case 0x2a:
1708 switch(secondary) {
1709 case 0x00:
1710 return(copy_lang (lang_out, lang_len, "Vietnamese (Viet Nam)"));
1711 break;
1712 case 0x01:
1713 return(copy_lang (lang_out, lang_len, "Vietnamese"));
1714 break;
1716 break;
1717 case 0x2b:
1718 switch(secondary) {
1719 case 0x00:
1720 return(copy_lang (lang_out, lang_len, "Armenian (Armenia)"));
1721 break;
1722 case 0x01:
1723 return(copy_lang (lang_out, lang_len, "Armenian"));
1724 break;
1726 break;
1727 case 0x2c:
1728 switch(secondary) {
1729 case 0x00:
1730 return(copy_lang (lang_out, lang_len, "Azeri (Latin) (Azerbaijan)"));
1731 break;
1732 case 0x01:
1733 return(copy_lang (lang_out, lang_len, "Azeri (Latin)"));
1734 break;
1735 case 0x02:
1736 return(copy_lang (lang_out, lang_len, "Azeri (Cyrillic)"));
1737 break;
1739 break;
1740 case 0x2d:
1741 switch(secondary) {
1742 case 0x00:
1743 return(copy_lang (lang_out, lang_len, "Basque (Spain)"));
1744 break;
1745 case 0x01:
1746 return(copy_lang (lang_out, lang_len, "Basque"));
1747 break;
1749 break;
1750 case 0x2e:
1751 switch(secondary) {
1752 case 0x01:
1753 return(copy_lang (lang_out, lang_len, "Upper Sorbian (Germany)"));
1754 break;
1755 case 0x02:
1756 return(copy_lang (lang_out, lang_len, "Lower Sorbian (Germany)"));
1757 break;
1759 break;
1760 case 0x2f:
1761 switch(secondary) {
1762 case 0x00:
1763 return(copy_lang (lang_out, lang_len, "FYRO Macedonian (Former Yugoslav Republic of Macedonia)"));
1764 break;
1765 case 0x01:
1766 return(copy_lang (lang_out, lang_len, "FYRO Macedonian"));
1767 break;
1769 break;
1770 case 0x32:
1771 switch(secondary) {
1772 case 0x00:
1773 return(copy_lang (lang_out, lang_len, "Tswana (South Africa)"));
1774 break;
1775 case 0x01:
1776 return(copy_lang (lang_out, lang_len, "Tswana"));
1777 break;
1779 break;
1780 case 0x34:
1781 switch(secondary) {
1782 case 0x00:
1783 return(copy_lang (lang_out, lang_len, "Xhosa (South Africa)"));
1784 break;
1785 case 0x01:
1786 return(copy_lang (lang_out, lang_len, "Xhosa"));
1787 break;
1789 break;
1790 case 0x35:
1791 switch(secondary) {
1792 case 0x00:
1793 return(copy_lang (lang_out, lang_len, "Zulu (South Africa)"));
1794 break;
1795 case 0x01:
1796 return(copy_lang (lang_out, lang_len, "Zulu"));
1797 break;
1799 break;
1800 case 0x36:
1801 switch(secondary) {
1802 case 0x00:
1803 return(copy_lang (lang_out, lang_len, "Afrikaans (South Africa)"));
1804 break;
1805 case 0x01:
1806 return(copy_lang (lang_out, lang_len, "Afrikaans"));
1807 break;
1809 break;
1810 case 0x37:
1811 switch(secondary) {
1812 case 0x00:
1813 return(copy_lang (lang_out, lang_len, "Georgian (Georgia)"));
1814 break;
1815 case 0x01:
1816 return(copy_lang (lang_out, lang_len, "Georgian"));
1817 break;
1819 break;
1820 case 0x38:
1821 switch(secondary) {
1822 case 0x00:
1823 return(copy_lang (lang_out, lang_len, "Faroese (Faroe Islands)"));
1824 break;
1825 case 0x01:
1826 return(copy_lang (lang_out, lang_len, "Faroese"));
1827 break;
1829 break;
1830 case 0x39:
1831 switch(secondary) {
1832 case 0x00:
1833 return(copy_lang (lang_out, lang_len, "Hindi (India)"));
1834 break;
1835 case 0x01:
1836 return(copy_lang (lang_out, lang_len, "Hindi"));
1837 break;
1839 break;
1840 case 0x3a:
1841 switch(secondary) {
1842 case 0x00:
1843 return(copy_lang (lang_out, lang_len, "Maltese (Malta)"));
1844 break;
1845 case 0x01:
1846 return(copy_lang (lang_out, lang_len, "Maltese"));
1847 break;
1849 break;
1850 case 0x3b:
1851 switch(secondary) {
1852 case 0x00:
1853 return(copy_lang (lang_out, lang_len, "Sami (Northern) (Norway)"));
1854 break;
1855 case 0x01:
1856 return(copy_lang (lang_out, lang_len, "Sami, Northern (Norway)"));
1857 break;
1858 case 0x02:
1859 return(copy_lang (lang_out, lang_len, "Sami, Northern (Sweden)"));
1860 break;
1861 case 0x03:
1862 return(copy_lang (lang_out, lang_len, "Sami, Northern (Finland)"));
1863 break;
1864 case 0x04:
1865 return(copy_lang (lang_out, lang_len, "Sami, Lule (Norway)"));
1866 break;
1867 case 0x05:
1868 return(copy_lang (lang_out, lang_len, "Sami, Lule (Sweden)"));
1869 break;
1870 case 0x06:
1871 return(copy_lang (lang_out, lang_len, "Sami, Southern (Norway)"));
1872 break;
1873 case 0x07:
1874 return(copy_lang (lang_out, lang_len, "Sami, Southern (Sweden)"));
1875 break;
1876 case 0x08:
1877 return(copy_lang (lang_out, lang_len, "Sami, Skolt (Finland)"));
1878 break;
1879 case 0x09:
1880 return(copy_lang (lang_out, lang_len, "Sami, Inari (Finland)"));
1881 break;
1883 break;
1884 case 0x3c:
1885 switch(secondary) {
1886 case 0x02:
1887 return(copy_lang (lang_out, lang_len, "Irish (Ireland)"));
1888 break;
1890 break;
1891 case 0x3e:
1892 switch(secondary) {
1893 case 0x00:
1894 case 0x01:
1895 return(copy_lang (lang_out, lang_len, "Malay (Malaysia)"));
1896 break;
1897 case 0x02:
1898 return(copy_lang (lang_out, lang_len, "Malay (Brunei Darussalam)"));
1899 break;
1901 break;
1902 case 0x3f:
1903 switch(secondary) {
1904 case 0x00:
1905 return(copy_lang (lang_out, lang_len, "Kazakh (Kazakhstan)"));
1906 break;
1907 case 0x01:
1908 return(copy_lang (lang_out, lang_len, "Kazakh"));
1909 break;
1911 break;
1912 case 0x40:
1913 switch(secondary) {
1914 case 0x00:
1915 return(copy_lang (lang_out, lang_len, "Kyrgyz (Kyrgyzstan)"));
1916 break;
1917 case 0x01:
1918 return(copy_lang (lang_out, lang_len, "Kyrgyz (Cyrillic)"));
1919 break;
1921 break;
1922 case 0x41:
1923 switch(secondary) {
1924 case 0x00:
1925 return(copy_lang (lang_out, lang_len, "Swahili (Kenya)"));
1926 break;
1927 case 0x01:
1928 return(copy_lang (lang_out, lang_len, "Swahili"));
1929 break;
1931 break;
1932 case 0x42:
1933 switch(secondary) {
1934 case 0x01:
1935 return(copy_lang (lang_out, lang_len, "Turkmen (Turkmenistan)"));
1936 break;
1938 break;
1939 case 0x43:
1940 switch(secondary) {
1941 case 0x00:
1942 return(copy_lang (lang_out, lang_len, "Uzbek (Latin) (Uzbekistan)"));
1943 break;
1944 case 0x01:
1945 return(copy_lang (lang_out, lang_len, "Uzbek (Latin)"));
1946 break;
1947 case 0x02:
1948 return(copy_lang (lang_out, lang_len, "Uzbek (Cyrillic)"));
1949 break;
1951 break;
1952 case 0x44:
1953 switch(secondary) {
1954 case 0x00:
1955 return(copy_lang (lang_out, lang_len, "Tatar (Russia)"));
1956 break;
1957 case 0x01:
1958 return(copy_lang (lang_out, lang_len, "Tatar"));
1959 break;
1961 break;
1962 case 0x45:
1963 switch(secondary) {
1964 case 0x00:
1965 case 0x01:
1966 return(copy_lang (lang_out, lang_len, "Bengali (India)"));
1967 break;
1969 break;
1970 case 0x46:
1971 switch(secondary) {
1972 case 0x00:
1973 return(copy_lang (lang_out, lang_len, "Punjabi (India)"));
1974 break;
1975 case 0x01:
1976 return(copy_lang (lang_out, lang_len, "Punjabi"));
1977 break;
1979 break;
1980 case 0x47:
1981 switch(secondary) {
1982 case 0x00:
1983 return(copy_lang (lang_out, lang_len, "Gujarati (India)"));
1984 break;
1985 case 0x01:
1986 return(copy_lang (lang_out, lang_len, "Gujarati"));
1987 break;
1989 break;
1990 case 0x49:
1991 switch(secondary) {
1992 case 0x00:
1993 return(copy_lang (lang_out, lang_len, "Tamil (India)"));
1994 break;
1995 case 0x01:
1996 return(copy_lang (lang_out, lang_len, "Tamil"));
1997 break;
1999 break;
2000 case 0x4a:
2001 switch(secondary) {
2002 case 0x00:
2003 return(copy_lang (lang_out, lang_len, "Telugu (India)"));
2004 break;
2005 case 0x01:
2006 return(copy_lang (lang_out, lang_len, "Telugu"));
2007 break;
2009 break;
2010 case 0x4b:
2011 switch(secondary) {
2012 case 0x00:
2013 return(copy_lang (lang_out, lang_len, "Kannada (India)"));
2014 break;
2015 case 0x01:
2016 return(copy_lang (lang_out, lang_len, "Kannada"));
2017 break;
2019 break;
2020 case 0x4c:
2021 switch(secondary) {
2022 case 0x00:
2023 case 0x01:
2024 return(copy_lang (lang_out, lang_len, "Malayalam (India)"));
2025 break;
2027 break;
2028 case 0x4d:
2029 switch(secondary) {
2030 case 0x01:
2031 return(copy_lang (lang_out, lang_len, "Assamese (India)"));
2032 break;
2034 break;
2035 case 0x4e:
2036 switch(secondary) {
2037 case 0x00:
2038 return(copy_lang (lang_out, lang_len, "Marathi (India)"));
2039 break;
2040 case 0x01:
2041 return(copy_lang (lang_out, lang_len, "Marathi"));
2042 break;
2044 break;
2045 case 0x4f:
2046 switch(secondary) {
2047 case 0x00:
2048 return(copy_lang (lang_out, lang_len, "Sanskrit (India)"));
2049 break;
2050 case 0x01:
2051 return(copy_lang (lang_out, lang_len, "Sanskrit"));
2052 break;
2054 break;
2055 case 0x50:
2056 switch(secondary) {
2057 case 0x00:
2058 return(copy_lang (lang_out, lang_len, "Mongolian (Mongolia)"));
2059 break;
2060 case 0x01:
2061 return(copy_lang (lang_out, lang_len, "Mongolian (Cyrillic)"));
2062 break;
2063 case 0x02:
2064 return(copy_lang (lang_out, lang_len, "Mongolian (PRC)"));
2065 break;
2067 break;
2068 case 0x51:
2069 switch(secondary) {
2070 case 0x01:
2071 return(copy_lang (lang_out, lang_len, "Tibetan (PRC)"));
2072 break;
2073 case 0x02:
2074 return(copy_lang (lang_out, lang_len, "Tibetan (Bhutan)"));
2075 break;
2077 break;
2078 case 0x52:
2079 switch(secondary) {
2080 case 0x00:
2081 return(copy_lang (lang_out, lang_len, "Welsh (United Kingdom)"));
2082 break;
2083 case 0x01:
2084 return(copy_lang (lang_out, lang_len, "Welsh"));
2085 break;
2087 break;
2088 case 0x53:
2089 switch(secondary) {
2090 case 0x01:
2091 return(copy_lang (lang_out, lang_len, "Khmer (Cambodia)"));
2092 break;
2094 break;
2095 case 0x54:
2096 switch(secondary) {
2097 case 0x01:
2098 return(copy_lang (lang_out, lang_len, "Lao (Lao PDR)"));
2099 break;
2101 break;
2102 case 0x56:
2103 switch(secondary) {
2104 case 0x00:
2105 return(copy_lang (lang_out, lang_len, "Galician (Spain)"));
2106 break;
2107 case 0x01:
2108 return(copy_lang (lang_out, lang_len, "Galician"));
2109 break;
2111 break;
2112 case 0x57:
2113 switch(secondary) {
2114 case 0x00:
2115 return(copy_lang (lang_out, lang_len, "Konkani (India)"));
2116 break;
2117 case 0x01:
2118 return(copy_lang (lang_out, lang_len, "Konkani"));
2119 break;
2121 break;
2122 case 0x5a:
2123 switch(secondary) {
2124 case 0x00:
2125 return(copy_lang (lang_out, lang_len, "Syriac (Syria)"));
2126 break;
2127 case 0x01:
2128 return(copy_lang (lang_out, lang_len, "Syriac"));
2129 break;
2131 break;
2132 case 0x5b:
2133 switch(secondary) {
2134 case 0x01:
2135 return(copy_lang (lang_out, lang_len, "Sinhala (Sri Lanka)"));
2136 break;
2138 break;
2139 case 0x5d:
2140 switch(secondary) {
2141 case 0x01:
2142 return(copy_lang (lang_out, lang_len, "Inuktitut (Syllabics, Canada)"));
2143 break;
2144 case 0x02:
2145 return(copy_lang (lang_out, lang_len, "Inuktitut (Latin, Canada)"));
2146 break;
2148 break;
2149 case 0x5e:
2150 switch(secondary) {
2151 case 0x01:
2152 return(copy_lang (lang_out, lang_len, "Amharic (Ethiopia)"));
2153 break;
2155 break;
2156 case 0x5f:
2157 switch(secondary) {
2158 case 0x02:
2159 return(copy_lang (lang_out, lang_len, "Tamazight (Algeria, Latin)"));
2160 break;
2162 break;
2163 case 0x61:
2164 switch(secondary) {
2165 case 0x01:
2166 return(copy_lang (lang_out, lang_len, "Nepali (Nepal)"));
2167 break;
2169 break;
2170 case 0x62:
2171 switch(secondary) {
2172 case 0x01:
2173 return(copy_lang (lang_out, lang_len, "Frisian (Netherlands)"));
2174 break;
2176 break;
2177 case 0x63:
2178 switch(secondary) {
2179 case 0x01:
2180 return(copy_lang (lang_out, lang_len, "Pashto (Afghanistan)"));
2181 break;
2183 break;
2184 case 0x64:
2185 switch(secondary) {
2186 case 0x01:
2187 return(copy_lang (lang_out, lang_len, "Filipino (Philippines)"));
2188 break;
2190 break;
2191 case 0x65:
2192 switch(secondary) {
2193 case 0x00:
2194 return(copy_lang (lang_out, lang_len, "Divehi (Maldives)"));
2195 break;
2196 case 0x01:
2197 return(copy_lang (lang_out, lang_len, "Divehi"));
2198 break;
2200 break;
2201 case 0x68:
2202 switch(secondary) {
2203 case 0x01:
2204 return(copy_lang (lang_out, lang_len, "Hausa (Nigeria, Latin)"));
2205 break;
2207 break;
2208 case 0x6a:
2209 switch(secondary) {
2210 case 0x01:
2211 return(copy_lang (lang_out, lang_len, "Yoruba (Nigeria)"));
2212 break;
2214 break;
2215 case 0x6b:
2216 switch(secondary) {
2217 case 0x00:
2218 case 0x01:
2219 return(copy_lang (lang_out, lang_len, "Quechua (Bolivia)"));
2220 break;
2221 case 0x02:
2222 return(copy_lang (lang_out, lang_len, "Quechua (Ecuador)"));
2223 break;
2224 case 0x03:
2225 return(copy_lang (lang_out, lang_len, "Quechua (Peru)"));
2226 break;
2228 break;
2229 case 0x6c:
2230 switch(secondary) {
2231 case 0x00:
2232 return(copy_lang (lang_out, lang_len, "Northern Sotho (South Africa)"));
2233 break;
2234 case 0x01:
2235 return(copy_lang (lang_out, lang_len, "Northern Sotho"));
2236 break;
2238 break;
2239 case 0x6d:
2240 switch(secondary) {
2241 case 0x01:
2242 return(copy_lang (lang_out, lang_len, "Bashkir (Russia)"));
2243 break;
2245 break;
2246 case 0x6e:
2247 switch(secondary) {
2248 case 0x01:
2249 return(copy_lang (lang_out, lang_len, "Luxembourgish (Luxembourg)"));
2250 break;
2252 break;
2253 case 0x6f:
2254 switch(secondary) {
2255 case 0x01:
2256 return(copy_lang (lang_out, lang_len, "Greenlandic (Greenland)"));
2257 break;
2259 break;
2260 case 0x78:
2261 switch(secondary) {
2262 case 0x01:
2263 return(copy_lang (lang_out, lang_len, "Yi (PRC)"));
2264 break;
2266 break;
2267 case 0x7a:
2268 switch(secondary) {
2269 case 0x01:
2270 return(copy_lang (lang_out, lang_len, "Mapudungun (Chile)"));
2271 break;
2273 break;
2274 case 0x7c:
2275 switch(secondary) {
2276 case 0x01:
2277 return(copy_lang (lang_out, lang_len, "Mohawk (Mohawk)"));
2278 break;
2280 break;
2281 case 0x7e:
2282 switch(secondary) {
2283 case 0x01:
2284 return(copy_lang (lang_out, lang_len, "Breton (France)"));
2285 break;
2287 break;
2288 case 0x7f:
2289 switch(secondary) {
2290 case 0x00:
2291 return(copy_lang (lang_out, lang_len, "Invariant Language (Invariant Country)"));
2292 break;
2294 break;
2295 case 0x80:
2296 switch(secondary) {
2297 case 0x01:
2298 return(copy_lang (lang_out, lang_len, "Uighur (PRC)"));
2299 break;
2301 break;
2302 case 0x81:
2303 switch(secondary) {
2304 case 0x00:
2305 return(copy_lang (lang_out, lang_len, "Maori (New Zealand)"));
2306 break;
2307 case 0x01:
2308 return(copy_lang (lang_out, lang_len, "Maori"));
2309 break;
2311 break;
2312 case 0x83:
2313 switch(secondary) {
2314 case 0x01:
2315 return(copy_lang (lang_out, lang_len, "Corsican (France)"));
2316 break;
2318 break;
2319 case 0x84:
2320 switch(secondary) {
2321 case 0x01:
2322 return(copy_lang (lang_out, lang_len, "Alsatian (France)"));
2323 break;
2325 break;
2326 case 0x85:
2327 switch(secondary) {
2328 case 0x01:
2329 return(copy_lang (lang_out, lang_len, "Yakut (Russia)"));
2330 break;
2332 break;
2333 case 0x86:
2334 switch(secondary) {
2335 case 0x01:
2336 return(copy_lang (lang_out, lang_len, "K'iche (Guatemala)"));
2337 break;
2339 break;
2340 case 0x87:
2341 switch(secondary) {
2342 case 0x01:
2343 return(copy_lang (lang_out, lang_len, "Kinyarwanda (Rwanda)"));
2344 break;
2346 break;
2347 case 0x88:
2348 switch(secondary) {
2349 case 0x01:
2350 return(copy_lang (lang_out, lang_len, "Wolof (Senegal)"));
2351 break;
2353 break;
2354 case 0x8c:
2355 switch(secondary) {
2356 case 0x01:
2357 return(copy_lang (lang_out, lang_len, "Dari (Afghanistan)"));
2358 break;
2360 break;
2362 default:
2363 return(copy_lang (lang_out, lang_len, "Language Neutral"));
2367 return(copy_lang (lang_out, lang_len, "Language Neutral"));