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