2008-07-15 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         
597         /* data_ptr is pointing at an array of StringTable blocks,
598          * with total length (not including alignment padding) of
599          * data_len
600          */
601
602         while(string_len < data_len) {
603                 /* align on a 32-bit boundary */
604                 ALIGN32 (data_ptr);
605                 
606                 data_ptr = get_versioninfo_block (data_ptr, block);
607                 if (block->data_len == 0) {
608                         /* We must have hit padding, so give up
609                          * processing now
610                          */
611 #ifdef DEBUG
612                         g_message ("%s: Hit 0-length block, giving up",
613                                    __func__);
614 #endif
615                         return(NULL);
616                 }
617                 
618                 string_len = string_len + block->data_len;
619
620                 found_lang = g_utf16_to_utf8 (block->key, 8, NULL, NULL, NULL);
621                 if (found_lang == NULL) {
622 #ifdef DEBUG
623                         g_message ("%s: Didn't find a valid language key, giving up", __func__);
624 #endif
625                         return(NULL);
626                 }
627                 
628                 g_strdown (found_lang);
629                 
630                 if (lang != NULL && !strcmp (found_lang, lang)) {
631                         /* Got the one we're interested in */
632                         data_ptr = get_string_block (data_ptr, string_key,
633                                                      string_value,
634                                                      string_value_len, block);
635                 } else {
636                         data_ptr = get_string_block (data_ptr, NULL, NULL,
637                                                      NULL, block);
638                 }
639
640                 g_free (found_lang);
641                 
642                 if (data_ptr == NULL) {
643                         /* Child block hit padding */
644 #ifdef DEBUG
645                         g_message ("%s: Child block hit 0-length block, giving up", __func__);
646 #endif
647                         return(NULL);
648                 }
649         }
650         
651         return(data_ptr);
652 }
653
654 #if G_BYTE_ORDER == G_BIG_ENDIAN
655 static gconstpointer big_up_string_block (gconstpointer data_ptr,
656                                           version_data *block)
657 {
658         guint16 data_len = block->data_len;
659         guint16 string_len = 28; /* Length of the StringTable block */
660         gchar *big_value;
661         char *orig_data_ptr = (char *)data_ptr - 28;
662         
663         /* data_ptr is pointing at an array of one or more String
664          * blocks with total length (not including alignment padding)
665          * of data_len
666          */
667         while (((char *)data_ptr - (char *)orig_data_ptr) < data_len) {
668                 /* align on a 32-bit boundary */
669                 ALIGN32 (data_ptr);
670                 
671                 data_ptr = get_versioninfo_block (data_ptr, block);
672                 if (block->data_len == 0) {
673                         /* We must have hit padding, so give up
674                          * processing now
675                          */
676 #ifdef DEBUG
677                         g_message ("%s: Hit 0-length block, giving up",
678                                    __func__);
679 #endif
680                         return(NULL);
681                 }
682                 
683                 string_len = string_len + block->data_len;
684                 
685                 big_value = g_convert ((gchar *)block->key,
686                                        unicode_chars (block->key) * 2,
687                                        "UTF-16BE", "UTF-16LE", NULL, NULL,
688                                        NULL);
689                 if (big_value == NULL) {
690 #ifdef DEBUG
691                         g_message ("%s: Didn't find a valid string, giving up",
692                                    __func__);
693 #endif
694                         return(NULL);
695                 }
696                 
697                 /* The swapped string should be exactly the same
698                  * length as the original little-endian one, but only
699                  * copy the number of original chars just to be on the
700                  * safe side
701                  */
702                 memcpy (block->key, big_value, unicode_chars (block->key) * 2);
703                 g_free (big_value);
704
705                 big_value = g_convert ((gchar *)data_ptr,
706                                        unicode_chars (data_ptr) * 2,
707                                        "UTF-16BE", "UTF-16LE", NULL, NULL,
708                                        NULL);
709                 if (big_value == NULL) {
710 #ifdef DEBUG
711                         g_message ("%s: Didn't find a valid data string, giving up", __func__);
712 #endif
713                         return(NULL);
714                 }
715                 memcpy ((gpointer)data_ptr, big_value,
716                         unicode_chars (data_ptr) * 2);
717                 g_free (big_value);
718
719                 data_ptr = ((gunichar2 *)data_ptr) + block->value_len;
720         }
721         
722         return(data_ptr);
723 }
724
725 /* Returns a pointer to the byte following the Stringtable block, or
726  * NULL if the data read hits padding.  We can't recover from this
727  * because the data length does not include padding bytes, so it's not
728  * possible to just return the start position + length
729  */
730 static gconstpointer big_up_stringtable_block (gconstpointer data_ptr,
731                                                version_data *block)
732 {
733         guint16 data_len = block->data_len;
734         guint16 string_len = 36; /* length of the StringFileInfo block */
735         gchar *big_value;
736         
737         /* data_ptr is pointing at an array of StringTable blocks,
738          * with total length (not including alignment padding) of
739          * data_len
740          */
741
742         while(string_len < data_len) {
743                 /* align on a 32-bit boundary */
744                 ALIGN32 (data_ptr);
745
746                 data_ptr = get_versioninfo_block (data_ptr, block);
747                 if (block->data_len == 0) {
748                         /* We must have hit padding, so give up
749                          * processing now
750                          */
751 #ifdef DEBUG
752                         g_message ("%s: Hit 0-length block, giving up",
753                                    __func__);
754 #endif
755                         return(NULL);
756                 }
757                 
758                 string_len = string_len + block->data_len;
759
760                 big_value = g_convert ((gchar *)block->key, 16, "UTF-16BE",
761                                        "UTF-16LE", NULL, NULL, NULL);
762                 if (big_value == NULL) {
763 #ifdef DEBUG
764                         g_message ("%s: Didn't find a valid string, giving up",
765                                    __func__);
766 #endif
767                         return(NULL);
768                 }
769                 
770                 memcpy (block->key, big_value, 16);
771                 g_free (big_value);
772                 
773                 data_ptr = big_up_string_block (data_ptr, block);
774                 
775                 if (data_ptr == NULL) {
776                         /* Child block hit padding */
777 #ifdef DEBUG
778                         g_message ("%s: Child block hit 0-length block, giving up", __func__);
779 #endif
780                         return(NULL);
781                 }
782         }
783         
784         return(data_ptr);
785 }
786
787 /* Follows the data structures and turns all UTF-16 strings from the
788  * LE found in the resource section into UTF-16BE
789  */
790 static void big_up (gconstpointer datablock, guint32 size)
791 {
792         gconstpointer data_ptr;
793         gint32 data_len; /* signed to guard against underflow */
794         version_data block;
795         
796         data_ptr = get_fixedfileinfo_block (datablock, &block);
797         if (data_ptr != NULL) {
798                 WapiFixedFileInfo *ffi = (WapiFixedFileInfo *)data_ptr;
799                 
800                 /* Byteswap all the fields */
801                 ffi->dwFileVersionMS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionMS);
802                 ffi->dwFileVersionLS = GUINT32_SWAP_LE_BE (ffi->dwFileVersionLS);
803                 ffi->dwProductVersionMS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionMS);
804                 ffi->dwProductVersionLS = GUINT32_SWAP_LE_BE (ffi->dwProductVersionLS);
805                 ffi->dwFileFlagsMask = GUINT32_SWAP_LE_BE (ffi->dwFileFlagsMask);
806                 ffi->dwFileFlags = GUINT32_SWAP_LE_BE (ffi->dwFileFlags);
807                 ffi->dwFileOS = GUINT32_SWAP_LE_BE (ffi->dwFileOS);
808                 ffi->dwFileType = GUINT32_SWAP_LE_BE (ffi->dwFileType);
809                 ffi->dwFileSubtype = GUINT32_SWAP_LE_BE (ffi->dwFileSubtype);
810                 ffi->dwFileDateMS = GUINT32_SWAP_LE_BE (ffi->dwFileDateMS);
811                 ffi->dwFileDateLS = GUINT32_SWAP_LE_BE (ffi->dwFileDateLS);
812
813                 /* The FFI and header occupies the first 92 bytes
814                  */
815                 data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
816                 data_len = block.data_len - 92;
817                 
818                 /* There now follow zero or one StringFileInfo blocks
819                  * and zero or one VarFileInfo blocks
820                  */
821                 while (data_len > 0) {
822                         /* align on a 32-bit boundary */
823                         ALIGN32 (data_ptr);
824                         
825                         data_ptr = get_versioninfo_block (data_ptr, &block);
826                         if (block.data_len == 0) {
827                                 /* We must have hit padding, so give
828                                  * up processing now
829                                  */
830 #ifdef DEBUG
831                                 g_message ("%s: Hit 0-length block, giving up",
832                                            __func__);
833 #endif
834                                 return;
835                         }
836                         
837                         data_len = data_len - block.data_len;
838                         
839                         if (unicode_string_equals (block.key, "VarFileInfo")) {
840                                 data_ptr = get_varfileinfo_block (data_ptr,
841                                                                   &block);
842                                 data_ptr = ((guchar *)data_ptr) + block.value_len;
843                         } else if (unicode_string_equals (block.key,
844                                                           "StringFileInfo")) {
845                                 data_ptr = big_up_stringtable_block (data_ptr,
846                                                                      &block);
847                         } else {
848                                 /* Bogus data */
849 #ifdef DEBUG
850                                 g_message ("%s: Not a valid VERSIONINFO child block", __func__);
851 #endif
852                                 return;
853                         }
854                         
855                         if (data_ptr == NULL) {
856                                 /* Child block hit padding */
857 #ifdef DEBUG
858                                 g_message ("%s: Child block hit 0-length block, giving up", __func__);
859 #endif
860                                 return;
861                         }
862                 }
863         }
864 }
865 #endif
866
867 gboolean VerQueryValue (gconstpointer datablock, const gunichar2 *subblock,
868                         gpointer *buffer, guint32 *len)
869 {
870         gchar *subblock_utf8, *lang_utf8 = NULL;
871         gboolean ret = FALSE;
872         version_data block;
873         gconstpointer data_ptr;
874         gint32 data_len; /* signed to guard against underflow */
875         gboolean want_var = FALSE;
876         gboolean want_string = FALSE;
877         gunichar2 lang[8];
878         const gunichar2 *string_key = NULL;
879         gpointer string_value = NULL;
880         guint32 string_value_len = 0;
881         
882         subblock_utf8 = g_utf16_to_utf8 (subblock, -1, NULL, NULL, NULL);
883         if (subblock_utf8 == NULL) {
884                 return(FALSE);
885         }
886
887         if (!strcmp (subblock_utf8, "\\VarFileInfo\\Translation")) {
888                 want_var = TRUE;
889         } else if (!strncmp (subblock_utf8, "\\StringFileInfo\\", 16)) {
890                 want_string = TRUE;
891                 memcpy (lang, subblock + 16, 8 * sizeof(gunichar2));
892                 lang_utf8 = g_utf16_to_utf8 (lang, 8, NULL, NULL, NULL);
893                 g_strdown (lang_utf8);
894                 string_key = subblock + 25;
895         }
896         
897         if (!strcmp (subblock_utf8, "\\")) {
898                 data_ptr = get_fixedfileinfo_block (datablock, &block);
899                 if (data_ptr != NULL) {
900                         *buffer = (gpointer)data_ptr;
901                         *len = block.value_len;
902                 
903                         ret = TRUE;
904                 }
905         } else if (want_var || want_string) {
906                 data_ptr = get_fixedfileinfo_block (datablock, &block);
907                 if (data_ptr != NULL) {
908                         /* The FFI and header occupies the first 92
909                          * bytes
910                          */
911                         data_ptr = (char *)data_ptr + sizeof(WapiFixedFileInfo);
912                         data_len = block.data_len - 92;
913                         
914                         /* There now follow zero or one StringFileInfo
915                          * blocks and zero or one VarFileInfo blocks
916                          */
917                         while (data_len > 0) {
918                                 /* align on a 32-bit boundary */
919                                 ALIGN32 (data_ptr);
920                                 
921                                 data_ptr = get_versioninfo_block (data_ptr,
922                                                                   &block);
923                                 if (block.data_len == 0) {
924                                         /* We must have hit padding,
925                                          * so give up processing now
926                                          */
927 #ifdef DEBUG
928                                         g_message ("%s: Hit 0-length block, giving up", __func__);
929 #endif
930                                         goto done;
931                                 }
932                                 
933                                 data_len = data_len - block.data_len;
934                                 
935                                 if (unicode_string_equals (block.key, "VarFileInfo")) {
936                                         data_ptr = get_varfileinfo_block (data_ptr, &block);
937                                         if (want_var) {
938                                                 *buffer = (gpointer)data_ptr;
939                                                 *len = block.value_len;
940                                                 ret = TRUE;
941                                                 goto done;
942                                         } else {
943                                                 /* Skip over the Var block */
944                                                 data_ptr = ((guchar *)data_ptr) + block.value_len;
945                                         }
946                                 } else if (unicode_string_equals (block.key, "StringFileInfo")) {
947                                         data_ptr = get_stringtable_block (data_ptr, lang_utf8, string_key, &string_value, &string_value_len, &block);
948                                         if (want_string &&
949                                             string_value != NULL &&
950                                             string_value_len != 0) {
951                                                 *buffer = string_value;
952                                                 *len = unicode_chars (string_value) + 1; /* Include trailing null */
953                                                 ret = TRUE;
954                                                 goto done;
955                                         }
956                                 } else {
957                                         /* Bogus data */
958 #ifdef DEBUG
959                                         g_message ("%s: Not a valid VERSIONINFO child block", __func__);
960 #endif
961                                         goto done;
962                                 }
963                                 
964                                 if (data_ptr == NULL) {
965                                         /* Child block hit padding */
966 #ifdef DEBUG
967                                         g_message ("%s: Child block hit 0-length block, giving up", __func__);
968 #endif
969                                         goto done;
970                                 }
971                         }
972                 }
973         }
974
975   done:
976         if (lang_utf8) {
977                 g_free (lang_utf8);
978         }
979         
980         g_free (subblock_utf8);
981         return(ret);
982 }
983
984 guint32 GetFileVersionInfoSize (gunichar2 *filename, guint32 *handle)
985 {
986         gpointer file_map;
987         gpointer versioninfo;
988         guint32 map_size;
989         guint32 size;
990         
991         /* This value is unused, but set to zero */
992         *handle = 0;
993         
994         file_map = map_pe_file (filename, &map_size);
995         if (file_map == NULL) {
996                 return(0);
997         }
998         
999         versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1000                                               0, &size);
1001         if (versioninfo == NULL) {
1002                 /* Didn't find the resource, so set the return value
1003                  * to 0
1004                  */
1005                 size = 0;
1006         }
1007
1008         unmap_pe_file (file_map, map_size);
1009
1010         return(size);
1011 }
1012
1013 gboolean GetFileVersionInfo (gunichar2 *filename, guint32 handle G_GNUC_UNUSED,
1014                              guint32 len, gpointer data)
1015 {
1016         gpointer file_map;
1017         gpointer versioninfo;
1018         guint32 map_size;
1019         guint32 size;
1020         gboolean ret = FALSE;
1021         
1022         file_map = map_pe_file (filename, &map_size);
1023         if (file_map == NULL) {
1024                 return(FALSE);
1025         }
1026         
1027         versioninfo = find_pe_file_resources (file_map, map_size, RT_VERSION,
1028                                               0, &size);
1029         if (versioninfo != NULL) {
1030                 /* This could probably process the data so that
1031                  * VerQueryValue() doesn't have to follow the data
1032                  * blocks every time.  But hey, these functions aren't
1033                  * likely to appear in many profiles.
1034                  */
1035                 memcpy (data, versioninfo, len < size?len:size);
1036                 ret = TRUE;
1037
1038 #if G_BYTE_ORDER == G_BIG_ENDIAN
1039                 big_up (data, size);
1040 #endif
1041         }
1042
1043         unmap_pe_file (file_map, map_size);
1044         
1045         return(ret);
1046 }
1047
1048 static guint32 copy_lang (gunichar2 *lang_out, guint32 lang_len,
1049                           const gchar *text)
1050 {
1051         gunichar2 *unitext;
1052         int chars = strlen (text);
1053         int ret;
1054         
1055         unitext = g_utf8_to_utf16 (text, -1, NULL, NULL, NULL);
1056         g_assert (unitext != NULL);
1057         
1058         if (chars < (lang_len - 1)) {
1059                 memcpy (lang_out, (gpointer)unitext, chars * 2);
1060                 lang_out[chars] = '\0';
1061                 ret = chars;
1062         } else {
1063                 memcpy (lang_out, (gpointer)unitext, (lang_len - 1) * 2);
1064                 lang_out[lang_len] = '\0';
1065                 ret = lang_len;
1066         }
1067         
1068         g_free (unitext);
1069
1070         return(ret);
1071 }
1072
1073 guint32 VerLanguageName (guint32 lang, gunichar2 *lang_out, guint32 lang_len)
1074 {
1075         int primary, secondary;
1076         
1077         primary = lang & 0x3FF;
1078         secondary = (lang >> 10) & 0x3F;
1079         
1080         switch(primary) {
1081         case 0x00:
1082                 switch(secondary) {
1083                 case 0x01:
1084                         return(copy_lang (lang_out, lang_len, "Process Default Language"));
1085                         break;
1086                 }
1087                 break;
1088         case 0x01:
1089                 switch(secondary) {
1090                 case 0x00:
1091                 case 0x01:
1092                         return(copy_lang (lang_out, lang_len, "Arabic (Saudi Arabia)"));
1093                         break;
1094                 case 0x02:
1095                         return(copy_lang (lang_out, lang_len, "Arabic (Iraq)"));
1096                         break;
1097                 case 0x03:
1098                         return(copy_lang (lang_out, lang_len, "Arabic (Egypt)"));
1099                         break;
1100                 case 0x04:
1101                         return(copy_lang (lang_out, lang_len, "Arabic (Libya)"));
1102                         break;
1103                 case 0x05:
1104                         return(copy_lang (lang_out, lang_len, "Arabic (Algeria)"));
1105                         break;
1106                 case 0x06:
1107                         return(copy_lang (lang_out, lang_len, "Arabic (Morocco)"));
1108                         break;
1109                 case 0x07:
1110                         return(copy_lang (lang_out, lang_len, "Arabic (Tunisia)"));
1111                         break;
1112                 case 0x08:
1113                         return(copy_lang (lang_out, lang_len, "Arabic (Oman)"));
1114                         break;
1115                 case 0x09:
1116                         return(copy_lang (lang_out, lang_len, "Arabic (Yemen)"));
1117                         break;
1118                 case 0x0a:
1119                         return(copy_lang (lang_out, lang_len, "Arabic (Syria)"));
1120                         break;
1121                 case 0x0b:
1122                         return(copy_lang (lang_out, lang_len, "Arabic (Jordan)"));
1123                         break;
1124                 case 0x0c:
1125                         return(copy_lang (lang_out, lang_len, "Arabic (Lebanon)"));
1126                         break;
1127                 case 0x0d:
1128                         return(copy_lang (lang_out, lang_len, "Arabic (Kuwait)"));
1129                         break;
1130                 case 0x0e:
1131                         return(copy_lang (lang_out, lang_len, "Arabic (U.A.E.)"));
1132                         break;
1133                 case 0x0f:
1134                         return(copy_lang (lang_out, lang_len, "Arabic (Bahrain)"));
1135                         break;
1136                 case 0x10:
1137                         return(copy_lang (lang_out, lang_len, "Arabic (Qatar)"));
1138                         break;
1139                 }
1140                 break;
1141         case 0x02:
1142                 switch(secondary) {
1143                 case 0x00:
1144                         return(copy_lang (lang_out, lang_len, "Bulgarian (Bulgaria)"));
1145                         break;
1146                 case 0x01:
1147                         return(copy_lang (lang_out, lang_len, "Bulgarian"));
1148                         break;
1149                 }
1150                 break;
1151         case 0x03:
1152                 switch(secondary) {
1153                 case 0x00:
1154                         return(copy_lang (lang_out, lang_len, "Catalan (Spain)"));
1155                         break;
1156                 case 0x01:
1157                         return(copy_lang (lang_out, lang_len, "Catalan"));
1158                         break;
1159                 }
1160                 break;
1161         case 0x04:
1162                 switch(secondary) {
1163                 case 0x00:
1164                 case 0x01:
1165                         return(copy_lang (lang_out, lang_len, "Chinese (Taiwan)"));
1166                         break;
1167                 case 0x02:
1168                         return(copy_lang (lang_out, lang_len, "Chinese (PRC)"));
1169                         break;
1170                 case 0x03:
1171                         return(copy_lang (lang_out, lang_len, "Chinese (Hong Kong S.A.R.)"));
1172                         break;
1173                 case 0x04:
1174                         return(copy_lang (lang_out, lang_len, "Chinese (Singapore)"));
1175                         break;
1176                 case 0x05:
1177                         return(copy_lang (lang_out, lang_len, "Chinese (Macau S.A.R.)"));
1178                         break;
1179                 }
1180                 break;
1181         case 0x05:
1182                 switch(secondary) {
1183                 case 0x00:
1184                         return(copy_lang (lang_out, lang_len, "Czech (Czech Republic)"));
1185                         break;
1186                 case 0x01:
1187                         return(copy_lang (lang_out, lang_len, "Czech"));
1188                         break;
1189                 }
1190                 break;
1191         case 0x06:
1192                 switch(secondary) {
1193                 case 0x00:
1194                         return(copy_lang (lang_out, lang_len, "Danish (Denmark)"));
1195                         break;
1196                 case 0x01:
1197                         return(copy_lang (lang_out, lang_len, "Danish"));
1198                         break;
1199                 }
1200                 break;
1201         case 0x07:
1202                 switch(secondary) {
1203                 case 0x00:
1204                 case 0x01:
1205                         return(copy_lang (lang_out, lang_len, "German (Germany)"));
1206                         break;
1207                 case 0x02:
1208                         return(copy_lang (lang_out, lang_len, "German (Switzerland)"));
1209                         break;
1210                 case 0x03:
1211                         return(copy_lang (lang_out, lang_len, "German (Austria)"));
1212                         break;
1213                 case 0x04:
1214                         return(copy_lang (lang_out, lang_len, "German (Luxembourg)"));
1215                         break;
1216                 case 0x05:
1217                         return(copy_lang (lang_out, lang_len, "German (Liechtenstein)"));
1218                         break;
1219                 }
1220                 break;
1221         case 0x08:
1222                 switch(secondary) {
1223                 case 0x00:
1224                         return(copy_lang (lang_out, lang_len, "Greek (Greece)"));
1225                         break;
1226                 case 0x01:
1227                         return(copy_lang (lang_out, lang_len, "Greek"));
1228                         break;
1229                 }
1230                 break;
1231         case 0x09:
1232                 switch(secondary) {
1233                 case 0x00:
1234                 case 0x01:
1235                         return(copy_lang (lang_out, lang_len, "English (United States)"));
1236                         break;
1237                 case 0x02:
1238                         return(copy_lang (lang_out, lang_len, "English (United Kingdom)"));
1239                         break;
1240                 case 0x03:
1241                         return(copy_lang (lang_out, lang_len, "English (Australia)"));
1242                         break;
1243                 case 0x04:
1244                         return(copy_lang (lang_out, lang_len, "English (Canada)"));
1245                         break;
1246                 case 0x05:
1247                         return(copy_lang (lang_out, lang_len, "English (New Zealand)"));
1248                         break;
1249                 case 0x06:
1250                         return(copy_lang (lang_out, lang_len, "English (Ireland)"));
1251                         break;
1252                 case 0x07:
1253                         return(copy_lang (lang_out, lang_len, "English (South Africa)"));
1254                         break;
1255                 case 0x08:
1256                         return(copy_lang (lang_out, lang_len, "English (Jamaica)"));
1257                         break;
1258                 case 0x09:
1259                         return(copy_lang (lang_out, lang_len, "English (Caribbean)"));
1260                         break;
1261                 case 0x0a:
1262                         return(copy_lang (lang_out, lang_len, "English (Belize)"));
1263                         break;
1264                 case 0x0b:
1265                         return(copy_lang (lang_out, lang_len, "English (Trinidad and Tobago)"));
1266                         break;
1267                 case 0x0c:
1268                         return(copy_lang (lang_out, lang_len, "English (Zimbabwe)"));
1269                         break;
1270                 case 0x0d:
1271                         return(copy_lang (lang_out, lang_len, "English (Philippines)"));
1272                         break;
1273                 case 0x10:
1274                         return(copy_lang (lang_out, lang_len, "English (India)"));
1275                         break;
1276                 case 0x11:
1277                         return(copy_lang (lang_out, lang_len, "English (Malaysia)"));
1278                         break;
1279                 case 0x12:
1280                         return(copy_lang (lang_out, lang_len, "English (Singapore)"));
1281                         break;
1282                 }
1283                 break;
1284         case 0x0a:
1285                 switch(secondary) {
1286                 case 0x00:
1287                         return(copy_lang (lang_out, lang_len, "Spanish (Spain)"));
1288                         break;
1289                 case 0x01:
1290                         return(copy_lang (lang_out, lang_len, "Spanish (Traditional Sort)"));
1291                         break;
1292                 case 0x02:
1293                         return(copy_lang (lang_out, lang_len, "Spanish (Mexico)"));
1294                         break;
1295                 case 0x03:
1296                         return(copy_lang (lang_out, lang_len, "Spanish (International Sort)"));
1297                         break;
1298                 case 0x04:
1299                         return(copy_lang (lang_out, lang_len, "Spanish (Guatemala)"));
1300                         break;
1301                 case 0x05:
1302                         return(copy_lang (lang_out, lang_len, "Spanish (Costa Rica)"));
1303                         break;
1304                 case 0x06:
1305                         return(copy_lang (lang_out, lang_len, "Spanish (Panama)"));
1306                         break;
1307                 case 0x07:
1308                         return(copy_lang (lang_out, lang_len, "Spanish (Dominican Republic)"));
1309                         break;
1310                 case 0x08:
1311                         return(copy_lang (lang_out, lang_len, "Spanish (Venezuela)"));
1312                         break;
1313                 case 0x09:
1314                         return(copy_lang (lang_out, lang_len, "Spanish (Colombia)"));
1315                         break;
1316                 case 0x0a:
1317                         return(copy_lang (lang_out, lang_len, "Spanish (Peru)"));
1318                         break;
1319                 case 0x0b:
1320                         return(copy_lang (lang_out, lang_len, "Spanish (Argentina)"));
1321                         break;
1322                 case 0x0c:
1323                         return(copy_lang (lang_out, lang_len, "Spanish (Ecuador)"));
1324                         break;
1325                 case 0x0d:
1326                         return(copy_lang (lang_out, lang_len, "Spanish (Chile)"));
1327                         break;
1328                 case 0x0e:
1329                         return(copy_lang (lang_out, lang_len, "Spanish (Uruguay)"));
1330                         break;
1331                 case 0x0f:
1332                         return(copy_lang (lang_out, lang_len, "Spanish (Paraguay)"));
1333                         break;
1334                 case 0x10:
1335                         return(copy_lang (lang_out, lang_len, "Spanish (Bolivia)"));
1336                         break;
1337                 case 0x11:
1338                         return(copy_lang (lang_out, lang_len, "Spanish (El Salvador)"));
1339                         break;
1340                 case 0x12:
1341                         return(copy_lang (lang_out, lang_len, "Spanish (Honduras)"));
1342                         break;
1343                 case 0x13:
1344                         return(copy_lang (lang_out, lang_len, "Spanish (Nicaragua)"));
1345                         break;
1346                 case 0x14:
1347                         return(copy_lang (lang_out, lang_len, "Spanish (Puerto Rico)"));
1348                         break;
1349                 case 0x15:
1350                         return(copy_lang (lang_out, lang_len, "Spanish (United States)"));
1351                         break;
1352                 }
1353                 break;
1354         case 0x0b:
1355                 switch(secondary) {
1356                 case 0x00:
1357                         return(copy_lang (lang_out, lang_len, "Finnish (Finland)"));
1358                         break;
1359                 case 0x01:
1360                         return(copy_lang (lang_out, lang_len, "Finnish"));
1361                         break;
1362                 }
1363                 break;
1364         case 0x0c:
1365                 switch(secondary) {
1366                 case 0x00:
1367                 case 0x01:
1368                         return(copy_lang (lang_out, lang_len, "French (France)"));
1369                         break;
1370                 case 0x02:
1371                         return(copy_lang (lang_out, lang_len, "French (Belgium)"));
1372                         break;
1373                 case 0x03:
1374                         return(copy_lang (lang_out, lang_len, "French (Canada)"));
1375                         break;
1376                 case 0x04:
1377                         return(copy_lang (lang_out, lang_len, "French (Switzerland)"));
1378                         break;
1379                 case 0x05:
1380                         return(copy_lang (lang_out, lang_len, "French (Luxembourg)"));
1381                         break;
1382                 case 0x06:
1383                         return(copy_lang (lang_out, lang_len, "French (Monaco)"));
1384                         break;
1385                 }
1386                 break;
1387         case 0x0d:
1388                 switch(secondary) {
1389                 case 0x00:
1390                         return(copy_lang (lang_out, lang_len, "Hebrew (Israel)"));
1391                         break;
1392                 case 0x01:
1393                         return(copy_lang (lang_out, lang_len, "Hebrew"));
1394                         break;
1395                 }
1396                 break;
1397         case 0x0e:
1398                 switch(secondary) {
1399                 case 0x00:
1400                         return(copy_lang (lang_out, lang_len, "Hungarian (Hungary)"));
1401                         break;
1402                 case 0x01:
1403                         return(copy_lang (lang_out, lang_len, "Hungarian"));
1404                         break;
1405                 }
1406                 break;
1407         case 0x0f:
1408                 switch(secondary) {
1409                 case 0x00:
1410                         return(copy_lang (lang_out, lang_len, "Icelandic (Iceland)"));
1411                         break;
1412                 case 0x01:
1413                         return(copy_lang (lang_out, lang_len, "Icelandic"));
1414                         break;
1415                 }
1416                 break;
1417         case 0x10:
1418                 switch(secondary) {
1419                 case 0x00:
1420                 case 0x01:
1421                         return(copy_lang (lang_out, lang_len, "Italian (Italy)"));
1422                         break;
1423                 case 0x02:
1424                         return(copy_lang (lang_out, lang_len, "Italian (Switzerland)"));
1425                         break;
1426                 }
1427                 break;
1428         case 0x11:
1429                 switch(secondary) {
1430                 case 0x00:
1431                         return(copy_lang (lang_out, lang_len, "Japanese (Japan)"));
1432                         break;
1433                 case 0x01:
1434                         return(copy_lang (lang_out, lang_len, "Japanese"));
1435                         break;
1436                 }
1437                 break;
1438         case 0x12:
1439                 switch(secondary) {
1440                 case 0x00:
1441                         return(copy_lang (lang_out, lang_len, "Korean (Korea)"));
1442                         break;
1443                 case 0x01:
1444                         return(copy_lang (lang_out, lang_len, "Korean"));
1445                         break;
1446                 }
1447                 break;
1448         case 0x13:
1449                 switch(secondary) {
1450                 case 0x00:
1451                 case 0x01:
1452                         return(copy_lang (lang_out, lang_len, "Dutch (Netherlands)"));
1453                         break;
1454                 case 0x02:
1455                         return(copy_lang (lang_out, lang_len, "Dutch (Belgium)"));
1456                         break;
1457                 }
1458                 break;
1459         case 0x14:
1460                 switch(secondary) {
1461                 case 0x00:
1462                 case 0x01:
1463                         return(copy_lang (lang_out, lang_len, "Norwegian (Bokmal)"));
1464                         break;
1465                 case 0x02:
1466                         return(copy_lang (lang_out, lang_len, "Norwegian (Nynorsk)"));
1467                         break;
1468                 }
1469                 break;
1470         case 0x15:
1471                 switch(secondary) {
1472                 case 0x00:
1473                         return(copy_lang (lang_out, lang_len, "Polish (Poland)"));
1474                         break;
1475                 case 0x01:
1476                         return(copy_lang (lang_out, lang_len, "Polish"));
1477                         break;
1478                 }
1479                 break;
1480         case 0x16:
1481                 switch(secondary) {
1482                 case 0x00:
1483                 case 0x01:
1484                         return(copy_lang (lang_out, lang_len, "Portuguese (Brazil)"));
1485                         break;
1486                 case 0x02:
1487                         return(copy_lang (lang_out, lang_len, "Portuguese (Portugal)"));
1488                         break;
1489                 }
1490                 break;
1491         case 0x17:
1492                 switch(secondary) {
1493                 case 0x01:
1494                         return(copy_lang (lang_out, lang_len, "Romansh (Switzerland)"));
1495                         break;
1496                 }
1497                 break;
1498         case 0x18:
1499                 switch(secondary) {
1500                 case 0x00:
1501                         return(copy_lang (lang_out, lang_len, "Romanian (Romania)"));
1502                         break;
1503                 case 0x01:
1504                         return(copy_lang (lang_out, lang_len, "Romanian"));
1505                         break;
1506                 }
1507                 break;
1508         case 0x19:
1509                 switch(secondary) {
1510                 case 0x00:
1511                         return(copy_lang (lang_out, lang_len, "Russian (Russia)"));
1512                         break;
1513                 case 0x01:
1514                         return(copy_lang (lang_out, lang_len, "Russian"));
1515                         break;
1516                 }
1517                 break;
1518         case 0x1a:
1519                 switch(secondary) {
1520                 case 0x00:
1521                         return(copy_lang (lang_out, lang_len, "Croatian (Croatia)"));
1522                         break;
1523                 case 0x01:
1524                         return(copy_lang (lang_out, lang_len, "Croatian"));
1525                         break;
1526                 case 0x02:
1527                         return(copy_lang (lang_out, lang_len, "Serbian (Latin)"));
1528                         break;
1529                 case 0x03:
1530                         return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic)"));
1531                         break;
1532                 case 0x04:
1533                         return(copy_lang (lang_out, lang_len, "Croatian (Bosnia and Herzegovina)"));
1534                         break;
1535                 case 0x05:
1536                         return(copy_lang (lang_out, lang_len, "Bosnian (Latin, Bosnia and Herzegovina)"));
1537                         break;
1538                 case 0x06:
1539                         return(copy_lang (lang_out, lang_len, "Serbian (Latin, Bosnia and Herzegovina)"));
1540                         break;
1541                 case 0x07:
1542                         return(copy_lang (lang_out, lang_len, "Serbian (Cyrillic, Bosnia and Herzegovina)"));
1543                         break;
1544                 case 0x08:
1545                         return(copy_lang (lang_out, lang_len, "Bosnian (Cyrillic, Bosnia and Herzegovina)"));
1546                         break;
1547                 }
1548                 break;
1549         case 0x1b:
1550                 switch(secondary) {
1551                 case 0x00:
1552                         return(copy_lang (lang_out, lang_len, "Slovak (Slovakia)"));
1553                         break;
1554                 case 0x01:
1555                         return(copy_lang (lang_out, lang_len, "Slovak"));
1556                         break;
1557                 }
1558                 break;
1559         case 0x1c:
1560                 switch(secondary) {
1561                 case 0x00:
1562                         return(copy_lang (lang_out, lang_len, "Albanian (Albania)"));
1563                         break;
1564                 case 0x01:
1565                         return(copy_lang (lang_out, lang_len, "Albanian"));
1566                         break;
1567                 }
1568                 break;
1569         case 0x1d:
1570                 switch(secondary) {
1571                 case 0x00:
1572                         return(copy_lang (lang_out, lang_len, "Swedish (Sweden)"));
1573                         break;
1574                 case 0x01:
1575                         return(copy_lang (lang_out, lang_len, "Swedish"));
1576                         break;
1577                 case 0x02:
1578                         return(copy_lang (lang_out, lang_len, "Swedish (Finland)"));
1579                         break;
1580                 }
1581                 break;
1582         case 0x1e:
1583                 switch(secondary) {
1584                 case 0x00:
1585                         return(copy_lang (lang_out, lang_len, "Thai (Thailand)"));
1586                         break;
1587                 case 0x01:
1588                         return(copy_lang (lang_out, lang_len, "Thai"));
1589                         break;
1590                 }
1591                 break;
1592         case 0x1f:
1593                 switch(secondary) {
1594                 case 0x00:
1595                         return(copy_lang (lang_out, lang_len, "Turkish (Turkey)"));
1596                         break;
1597                 case 0x01:
1598                         return(copy_lang (lang_out, lang_len, "Turkish"));
1599                         break;
1600                 }
1601                 break;
1602         case 0x20:
1603                 switch(secondary) {
1604                 case 0x00:
1605                         return(copy_lang (lang_out, lang_len, "Urdu (Islamic Republic of Pakistan)"));
1606                         break;
1607                 case 0x01:
1608                         return(copy_lang (lang_out, lang_len, "Urdu"));
1609                         break;
1610                 }
1611                 break;
1612         case 0x21:
1613                 switch(secondary) {
1614                 case 0x00:
1615                         return(copy_lang (lang_out, lang_len, "Indonesian (Indonesia)"));
1616                         break;
1617                 case 0x01:
1618                         return(copy_lang (lang_out, lang_len, "Indonesian"));
1619                         break;
1620                 }
1621                 break;
1622         case 0x22:
1623                 switch(secondary) {
1624                 case 0x00:
1625                         return(copy_lang (lang_out, lang_len, "Ukrainian (Ukraine)"));
1626                         break;
1627                 case 0x01:
1628                         return(copy_lang (lang_out, lang_len, "Ukrainian"));
1629                         break;
1630                 }
1631                 break;
1632         case 0x23:
1633                 switch(secondary) {
1634                 case 0x00:
1635                         return(copy_lang (lang_out, lang_len, "Belarusian (Belarus)"));
1636                         break;
1637                 case 0x01:
1638                         return(copy_lang (lang_out, lang_len, "Belarusian"));
1639                         break;
1640                 }
1641                 break;
1642         case 0x24:
1643                 switch(secondary) {
1644                 case 0x00:
1645                         return(copy_lang (lang_out, lang_len, "Slovenian (Slovenia)"));
1646                         break;
1647                 case 0x01:
1648                         return(copy_lang (lang_out, lang_len, "Slovenian"));
1649                         break;
1650                 }
1651                 break;
1652         case 0x25:
1653                 switch(secondary) {
1654                 case 0x00:
1655                         return(copy_lang (lang_out, lang_len, "Estonian (Estonia)"));
1656                         break;
1657                 case 0x01:
1658                         return(copy_lang (lang_out, lang_len, "Estonian"));
1659                         break;
1660                 }
1661                 break;
1662         case 0x26:
1663                 switch(secondary) {
1664                 case 0x00:
1665                         return(copy_lang (lang_out, lang_len, "Latvian (Latvia)"));
1666                         break;
1667                 case 0x01:
1668                         return(copy_lang (lang_out, lang_len, "Latvian"));
1669                         break;
1670                 }
1671                 break;
1672         case 0x27:
1673                 switch(secondary) {
1674                 case 0x00:
1675                         return(copy_lang (lang_out, lang_len, "Lithuanian (Lithuania)"));
1676                         break;
1677                 case 0x01:
1678                         return(copy_lang (lang_out, lang_len, "Lithuanian"));
1679                         break;
1680                 }
1681                 break;
1682         case 0x28:
1683                 switch(secondary) {
1684                 case 0x01:
1685                         return(copy_lang (lang_out, lang_len, "Tajik (Tajikistan)"));
1686                         break;
1687                 }
1688                 break;
1689         case 0x29:
1690                 switch(secondary) {
1691                 case 0x00:
1692                         return(copy_lang (lang_out, lang_len, "Farsi (Iran)"));
1693                         break;
1694                 case 0x01:
1695                         return(copy_lang (lang_out, lang_len, "Farsi"));
1696                         break;
1697                 }
1698                 break;
1699         case 0x2a:
1700                 switch(secondary) {
1701                 case 0x00:
1702                         return(copy_lang (lang_out, lang_len, "Vietnamese (Viet Nam)"));
1703                         break;
1704                 case 0x01:
1705                         return(copy_lang (lang_out, lang_len, "Vietnamese"));
1706                         break;
1707                 }
1708                 break;
1709         case 0x2b:
1710                 switch(secondary) {
1711                 case 0x00:
1712                         return(copy_lang (lang_out, lang_len, "Armenian (Armenia)"));
1713                         break;
1714                 case 0x01:
1715                         return(copy_lang (lang_out, lang_len, "Armenian"));
1716                         break;
1717                 }
1718                 break;
1719         case 0x2c:
1720                 switch(secondary) {
1721                 case 0x00:
1722                         return(copy_lang (lang_out, lang_len, "Azeri (Latin) (Azerbaijan)"));
1723                         break;
1724                 case 0x01:
1725                         return(copy_lang (lang_out, lang_len, "Azeri (Latin)"));
1726                         break;
1727                 case 0x02:
1728                         return(copy_lang (lang_out, lang_len, "Azeri (Cyrillic)"));
1729                         break;
1730                 }
1731                 break;
1732         case 0x2d:
1733                 switch(secondary) {
1734                 case 0x00:
1735                         return(copy_lang (lang_out, lang_len, "Basque (Spain)"));
1736                         break;
1737                 case 0x01:
1738                         return(copy_lang (lang_out, lang_len, "Basque"));
1739                         break;
1740                 }
1741                 break;
1742         case 0x2e:
1743                 switch(secondary) {
1744                 case 0x01:
1745                         return(copy_lang (lang_out, lang_len, "Upper Sorbian (Germany)"));
1746                         break;
1747                 case 0x02:
1748                         return(copy_lang (lang_out, lang_len, "Lower Sorbian (Germany)"));
1749                         break;
1750                 }
1751                 break;
1752         case 0x2f:
1753                 switch(secondary) {
1754                 case 0x00:
1755                         return(copy_lang (lang_out, lang_len, "FYRO Macedonian (Former Yugoslav Republic of Macedonia)"));
1756                         break;
1757                 case 0x01:
1758                         return(copy_lang (lang_out, lang_len, "FYRO Macedonian"));
1759                         break;
1760                 }
1761                 break;
1762         case 0x32:
1763                 switch(secondary) {
1764                 case 0x00:
1765                         return(copy_lang (lang_out, lang_len, "Tswana (South Africa)"));
1766                         break;
1767                 case 0x01:
1768                         return(copy_lang (lang_out, lang_len, "Tswana"));
1769                         break;
1770                 }
1771                 break;
1772         case 0x34:
1773                 switch(secondary) {
1774                 case 0x00:
1775                         return(copy_lang (lang_out, lang_len, "Xhosa (South Africa)"));
1776                         break;
1777                 case 0x01:
1778                         return(copy_lang (lang_out, lang_len, "Xhosa"));
1779                         break;
1780                 }
1781                 break;
1782         case 0x35:
1783                 switch(secondary) {
1784                 case 0x00:
1785                         return(copy_lang (lang_out, lang_len, "Zulu (South Africa)"));
1786                         break;
1787                 case 0x01:
1788                         return(copy_lang (lang_out, lang_len, "Zulu"));
1789                         break;
1790                 }
1791                 break;
1792         case 0x36:
1793                 switch(secondary) {
1794                 case 0x00:
1795                         return(copy_lang (lang_out, lang_len, "Afrikaans (South Africa)"));
1796                         break;
1797                 case 0x01:
1798                         return(copy_lang (lang_out, lang_len, "Afrikaans"));
1799                         break;
1800                 }
1801                 break;
1802         case 0x37:
1803                 switch(secondary) {
1804                 case 0x00:
1805                         return(copy_lang (lang_out, lang_len, "Georgian (Georgia)"));
1806                         break;
1807                 case 0x01:
1808                         return(copy_lang (lang_out, lang_len, "Georgian"));
1809                         break;
1810                 }
1811                 break;
1812         case 0x38:
1813                 switch(secondary) {
1814                 case 0x00:
1815                         return(copy_lang (lang_out, lang_len, "Faroese (Faroe Islands)"));
1816                         break;
1817                 case 0x01:
1818                         return(copy_lang (lang_out, lang_len, "Faroese"));
1819                         break;
1820                 }
1821                 break;
1822         case 0x39:
1823                 switch(secondary) {
1824                 case 0x00:
1825                         return(copy_lang (lang_out, lang_len, "Hindi (India)"));
1826                         break;
1827                 case 0x01:
1828                         return(copy_lang (lang_out, lang_len, "Hindi"));
1829                         break;
1830                 }
1831                 break;
1832         case 0x3a:
1833                 switch(secondary) {
1834                 case 0x00:
1835                         return(copy_lang (lang_out, lang_len, "Maltese (Malta)"));
1836                         break;
1837                 case 0x01:
1838                         return(copy_lang (lang_out, lang_len, "Maltese"));
1839                         break;
1840                 }
1841                 break;
1842         case 0x3b:
1843                 switch(secondary) {
1844                 case 0x00:
1845                         return(copy_lang (lang_out, lang_len, "Sami (Northern) (Norway)"));
1846                         break;
1847                 case 0x01:
1848                         return(copy_lang (lang_out, lang_len, "Sami, Northern (Norway)"));
1849                         break;
1850                 case 0x02:
1851                         return(copy_lang (lang_out, lang_len, "Sami, Northern (Sweden)"));
1852                         break;
1853                 case 0x03:
1854                         return(copy_lang (lang_out, lang_len, "Sami, Northern (Finland)"));
1855                         break;
1856                 case 0x04:
1857                         return(copy_lang (lang_out, lang_len, "Sami, Lule (Norway)"));
1858                         break;
1859                 case 0x05:
1860                         return(copy_lang (lang_out, lang_len, "Sami, Lule (Sweden)"));
1861                         break;
1862                 case 0x06:
1863                         return(copy_lang (lang_out, lang_len, "Sami, Southern (Norway)"));
1864                         break;
1865                 case 0x07:
1866                         return(copy_lang (lang_out, lang_len, "Sami, Southern (Sweden)"));
1867                         break;
1868                 case 0x08:
1869                         return(copy_lang (lang_out, lang_len, "Sami, Skolt (Finland)"));
1870                         break;
1871                 case 0x09:
1872                         return(copy_lang (lang_out, lang_len, "Sami, Inari (Finland)"));
1873                         break;
1874                 }
1875                 break;
1876         case 0x3c:
1877                 switch(secondary) {
1878                 case 0x02:
1879                         return(copy_lang (lang_out, lang_len, "Irish (Ireland)"));
1880                         break;
1881                 }
1882                 break;
1883         case 0x3e:
1884                 switch(secondary) {
1885                 case 0x00:
1886                 case 0x01:
1887                         return(copy_lang (lang_out, lang_len, "Malay (Malaysia)"));
1888                         break;
1889                 case 0x02:
1890                         return(copy_lang (lang_out, lang_len, "Malay (Brunei Darussalam)"));
1891                         break;
1892                 }
1893                 break;
1894         case 0x3f:
1895                 switch(secondary) {
1896                 case 0x00:
1897                         return(copy_lang (lang_out, lang_len, "Kazakh (Kazakhstan)"));
1898                         break;
1899                 case 0x01:
1900                         return(copy_lang (lang_out, lang_len, "Kazakh"));
1901                         break;
1902                 }
1903                 break;
1904         case 0x40:
1905                 switch(secondary) {
1906                 case 0x00:
1907                         return(copy_lang (lang_out, lang_len, "Kyrgyz (Kyrgyzstan)"));
1908                         break;
1909                 case 0x01:
1910                         return(copy_lang (lang_out, lang_len, "Kyrgyz (Cyrillic)"));
1911                         break;
1912                 }
1913                 break;
1914         case 0x41:
1915                 switch(secondary) {
1916                 case 0x00:
1917                         return(copy_lang (lang_out, lang_len, "Swahili (Kenya)"));
1918                         break;
1919                 case 0x01:
1920                         return(copy_lang (lang_out, lang_len, "Swahili"));
1921                         break;
1922                 }
1923                 break;
1924         case 0x42:
1925                 switch(secondary) {
1926                 case 0x01:
1927                         return(copy_lang (lang_out, lang_len, "Turkmen (Turkmenistan)"));
1928                         break;
1929                 }
1930                 break;
1931         case 0x43:
1932                 switch(secondary) {
1933                 case 0x00:
1934                         return(copy_lang (lang_out, lang_len, "Uzbek (Latin) (Uzbekistan)"));
1935                         break;
1936                 case 0x01:
1937                         return(copy_lang (lang_out, lang_len, "Uzbek (Latin)"));
1938                         break;
1939                 case 0x02:
1940                         return(copy_lang (lang_out, lang_len, "Uzbek (Cyrillic)"));
1941                         break;
1942                 }
1943                 break;
1944         case 0x44:
1945                 switch(secondary) {
1946                 case 0x00:
1947                         return(copy_lang (lang_out, lang_len, "Tatar (Russia)"));
1948                         break;
1949                 case 0x01:
1950                         return(copy_lang (lang_out, lang_len, "Tatar"));
1951                         break;
1952                 }
1953                 break;
1954         case 0x45:
1955                 switch(secondary) {
1956                 case 0x00:
1957                 case 0x01:
1958                         return(copy_lang (lang_out, lang_len, "Bengali (India)"));
1959                         break;
1960                 }
1961                 break;
1962         case 0x46:
1963                 switch(secondary) {
1964                 case 0x00:
1965                         return(copy_lang (lang_out, lang_len, "Punjabi (India)"));
1966                         break;
1967                 case 0x01:
1968                         return(copy_lang (lang_out, lang_len, "Punjabi"));
1969                         break;
1970                 }
1971                 break;
1972         case 0x47:
1973                 switch(secondary) {
1974                 case 0x00:
1975                         return(copy_lang (lang_out, lang_len, "Gujarati (India)"));
1976                         break;
1977                 case 0x01:
1978                         return(copy_lang (lang_out, lang_len, "Gujarati"));
1979                         break;
1980                 }
1981                 break;
1982         case 0x49:
1983                 switch(secondary) {
1984                 case 0x00:
1985                         return(copy_lang (lang_out, lang_len, "Tamil (India)"));
1986                         break;
1987                 case 0x01:
1988                         return(copy_lang (lang_out, lang_len, "Tamil"));
1989                         break;
1990                 }
1991                 break;
1992         case 0x4a:
1993                 switch(secondary) {
1994                 case 0x00:
1995                         return(copy_lang (lang_out, lang_len, "Telugu (India)"));
1996                         break;
1997                 case 0x01:
1998                         return(copy_lang (lang_out, lang_len, "Telugu"));
1999                         break;
2000                 }
2001                 break;
2002         case 0x4b:
2003                 switch(secondary) {
2004                 case 0x00:
2005                         return(copy_lang (lang_out, lang_len, "Kannada (India)"));
2006                         break;
2007                 case 0x01:
2008                         return(copy_lang (lang_out, lang_len, "Kannada"));
2009                         break;
2010                 }
2011                 break;
2012         case 0x4c:
2013                 switch(secondary) {
2014                 case 0x00:
2015                 case 0x01:
2016                         return(copy_lang (lang_out, lang_len, "Malayalam (India)"));
2017                         break;
2018                 }
2019                 break;
2020         case 0x4d:
2021                 switch(secondary) {
2022                 case 0x01:
2023                         return(copy_lang (lang_out, lang_len, "Assamese (India)"));
2024                         break;
2025                 }
2026                 break;
2027         case 0x4e:
2028                 switch(secondary) {
2029                 case 0x00:
2030                         return(copy_lang (lang_out, lang_len, "Marathi (India)"));
2031                         break;
2032                 case 0x01:
2033                         return(copy_lang (lang_out, lang_len, "Marathi"));
2034                         break;
2035                 }
2036                 break;
2037         case 0x4f:
2038                 switch(secondary) {
2039                 case 0x00:
2040                         return(copy_lang (lang_out, lang_len, "Sanskrit (India)"));
2041                         break;
2042                 case 0x01:
2043                         return(copy_lang (lang_out, lang_len, "Sanskrit"));
2044                         break;
2045                 }
2046                 break;
2047         case 0x50:
2048                 switch(secondary) {
2049                 case 0x00:
2050                         return(copy_lang (lang_out, lang_len, "Mongolian (Mongolia)"));
2051                         break;
2052                 case 0x01:
2053                         return(copy_lang (lang_out, lang_len, "Mongolian (Cyrillic)"));
2054                         break;
2055                 case 0x02:
2056                         return(copy_lang (lang_out, lang_len, "Mongolian (PRC)"));
2057                         break;
2058                 }
2059                 break;
2060         case 0x51:
2061                 switch(secondary) {
2062                 case 0x01:
2063                         return(copy_lang (lang_out, lang_len, "Tibetan (PRC)"));
2064                         break;
2065                 case 0x02:
2066                         return(copy_lang (lang_out, lang_len, "Tibetan (Bhutan)"));
2067                         break;
2068                 }
2069                 break;
2070         case 0x52:
2071                 switch(secondary) {
2072                 case 0x00:
2073                         return(copy_lang (lang_out, lang_len, "Welsh (United Kingdom)"));
2074                         break;
2075                 case 0x01:
2076                         return(copy_lang (lang_out, lang_len, "Welsh"));
2077                         break;
2078                 }
2079                 break;
2080         case 0x53:
2081                 switch(secondary) {
2082                 case 0x01:
2083                         return(copy_lang (lang_out, lang_len, "Khmer (Cambodia)"));
2084                         break;
2085                 }
2086                 break;
2087         case 0x54:
2088                 switch(secondary) {
2089                 case 0x01:
2090                         return(copy_lang (lang_out, lang_len, "Lao (Lao PDR)"));
2091                         break;
2092                 }
2093                 break;
2094         case 0x56:
2095                 switch(secondary) {
2096                 case 0x00:
2097                         return(copy_lang (lang_out, lang_len, "Galician (Spain)"));
2098                         break;
2099                 case 0x01:
2100                         return(copy_lang (lang_out, lang_len, "Galician"));
2101                         break;
2102                 }
2103                 break;
2104         case 0x57:
2105                 switch(secondary) {
2106                 case 0x00:
2107                         return(copy_lang (lang_out, lang_len, "Konkani (India)"));
2108                         break;
2109                 case 0x01:
2110                         return(copy_lang (lang_out, lang_len, "Konkani"));
2111                         break;
2112                 }
2113                 break;
2114         case 0x5a:
2115                 switch(secondary) {
2116                 case 0x00:
2117                         return(copy_lang (lang_out, lang_len, "Syriac (Syria)"));
2118                         break;
2119                 case 0x01:
2120                         return(copy_lang (lang_out, lang_len, "Syriac"));
2121                         break;
2122                 }
2123                 break;
2124         case 0x5b:
2125                 switch(secondary) {
2126                 case 0x01:
2127                         return(copy_lang (lang_out, lang_len, "Sinhala (Sri Lanka)"));
2128                         break;
2129                 }
2130                 break;
2131         case 0x5d:
2132                 switch(secondary) {
2133                 case 0x01:
2134                         return(copy_lang (lang_out, lang_len, "Inuktitut (Syllabics, Canada)"));
2135                         break;
2136                 case 0x02:
2137                         return(copy_lang (lang_out, lang_len, "Inuktitut (Latin, Canada)"));
2138                         break;
2139                 }
2140                 break;
2141         case 0x5e:
2142                 switch(secondary) {
2143                 case 0x01:
2144                         return(copy_lang (lang_out, lang_len, "Amharic (Ethiopia)"));
2145                         break;
2146                 }
2147                 break;
2148         case 0x5f:
2149                 switch(secondary) {
2150                 case 0x02:
2151                         return(copy_lang (lang_out, lang_len, "Tamazight (Algeria, Latin)"));
2152                         break;
2153                 }
2154                 break;
2155         case 0x61:
2156                 switch(secondary) {
2157                 case 0x01:
2158                         return(copy_lang (lang_out, lang_len, "Nepali (Nepal)"));
2159                         break;
2160                 }
2161                 break;
2162         case 0x62:
2163                 switch(secondary) {
2164                 case 0x01:
2165                         return(copy_lang (lang_out, lang_len, "Frisian (Netherlands)"));
2166                         break;
2167                 }
2168                 break;
2169         case 0x63:
2170                 switch(secondary) {
2171                 case 0x01:
2172                         return(copy_lang (lang_out, lang_len, "Pashto (Afghanistan)"));
2173                         break;
2174                 }
2175                 break;
2176         case 0x64:
2177                 switch(secondary) {
2178                 case 0x01:
2179                         return(copy_lang (lang_out, lang_len, "Filipino (Philippines)"));
2180                         break;
2181                 }
2182                 break;
2183         case 0x65:
2184                 switch(secondary) {
2185                 case 0x00:
2186                         return(copy_lang (lang_out, lang_len, "Divehi (Maldives)"));
2187                         break;
2188                 case 0x01:
2189                         return(copy_lang (lang_out, lang_len, "Divehi"));
2190                         break;
2191                 }
2192                 break;
2193         case 0x68:
2194                 switch(secondary) {
2195                 case 0x01:
2196                         return(copy_lang (lang_out, lang_len, "Hausa (Nigeria, Latin)"));
2197                         break;
2198                 }
2199                 break;
2200         case 0x6a:
2201                 switch(secondary) {
2202                 case 0x01:
2203                         return(copy_lang (lang_out, lang_len, "Yoruba (Nigeria)"));
2204                         break;
2205                 }
2206                 break;
2207         case 0x6b:
2208                 switch(secondary) {
2209                 case 0x00:
2210                 case 0x01:
2211                         return(copy_lang (lang_out, lang_len, "Quechua (Bolivia)"));
2212                         break;
2213                 case 0x02:
2214                         return(copy_lang (lang_out, lang_len, "Quechua (Ecuador)"));
2215                         break;
2216                 case 0x03:
2217                         return(copy_lang (lang_out, lang_len, "Quechua (Peru)"));
2218                         break;
2219                 }
2220                 break;
2221         case 0x6c:
2222                 switch(secondary) {
2223                 case 0x00:
2224                         return(copy_lang (lang_out, lang_len, "Northern Sotho (South Africa)"));
2225                         break;
2226                 case 0x01:
2227                         return(copy_lang (lang_out, lang_len, "Northern Sotho"));
2228                         break;
2229                 }
2230                 break;
2231         case 0x6d:
2232                 switch(secondary) {
2233                 case 0x01:
2234                         return(copy_lang (lang_out, lang_len, "Bashkir (Russia)"));
2235                         break;
2236                 }
2237                 break;
2238         case 0x6e:
2239                 switch(secondary) {
2240                 case 0x01:
2241                         return(copy_lang (lang_out, lang_len, "Luxembourgish (Luxembourg)"));
2242                         break;
2243                 }
2244                 break;
2245         case 0x6f:
2246                 switch(secondary) {
2247                 case 0x01:
2248                         return(copy_lang (lang_out, lang_len, "Greenlandic (Greenland)"));
2249                         break;
2250                 }
2251                 break;
2252         case 0x78:
2253                 switch(secondary) {
2254                 case 0x01:
2255                         return(copy_lang (lang_out, lang_len, "Yi (PRC)"));
2256                         break;
2257                 }
2258                 break;
2259         case 0x7a:
2260                 switch(secondary) {
2261                 case 0x01:
2262                         return(copy_lang (lang_out, lang_len, "Mapudungun (Chile)"));
2263                         break;
2264                 }
2265                 break;
2266         case 0x7c:
2267                 switch(secondary) {
2268                 case 0x01:
2269                         return(copy_lang (lang_out, lang_len, "Mohawk (Mohawk)"));
2270                         break;
2271                 }
2272                 break;
2273         case 0x7e:
2274                 switch(secondary) {
2275                 case 0x01:
2276                         return(copy_lang (lang_out, lang_len, "Breton (France)"));
2277                         break;
2278                 }
2279                 break;
2280         case 0x7f:
2281                 switch(secondary) {
2282                 case 0x00:
2283                         return(copy_lang (lang_out, lang_len, "Invariant Language (Invariant Country)"));
2284                         break;
2285                 }
2286                 break;
2287         case 0x80:
2288                 switch(secondary) {
2289                 case 0x01:
2290                         return(copy_lang (lang_out, lang_len, "Uighur (PRC)"));
2291                         break;
2292                 }
2293                 break;
2294         case 0x81:
2295                 switch(secondary) {
2296                 case 0x00:
2297                         return(copy_lang (lang_out, lang_len, "Maori (New Zealand)"));
2298                         break;
2299                 case 0x01:
2300                         return(copy_lang (lang_out, lang_len, "Maori"));
2301                         break;
2302                 }
2303                 break;
2304         case 0x83:
2305                 switch(secondary) {
2306                 case 0x01:
2307                         return(copy_lang (lang_out, lang_len, "Corsican (France)"));
2308                         break;
2309                 }
2310                 break;
2311         case 0x84:
2312                 switch(secondary) {
2313                 case 0x01:
2314                         return(copy_lang (lang_out, lang_len, "Alsatian (France)"));
2315                         break;
2316                 }
2317                 break;
2318         case 0x85:
2319                 switch(secondary) {
2320                 case 0x01:
2321                         return(copy_lang (lang_out, lang_len, "Yakut (Russia)"));
2322                         break;
2323                 }
2324                 break;
2325         case 0x86:
2326                 switch(secondary) {
2327                 case 0x01:
2328                         return(copy_lang (lang_out, lang_len, "K'iche (Guatemala)"));
2329                         break;
2330                 }
2331                 break;
2332         case 0x87:
2333                 switch(secondary) {
2334                 case 0x01:
2335                         return(copy_lang (lang_out, lang_len, "Kinyarwanda (Rwanda)"));
2336                         break;
2337                 }
2338                 break;
2339         case 0x88:
2340                 switch(secondary) {
2341                 case 0x01:
2342                         return(copy_lang (lang_out, lang_len, "Wolof (Senegal)"));
2343                         break;
2344                 }
2345                 break;
2346         case 0x8c:
2347                 switch(secondary) {
2348                 case 0x01:
2349                         return(copy_lang (lang_out, lang_len, "Dari (Afghanistan)"));
2350                         break;
2351                 }
2352                 break;
2353
2354         default:
2355                 return(copy_lang (lang_out, lang_len, "Language Neutral"));
2356
2357         }
2358         
2359         return(copy_lang (lang_out, lang_len, "Language Neutral"));
2360 }