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