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