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