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