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