2008-12-08 Rodrigo Kumpera <rkumpera@novell.com>
[mono.git] / mono / io-layer / versioninfo.c
1 /*
2  * versioninfo.c:  Version information support
3  *
4  * Author:
5  *      Dick Porter (dick@ximian.com)
6  *
7  * (C) 2007 Novell, Inc.
8  */
9
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>
20
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>
27
28 #undef DEBUG
29
30 #define ALIGN32(ptr) ptr = (gpointer)((char *)ptr + 3); ptr = (gpointer)((char *)ptr - ((gsize)ptr & 3));
31
32 static WapiImageSectionHeader *get_enclosing_section_header (guint32 rva, WapiImageNTHeaders32 *nt_headers)
33 {
34         WapiImageSectionHeader *section = _WAPI_IMAGE_FIRST_SECTION32 (nt_headers);
35         guint32 i;
36         
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);
41                 }
42                 
43                 if ((rva >= GUINT32_FROM_LE (section->VirtualAddress)) &&
44                     (rva < (GUINT32_FROM_LE (section->VirtualAddress) + size))) {
45                         return(section);
46                 }
47         }
48         
49         return(NULL);
50 }
51
52 /* This works for both 32bit and 64bit files, as the differences are
53  * all after the section header block
54  */
55 static gpointer get_ptr_from_rva (guint32 rva, WapiImageNTHeaders32 *ntheaders,
56                                   gpointer file_map)
57 {
58         WapiImageSectionHeader *section_header;
59         guint32 delta;
60         
61         section_header = get_enclosing_section_header (rva, ntheaders);
62         if (section_header == NULL) {
63                 return(NULL);
64         }
65         
66         delta = (guint32)(GUINT32_FROM_LE (section_header->VirtualAddress) -
67                           GUINT32_FROM_LE (section_header->PointerToRawData));
68         
69         return((guint8 *)file_map + rva - delta);
70 }
71
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)
78 {
79         WapiImageResourceDirectoryEntry swapped_entry;
80         gboolean is_string, is_dir;
81         guint32 name_offset, dir_offset, data_offset;
82         
83         swapped_entry.Name = GUINT32_FROM_LE (entry->Name);
84         swapped_entry.OffsetToData = GUINT32_FROM_LE (entry->OffsetToData);
85         
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;
91         
92         if (level == 0) {
93                 /* Normally holds a directory entry for each type of
94                  * resource
95                  */
96                 if ((is_string == FALSE &&
97                      name_offset != res_id) ||
98                     (is_string == TRUE)) {
99                         return(NULL);
100                 }
101         } else if (level == 1) {
102                 /* Normally holds a directory entry for each resource
103                  * item
104                  */
105         } else if (level == 2) {
106                 /* Normally holds a directory entry for each language
107                  */
108                 if ((is_string == FALSE &&
109                      name_offset != lang_id &&
110                      lang_id != 0) ||
111                     (is_string == TRUE)) {
112                         return(NULL);
113                 }
114         } else {
115                 g_assert_not_reached ();
116         }
117         
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;
122                 
123                 entries = GUINT16_FROM_LE (res_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (res_dir->NumberOfIdEntries);
124                 
125                 for (i = 0; i < entries; i++) {
126                         WapiImageResourceDirectoryEntry *sub_entry = &sub_entries[i];
127                         gpointer ret;
128                         
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);
134                         }
135                 }
136                 
137                 return(NULL);
138         } else {
139                 WapiImageResourceDataEntry *data_entry = (WapiImageResourceDataEntry *)((guint8 *)root + data_offset);
140                 *size = GUINT32_FROM_LE (data_entry->Size);
141                 
142                 return(get_ptr_from_rva (GUINT32_FROM_LE (data_entry->OffsetToData), nt_headers, file_map));
143         }
144 }
145
146 static gpointer find_pe_file_resources32 (gpointer file_map, guint32 map_size,
147                                           guint32 res_id, guint32 lang_id,
148                                           guint32 *size)
149 {
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;
156
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
163
164                 SetLastError (ERROR_INVALID_DATA);
165                 return(NULL);
166         }
167         
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
172
173                 SetLastError (ERROR_BAD_LENGTH);
174                 return(NULL);
175         }
176         
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
183
184                 SetLastError (ERROR_INVALID_DATA);
185                 return(NULL);
186         }
187         
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);
193         }
194
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);
201         }
202         
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);
210         }
211         
212         entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
213         resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
214         
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);
223                 }
224         }
225
226         return(NULL);
227 }
228
229 static gpointer find_pe_file_resources64 (gpointer file_map, guint32 map_size,
230                                           guint32 res_id, guint32 lang_id,
231                                           guint32 *size)
232 {
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;
239
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
246
247                 SetLastError (ERROR_INVALID_DATA);
248                 return(NULL);
249         }
250         
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
255
256                 SetLastError (ERROR_BAD_LENGTH);
257                 return(NULL);
258         }
259         
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
266
267                 SetLastError (ERROR_INVALID_DATA);
268                 return(NULL);
269         }
270         
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);
276         }
277
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);
284         }
285         
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);
293         }
294
295         entries = GUINT16_FROM_LE (resource_dir->NumberOfNamedEntries) + GUINT16_FROM_LE (resource_dir->NumberOfIdEntries);
296         resource_dir_entry = (WapiImageResourceDirectoryEntry *)(resource_dir + 1);
297         
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);
306                 }
307         }
308
309         return(NULL);
310 }
311
312 static gpointer find_pe_file_resources (gpointer file_map, guint32 map_size,
313                                         guint32 res_id, guint32 lang_id,
314                                         guint32 *size)
315 {
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);
323         }
324 }
325
326 static gpointer map_pe_file (gunichar2 *filename, guint32 *map_size)
327 {
328         gchar *filename_ext;
329         int fd;
330         struct stat statbuf;
331         gpointer file_map;
332         
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
336          */
337
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
343
344                 SetLastError (ERROR_INVALID_NAME);
345                 return(NULL);
346         }
347         
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
354
355                 SetLastError (_wapi_get_win32_file_error (errno));
356                 g_free (filename_ext);
357                 
358                 return(NULL);
359         }
360
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
366
367                 SetLastError (_wapi_get_win32_file_error (errno));
368                 g_free (filename_ext);
369                 close (fd);
370                 return(NULL);
371         }
372         *map_size = statbuf.st_size;
373
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
380
381                 SetLastError (ERROR_BAD_LENGTH);
382                 g_free (filename_ext);
383                 close (fd);
384                 return(NULL);
385         }
386         
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
393
394                 SetLastError (_wapi_get_win32_file_error (errno));
395                 g_free (filename_ext);
396                 close (fd);
397                 return(NULL);
398         }
399
400         /* Don't need the fd any more */
401         close (fd);
402         g_free (filename_ext);
403
404         return(file_map);
405 }
406
407 static void unmap_pe_file (gpointer file_map, guint32 map_size)
408 {
409         munmap (file_map, map_size);
410 }
411
412 static guint32 unicode_chars (const gunichar2 *str)
413 {
414         guint32 len = 0;
415         
416         do {
417                 if (str[len] == '\0') {
418                         return(len);
419                 }
420                 len++;
421         } while(1);
422 }
423
424 static gboolean unicode_compare (const gunichar2 *str1, const gunichar2 *str2)
425 {
426         while (*str1 && *str2) {
427                 if (*str1 != *str2) {
428                         return(FALSE);
429                 }
430                 ++str1;
431                 ++str2;
432         }
433         
434         return(*str1 == *str2);
435 }
436
437 /* compare a little-endian null-terminated utf16 string and a normal string.
438  * Can be used only for ascii or latin1 chars.
439  */
440 static gboolean unicode_string_equals (const gunichar2 *str1, const gchar *str2)
441 {
442         while (*str1 && *str2) {
443                 if (GUINT16_TO_LE (*str1) != *str2) {
444                         return(FALSE);
445                 }
446                 ++str1;
447                 ++str2;
448         }
449         
450         return(*str1 == *str2);
451 }
452
453 typedef struct 
454 {
455         guint16 data_len;
456         guint16 value_len;
457         guint16 type;
458         gunichar2 *key;
459 } version_data;
460
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 :-( )
463  */
464 static gconstpointer get_versioninfo_block (gconstpointer data,
465                                             version_data *block)
466 {
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);
471         
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);
476         
477         /* Skip over the key (including the terminator) */
478         data = ((gunichar2 *)data) + (unicode_chars (block->key) + 1);
479         
480         /* align on a 32-bit boundary */
481         ALIGN32 (data);
482         
483         return(data);
484 }
485
486 static gconstpointer get_fixedfileinfo_block (gconstpointer data,
487                                               version_data *block)
488 {
489         gconstpointer data_ptr;
490         gint32 data_len; /* signed to guard against underflow */
491         WapiFixedFileInfo *ffi;
492
493         data_ptr = get_versioninfo_block (data, block);
494         data_len = block->data_len;
495                 
496         if (block->value_len != sizeof(WapiFixedFileInfo)) {
497 #ifdef DEBUG
498                 g_message ("%s: FIXEDFILEINFO size mismatch", __func__);
499 #endif
500                 return(NULL);
501         }
502
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);
508         }
509
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);
517         }
518
519         return(data_ptr);
520 }
521
522 static gconstpointer get_varfileinfo_block (gconstpointer data_ptr,
523                                             version_data *block)
524 {
525         /* data is pointing at a Var block
526          */
527         data_ptr = get_versioninfo_block (data_ptr, block);
528
529         return(data_ptr);
530 }
531
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)
537 {
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;
541
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
545          */
546         while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
547                 /* align on a 32-bit boundary */
548                 ALIGN32 (data_ptr);
549                 
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
554                          */
555 #ifdef DEBUG
556                         g_message ("%s: Hit 0-length block, giving up",
557                                    __func__);
558 #endif
559                         return(NULL);
560                 }
561                 
562                 string_len = string_len + block->data_len;
563                 
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;
570                 }
571                 
572                 /* Skip over the value */
573                 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
574         }
575         
576         return(data_ptr);
577 }
578
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
583  *
584  * If lang == NULL it means we're just stepping through this block
585  */
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)
592 {
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;
597         
598         /* data_ptr is pointing at an array of StringTable blocks,
599          * with total length (not including alignment padding) of
600          * data_len
601          */
602
603         while(string_len < data_len) {
604                 /* align on a 32-bit boundary */
605                 ALIGN32 (data_ptr);
606                 
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
611                          */
612 #ifdef DEBUG
613                         g_message ("%s: Hit 0-length block, giving up",
614                                    __func__);
615 #endif
616                         return(NULL);
617                 }
618                 
619                 string_len = string_len + block->data_len;
620
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);
627                 }
628                 
629                 lowercase_lang = g_utf8_strdown (found_lang, -1);
630                 g_free (found_lang);
631                 found_lang = lowercase_lang;
632                 lowercase_lang = NULL;
633                 
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);
642                 }
643
644                 g_free (found_lang);
645                 
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);
652                 }
653         }
654         
655         return(data_ptr);
656 }
657
658 #if G_BYTE_ORDER == G_BIG_ENDIAN
659 static gconstpointer big_up_string_block (gconstpointer data_ptr,
660                                           version_data *block)
661 {
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;
666         
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
670          */
671         while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
672                 /* align on a 32-bit boundary */
673                 ALIGN32 (data_ptr);
674                 
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
679                          */
680 #ifdef DEBUG
681                         g_message ("%s: Hit 0-length block, giving up",
682                                    __func__);
683 #endif
684                         return(NULL);
685                 }
686                 
687                 string_len = string_len + block->data_len;
688                 
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);
699                 }
700                 
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
705                  */
706                 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
707                 g_free (big_value);
708
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);
718                 }
719                 memcpy ((gpointer)data_ptr, big_value,
720                         unicode_chars (data_ptr) * 2);
721                 g_free (big_value);
722
723                 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
724         }
725         
726         return(data_ptr);
727 }
728
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
733  */
734 static gconstpointer big_up_stringtable_block (gconstpointer data_ptr,
735                                                version_data *block)
736 {
737         guint16 data_len = block->data_len;
738         guint16 string_len = 36; /* length of the StringFileInfo block */
739         gchar *big_value;
740         
741         /* data_ptr is pointing at an array of StringTable blocks,
742          * with total length (not including alignment padding) of
743          * data_len
744          */
745
746         while(string_len < data_len) {
747                 /* align on a 32-bit boundary */
748                 ALIGN32 (data_ptr);
749
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
754                          */
755 #ifdef DEBUG
756                         g_message ("%s: Hit 0-length block, giving up",
757                                    __func__);
758 #endif
759                         return(NULL);
760                 }
761                 
762                 string_len = string_len + block->data_len;
763
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);
772                 }
773                 
774                 memcpy (block->key, big_value, 16);
775                 g_free (big_value);
776                 
777                 data_ptr = big_up_string_block (data_ptr, block);
778                 
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);
785                 }
786         }
787         
788         return(data_ptr);
789 }
790
791 /* Follows the data structures and turns all UTF-16 strings from the
792  * LE found in the resource section into UTF-16BE
793  */
794 static void big_up (gconstpointer datablock, guint32 size)
795 {
796         gconstpointer data_ptr;
797         gint32 data_len; /* signed to guard against underflow */
798         version_data block;
799         
800         data_ptr = get_fixedfileinfo_block (datablock, &block);
801         if (data_ptr != NULL) {
802                 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
803                 
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);
816
817                 /* The FFI and header occupies the first 92 bytes
818                  */
819                 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
820                 data_len = block.data_len - 92;
821                 
822                 /* There now follow zero or one StringFileInfo blocks
823                  * and zero or one VarFileInfo blocks
824                  */
825                 while (data_len > 0) {
826                         /* align on a 32-bit boundary */
827                         ALIGN32 (data_ptr);
828                         
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
833                                  */
834 #ifdef DEBUG
835                                 g_message ("%s: Hit 0-length block, giving up",
836                                            __func__);
837 #endif
838                                 return;
839                         }
840                         
841                         data_len = data_len - block.data_len;
842                         
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;
857                         }
858                         
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;
865                         }
866                 }
867         }
868 }
869 #endif
870
871 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
872                         gpointer *buffer, guint32 *len)
873 {
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;
886         
887         subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
888         if (subblock_utf8 == NULL) {
889                 return(FALSE);
890         }
891
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;
903         }
904         
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;
910                 
911                         ret = TRUE;
912                 }
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
918                          */
919                         data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
920                         data_len = block.data_len - 92;
921                         
922                         /* There now follow zero or one StringFileInfo
923                          * blocks and zero or one VarFileInfo blocks
924                          */
925                         while (data_len > 0) {
926                                 /* align on a 32-bit boundary */
927                                 ALIGN32 (data_ptr);
928                                 
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
934                                          */
935 #ifdef DEBUG
936                                         g_message ("%s: Hit 0-length block, giving up", __func__);
937 #endif
938                                         goto done;
939                                 }
940                                 
941                                 data_len = data_len - block.data_len;
942                                 
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;
953                                         }
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;
963                                         }
964                                 } else {
965                                         /* Bogus data */
966 #ifdef DEBUG
967                                         g_message ("%s: Not a valid VERSIONINFO child block", __func__);
968 #endif
969                                         goto done;
970                                 }
971                                 
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;
978                                 }
979                         }
980                 }
981         }
982
983   done:
984         if (lang_utf8) {
985                 g_free (lang_utf8);
986         }
987         
988         g_free (subblock_utf8);
989         return(ret);
990 }
991
992 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
993 {
994         gpointer file_map;
995         gpointer versioninfo;
996         guint32 map_size;
997         guint32 size;
998         
999         /* This value is unused, but set to zero */
1000         *handle = 0;
1001         
1002         file_map = map_pe_file (filename, &map_size);
1003         if (file_map == NULL) {
1004                 return(0);
1005         }
1006         
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
1012                  */
1013                 size = 0;
1014         }
1015
1016         unmap_pe_file (file_map, map_size);
1017
1018         return(size);
1019 }
1020
1021 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
1022                              guint32 len, gpointer data)
1023 {
1024         gpointer file_map;
1025         gpointer versioninfo;
1026         guint32 map_size;
1027         guint32 size;
1028         gboolean ret = FALSE;
1029         
1030         file_map = map_pe_file (filename, &map_size);
1031         if (file_map == NULL) {
1032                 return(FALSE);
1033         }
1034         
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.
1042                  */
1043                 memcpy (data, versioninfo, len < size?len:size);
1044                 ret = TRUE;
1045
1046 #if G_BYTE_ORDER == G_BIG_ENDIAN
1047                 big_up (data, size);
1048 #endif
1049         }
1050
1051         unmap_pe_file (file_map, map_size);
1052         
1053         return(ret);
1054 }
1055
1056 static guint32 copy_lang (gunichar2 *lang_out, guint32 lang_len,
1057                           const gchar *text)
1058 {
1059         gunichar2 *unitext;
1060         int chars = strlen (text);
1061         int ret;
1062         
1063         unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1064         g_assert (unitext != NULL);
1065         
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;
1074         }
1075         
1076         g_free (unitext);
1077
1078         return(ret);
1079 }
1080
1081 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1082 {
1083         int primary, secondary;
1084         
1085         primary = lang & 0x3FF;
1086         secondary = (lang >> 10) & 0x3F;
1087         
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;
1094                 }
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;
1147                 }
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;
1157                 }
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;
1167                 }
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;
1187                 }
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;
1197                 }
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;
1207                 }
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;
1227                 }
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;
1237                 }
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;
1290                 }
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;
1360                 }
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;
1370                 }
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;
1393                 }
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;
1403                 }
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;
1413                 }
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;
1423                 }
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;
1434                 }
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;
1444                 }
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;
1454                 }
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;
1465                 }
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;
1476                 }
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;
1486                 }
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;
1497                 }
1498                 break;
1499         case 0x17:
1500                 switch(secondary) {
1501                 case 0x01:
1502                         return(copy_lang (lang_out, lang_len, "Romansh (Switzerland)"));
1503                         break;
1504                 }
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;
1514                 }
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;
1524                 }
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;
1555                 }
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;
1565                 }
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;
1575                 }
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;
1588                 }
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;
1598                 }
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;
1608                 }
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;
1618                 }
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;
1628                 }
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;
1638                 }
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;
1648                 }
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;
1658                 }
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;
1668                 }
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;
1678                 }
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;
1688                 }
1689                 break;
1690         case 0x28:
1691                 switch(secondary) {
1692                 case 0x01:
1693                         return(copy_lang (lang_out, lang_len, "Tajik (Tajikistan)"));
1694                         break;
1695                 }
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;
1705                 }
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;
1715                 }
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;
1725                 }
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;
1738                 }
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;
1748                 }
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;
1758                 }
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;
1768                 }
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;
1778                 }
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;
1788                 }
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;
1798                 }
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;
1808                 }
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;
1818                 }
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;
1828                 }
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;
1838                 }
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;
1848                 }
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;
1882                 }
1883                 break;
1884         case 0x3c:
1885                 switch(secondary) {
1886                 case 0x02:
1887                         return(copy_lang (lang_out, lang_len, "Irish (Ireland)"));
1888                         break;
1889                 }
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;
1900                 }
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;
1910                 }
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;
1920                 }
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;
1930                 }
1931                 break;
1932         case 0x42:
1933                 switch(secondary) {
1934                 case 0x01:
1935                         return(copy_lang (lang_out, lang_len, "Turkmen (Turkmenistan)"));
1936                         break;
1937                 }
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;
1950                 }
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;
1960                 }
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;
1968                 }
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;
1978                 }
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;
1988                 }
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;
1998                 }
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;
2008                 }
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;
2018                 }
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;
2026                 }
2027                 break;
2028         case 0x4d:
2029                 switch(secondary) {
2030                 case 0x01:
2031                         return(copy_lang (lang_out, lang_len, "Assamese (India)"));
2032                         break;
2033                 }
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;
2043                 }
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;
2053                 }
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;
2066                 }
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;
2076                 }
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;
2086                 }
2087                 break;
2088         case 0x53:
2089                 switch(secondary) {
2090                 case 0x01:
2091                         return(copy_lang (lang_out, lang_len, "Khmer (Cambodia)"));
2092                         break;
2093                 }
2094                 break;
2095         case 0x54:
2096                 switch(secondary) {
2097                 case 0x01:
2098                         return(copy_lang (lang_out, lang_len, "Lao (Lao PDR)"));
2099                         break;
2100                 }
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;
2110                 }
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;
2120                 }
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;
2130                 }
2131                 break;
2132         case 0x5b:
2133                 switch(secondary) {
2134                 case 0x01:
2135                         return(copy_lang (lang_out, lang_len, "Sinhala (Sri Lanka)"));
2136                         break;
2137                 }
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;
2147                 }
2148                 break;
2149         case 0x5e:
2150                 switch(secondary) {
2151                 case 0x01:
2152                         return(copy_lang (lang_out, lang_len, "Amharic (Ethiopia)"));
2153                         break;
2154                 }
2155                 break;
2156         case 0x5f:
2157                 switch(secondary) {
2158                 case 0x02:
2159                         return(copy_lang (lang_out, lang_len, "Tamazight (Algeria, Latin)"));
2160                         break;
2161                 }
2162                 break;
2163         case 0x61:
2164                 switch(secondary) {
2165                 case 0x01:
2166                         return(copy_lang (lang_out, lang_len, "Nepali (Nepal)"));
2167                         break;
2168                 }
2169                 break;
2170         case 0x62:
2171                 switch(secondary) {
2172                 case 0x01:
2173                         return(copy_lang (lang_out, lang_len, "Frisian (Netherlands)"));
2174                         break;
2175                 }
2176                 break;
2177         case 0x63:
2178                 switch(secondary) {
2179                 case 0x01:
2180                         return(copy_lang (lang_out, lang_len, "Pashto (Afghanistan)"));
2181                         break;
2182                 }
2183                 break;
2184         case 0x64:
2185                 switch(secondary) {
2186                 case 0x01:
2187                         return(copy_lang (lang_out, lang_len, "Filipino (Philippines)"));
2188                         break;
2189                 }
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;
2199                 }
2200                 break;
2201         case 0x68:
2202                 switch(secondary) {
2203                 case 0x01:
2204                         return(copy_lang (lang_out, lang_len, "Hausa (Nigeria, Latin)"));
2205                         break;
2206                 }
2207                 break;
2208         case 0x6a:
2209                 switch(secondary) {
2210                 case 0x01:
2211                         return(copy_lang (lang_out, lang_len, "Yoruba (Nigeria)"));
2212                         break;
2213                 }
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;
2227                 }
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;
2237                 }
2238                 break;
2239         case 0x6d:
2240                 switch(secondary) {
2241                 case 0x01:
2242                         return(copy_lang (lang_out, lang_len, "Bashkir (Russia)"));
2243                         break;
2244                 }
2245                 break;
2246         case 0x6e:
2247                 switch(secondary) {
2248                 case 0x01:
2249                         return(copy_lang (lang_out, lang_len, "Luxembourgish (Luxembourg)"));
2250                         break;
2251                 }
2252                 break;
2253         case 0x6f:
2254                 switch(secondary) {
2255                 case 0x01:
2256                         return(copy_lang (lang_out, lang_len, "Greenlandic (Greenland)"));
2257                         break;
2258                 }
2259                 break;
2260         case 0x78:
2261                 switch(secondary) {
2262                 case 0x01:
2263                         return(copy_lang (lang_out, lang_len, "Yi (PRC)"));
2264                         break;
2265                 }
2266                 break;
2267         case 0x7a:
2268                 switch(secondary) {
2269                 case 0x01:
2270                         return(copy_lang (lang_out, lang_len, "Mapudungun (Chile)"));
2271                         break;
2272                 }
2273                 break;
2274         case 0x7c:
2275                 switch(secondary) {
2276                 case 0x01:
2277                         return(copy_lang (lang_out, lang_len, "Mohawk (Mohawk)"));
2278                         break;
2279                 }
2280                 break;
2281         case 0x7e:
2282                 switch(secondary) {
2283                 case 0x01:
2284                         return(copy_lang (lang_out, lang_len, "Breton (France)"));
2285                         break;
2286                 }
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;
2293                 }
2294                 break;
2295         case 0x80:
2296                 switch(secondary) {
2297                 case 0x01:
2298                         return(copy_lang (lang_out, lang_len, "Uighur (PRC)"));
2299                         break;
2300                 }
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;
2310                 }
2311                 break;
2312         case 0x83:
2313                 switch(secondary) {
2314                 case 0x01:
2315                         return(copy_lang (lang_out, lang_len, "Corsican (France)"));
2316                         break;
2317                 }
2318                 break;
2319         case 0x84:
2320                 switch(secondary) {
2321                 case 0x01:
2322                         return(copy_lang (lang_out, lang_len, "Alsatian (France)"));
2323                         break;
2324                 }
2325                 break;
2326         case 0x85:
2327                 switch(secondary) {
2328                 case 0x01:
2329                         return(copy_lang (lang_out, lang_len, "Yakut (Russia)"));
2330                         break;
2331                 }
2332                 break;
2333         case 0x86:
2334                 switch(secondary) {
2335                 case 0x01:
2336                         return(copy_lang (lang_out, lang_len, "K'iche (Guatemala)"));
2337                         break;
2338                 }
2339                 break;
2340         case 0x87:
2341                 switch(secondary) {
2342                 case 0x01:
2343                         return(copy_lang (lang_out, lang_len, "Kinyarwanda (Rwanda)"));
2344                         break;
2345                 }
2346                 break;
2347         case 0x88:
2348                 switch(secondary) {
2349                 case 0x01:
2350                         return(copy_lang (lang_out, lang_len, "Wolof (Senegal)"));
2351                         break;
2352                 }
2353                 break;
2354         case 0x8c:
2355                 switch(secondary) {
2356                 case 0x01:
2357                         return(copy_lang (lang_out, lang_len, "Dari (Afghanistan)"));
2358                         break;
2359                 }
2360                 break;
2361
2362         default:
2363                 return(copy_lang (lang_out, lang_len, "Language Neutral"));
2364
2365         }
2366         
2367         return(copy_lang (lang_out, lang_len, "Language Neutral"));
2368 }