Fix the case of product and company names.
[wine] / loader / loadorder.c
1 /*
2  * Module/Library loadorder
3  *
4  * Copyright 1999 Bertho Stultiens
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20
21 #include "config.h"
22 #include "wine/port.h"
23
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <assert.h>
28
29 #include "windef.h"
30 #include "winbase.h"
31 #include "winerror.h"
32 #include "winreg.h"
33 #include "winternl.h"
34 #include "file.h"
35 #include "module.h"
36
37 #include "wine/debug.h"
38 #include "wine/unicode.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(module);
41
42 #define LOADORDER_ALLOC_CLUSTER 32      /* Allocate with 32 entries at a time */
43
44 typedef struct module_loadorder
45 {
46     const char         *modulename;
47     enum loadorder_type loadorder[LOADORDER_NTYPES];
48 } module_loadorder_t;
49
50 struct loadorder_list
51 {
52     int                 count;
53     int                 alloc;
54     module_loadorder_t *order;
55 };
56
57 /* default load-order if nothing specified */
58 /* the list must remain sorted by dll name */
59 static module_loadorder_t default_order_list[] =
60 {
61     { "display",      { LOADORDER_BI, 0, 0 } },
62     { "gdi.exe",      { LOADORDER_BI, 0, 0 } },
63     { "gdi32",        { LOADORDER_BI, 0, 0 } },
64     { "icmp",         { LOADORDER_BI, 0, 0 } },
65     { "kernel",       { LOADORDER_BI, 0, 0 } },
66     { "kernel32",     { LOADORDER_BI, 0, 0 } },
67     { "keyboard",     { LOADORDER_BI, 0, 0 } },
68     { "krnl386.exe",  { LOADORDER_BI, 0, 0 } },
69     { "mmsystem",     { LOADORDER_BI, 0, 0 } },
70     { "mouse",        { LOADORDER_BI, 0, 0 } },
71     { "ntdll",        { LOADORDER_BI, 0, 0 } },
72     { "odbc32",       { LOADORDER_BI, 0, 0 } },
73     { "system.drv",   { LOADORDER_BI, 0, 0 } },
74     { "toolhelp",     { LOADORDER_BI, 0, 0 } },
75     { "ttydrv",       { LOADORDER_BI, 0, 0 } },
76     { "user.exe",     { LOADORDER_BI, 0, 0 } },
77     { "user32",       { LOADORDER_BI, 0, 0 } },
78     { "w32skrnl",     { LOADORDER_BI, 0, 0 } },
79     { "winaspi",      { LOADORDER_BI, 0, 0 } },
80     { "winedos",      { LOADORDER_BI, 0, 0 } },
81     { "wineps",       { LOADORDER_BI, 0, 0 } },
82     { "wing",         { LOADORDER_BI, 0, 0 } },
83     { "winmm",        { LOADORDER_BI, 0, 0 } },
84     { "winsock",      { LOADORDER_BI, 0, 0 } },
85     { "wnaspi32",     { LOADORDER_BI, 0, 0 } },
86     { "wow32",        { LOADORDER_BI, 0, 0 } },
87     { "wprocs",       { LOADORDER_BI, 0, 0 } },
88     { "ws2_32",       { LOADORDER_BI, 0, 0 } },
89     { "wsock32",      { LOADORDER_BI, 0, 0 } },
90     { "x11drv",       { LOADORDER_BI, 0, 0 } }
91 };
92
93 static const struct loadorder_list default_list =
94 {
95     sizeof(default_order_list)/sizeof(default_order_list[0]),
96     sizeof(default_order_list)/sizeof(default_order_list[0]),
97     default_order_list
98 };
99
100 /* default if nothing else specified */
101 static const enum loadorder_type default_loadorder[LOADORDER_NTYPES] =
102 {
103     LOADORDER_BI, LOADORDER_DLL, 0
104 };
105
106 /* default for modules with an explicit path */
107 static const enum loadorder_type default_path_loadorder[LOADORDER_NTYPES] =
108 {
109     LOADORDER_DLL, LOADORDER_BI, 0
110 };
111
112 static struct loadorder_list cmdline_list;
113
114
115 /***************************************************************************
116  *      cmp_sort_func   (internal, static)
117  *
118  * Sorting and comparing function used in sort and search of loadorder
119  * entries.
120  */
121 static int cmp_sort_func(const void *s1, const void *s2)
122 {
123     return FILE_strcasecmp(((module_loadorder_t *)s1)->modulename,
124                            ((module_loadorder_t *)s2)->modulename);
125 }
126
127
128 /***************************************************************************
129  *      get_tok (internal, static)
130  *
131  * strtok wrapper for non-destructive buffer writing.
132  * NOTE: strtok is not reentrant and therefore this code is neither.
133  */
134 static char *get_tok(const char *str, const char *delim)
135 {
136         static char *buf = NULL;
137         char *cptr;
138
139         if(!str && !buf)
140                 return NULL;
141
142         if(str && buf)
143         {
144                 HeapFree(GetProcessHeap(), 0, buf);
145                 buf = NULL;
146         }
147
148         if(str && !buf)
149         {
150                 buf = HeapAlloc(GetProcessHeap(), 0, strlen(str)+1);
151                 strcpy( buf, str );
152                 cptr = strtok(buf, delim);
153         }
154         else
155         {
156                 cptr = strtok(NULL, delim);
157         }
158
159         if(!cptr)
160         {
161                 HeapFree(GetProcessHeap(), 0, buf);
162                 buf = NULL;
163         }
164         return cptr;
165 }
166
167
168 /***************************************************************************
169  *      get_basename
170  *
171  * Return the base name of a file name (i.e. remove the path components).
172  */
173 static const char *get_basename( const char *name )
174 {
175     const char *ptr;
176
177     if (name[0] && name[1] == ':') name += 2;  /* strip drive specification */
178     if ((ptr = strrchr( name, '\\' ))) name = ptr + 1;
179     if ((ptr = strrchr( name, '/' ))) name = ptr + 1;
180     return name;
181 }
182
183
184 /***************************************************************************
185  *      debugstr_loadorder
186  *
187  * Return a loadorder in printable form.
188  */
189 static const char *debugstr_loadorder( enum loadorder_type lo[] )
190 {
191     int i;
192     char buffer[LOADORDER_NTYPES*3+1];
193
194     buffer[0] = 0;
195     for(i = 0; i < LOADORDER_NTYPES; i++)
196     {
197         if (lo[i] == LOADORDER_INVALID) break;
198         switch(lo[i])
199         {
200         case LOADORDER_DLL: strcat( buffer, "n," ); break;
201         case LOADORDER_BI:  strcat( buffer, "b," ); break;
202         default:            strcat( buffer, "?," ); break;
203         }
204     }
205     if (buffer[0]) buffer[strlen(buffer)-1] = 0;
206     return debugstr_a(buffer);
207 }
208
209
210 /***************************************************************************
211  *      ParseLoadOrder  (internal, static)
212  *
213  * Parses the loadorder options from the configuration and puts it into
214  * a structure.
215  */
216 static BOOL ParseLoadOrder(char *order, enum loadorder_type lo[])
217 {
218     static int warn;
219         char *cptr;
220         int n = 0;
221
222         cptr = get_tok(order, ", \t");
223         while(cptr)
224         {
225             enum loadorder_type type = LOADORDER_INVALID;
226
227                 if(n >= LOADORDER_NTYPES-1)
228                 {
229                         ERR("More than existing %d module-types specified, rest ignored\n", LOADORDER_NTYPES-1);
230                         break;
231                 }
232
233                 switch(*cptr)
234                 {
235                 case 'N':       /* Native */
236                 case 'n': type = LOADORDER_DLL; break;
237
238                 case 'S':       /* So */
239                 case 's':
240                     if (!warn++) MESSAGE("Load order 'so' no longer supported, ignored\n");
241                     break;
242
243                 case 'B':       /* Builtin */
244                 case 'b': type = LOADORDER_BI; break;
245
246                 default:
247                         ERR("Invalid load order module-type '%s', ignored\n", cptr);
248                 }
249
250                 if(type != LOADORDER_INVALID) lo[n++] = type;
251                 cptr = get_tok(NULL, ", \t");
252         }
253         lo[n] = LOADORDER_INVALID;
254         return TRUE;
255 }
256
257
258 /***************************************************************************
259  *      AddLoadOrder    (internal, static)
260  *
261  * Adds an entry in the list of command-line overrides.
262  */
263 static BOOL AddLoadOrder(module_loadorder_t *plo)
264 {
265         int i;
266
267         /* TRACE(module, "'%s' -> %08lx\n", plo->modulename, *(DWORD *)(plo->loadorder)); */
268
269         for(i = 0; i < cmdline_list.count; i++)
270         {
271             if(!cmp_sort_func(plo, &cmdline_list.order[i] ))
272             {
273                 /* replace existing option */
274                 memcpy( cmdline_list.order[i].loadorder, plo->loadorder, sizeof(plo->loadorder));
275                 return TRUE;
276             }
277         }
278
279         if (i >= cmdline_list.alloc)
280         {
281                 /* No space in current array, make it larger */
282                 cmdline_list.alloc += LOADORDER_ALLOC_CLUSTER;
283                 cmdline_list.order = HeapReAlloc(GetProcessHeap(), 0, cmdline_list.order,
284                                           cmdline_list.alloc * sizeof(module_loadorder_t));
285                 if(!cmdline_list.order)
286                 {
287                         MESSAGE("Virtual memory exhausted\n");
288                         exit(1);
289                 }
290         }
291         memcpy(cmdline_list.order[i].loadorder, plo->loadorder, sizeof(plo->loadorder));
292         cmdline_list.order[i].modulename = HeapAlloc(GetProcessHeap(), 0, strlen(plo->modulename)+1);
293         strcpy( (char *)cmdline_list.order[i].modulename, plo->modulename );
294         cmdline_list.count++;
295         return TRUE;
296 }
297
298
299 /***************************************************************************
300  *      AddLoadOrderSet (internal, static)
301  *
302  * Adds a set of entries in the list of command-line overrides from the key parameter.
303  */
304 static BOOL AddLoadOrderSet(char *key, char *order)
305 {
306         module_loadorder_t ldo;
307         char *cptr;
308
309         /* Parse the loadorder before the rest because strtok is not reentrant */
310         if(!ParseLoadOrder(order, ldo.loadorder))
311                 return FALSE;
312
313         cptr = get_tok(key, ", \t");
314         while(cptr)
315         {
316                 char *ext = strrchr(cptr, '.');
317                 if(ext && !FILE_strcasecmp( ext, ".dll" )) *ext = 0;
318                 ldo.modulename = cptr;
319                 if(!AddLoadOrder(&ldo)) return FALSE;
320                 cptr = get_tok(NULL, ", \t");
321         }
322         return TRUE;
323 }
324
325
326 /***************************************************************************
327  *      MODULE_AddLoadOrderOption
328  *
329  * The commandline option is in the form:
330  * name[,name,...]=native[,b,...]
331  */
332 void MODULE_AddLoadOrderOption( const char *option )
333 {
334     char *value, *key = HeapAlloc(GetProcessHeap(), 0, strlen(option)+1);
335
336     strcpy( key, option );
337     if (!(value = strchr(key, '='))) goto error;
338     *value++ = '\0';
339
340     TRACE("Commandline override '%s' = '%s'\n", key, value);
341
342     if (!AddLoadOrderSet(key, value)) goto error;
343     HeapFree(GetProcessHeap(), 0, key);
344
345     /* sort the array for quick lookup */
346     qsort(cmdline_list.order, cmdline_list.count, sizeof(cmdline_list.order[0]), cmp_sort_func);
347     return;
348
349  error:
350     MESSAGE( "Syntax: -dll name[,name[,...]]={native|so|builtin}[,{n|s|b}[,...]]\n"
351              "    - 'name' is the name of any dll without extension\n"
352              "    - the order of loading (native, so and builtin) can be abbreviated\n"
353              "      with the first letter\n"
354              "    - the option can be specified multiple times\n"
355              "    Example:\n"
356              "    -dll comdlg32,commdlg=n -dll shell,shell32=b\n" );
357     ExitProcess(1);
358 }
359
360
361 /***************************************************************************
362  *      get_list_load_order
363  *
364  * Get the load order for a given module from the command-line or
365  * default lists.
366  */
367 static BOOL get_list_load_order( const char *module, const struct loadorder_list *list,
368                                  enum loadorder_type lo[] )
369 {
370     module_loadorder_t tmp, *res = NULL;
371
372     tmp.modulename = module;
373     /* some bsearch implementations (Solaris) are buggy when the number of items is 0 */
374     if (list->count && (res = bsearch(&tmp, list->order, list->count, sizeof(list->order[0]), cmp_sort_func)))
375         memcpy( lo, res->loadorder, sizeof(res->loadorder) );
376     return (res != NULL);
377 }
378
379
380 /***************************************************************************
381  *      open_app_key
382  *
383  * Open the registry key to the app-specific DllOverrides list.
384  */
385 static HKEY open_app_key( const char *module )
386 {
387     OBJECT_ATTRIBUTES attr;
388     UNICODE_STRING nameW;
389     HKEY hkey, appkey;
390     char buffer[MAX_PATH+16], *appname;
391     static const WCHAR AppDefaultsW[] = {'M','a','c','h','i','n','e','\\',
392                                          'S','o','f','t','w','a','r','e','\\',
393                                          'W','i','n','e','\\',
394                                          'W','i','n','e','\\',
395                                          'C','o','n','f','i','g','\\',
396                                          'A','p','p','D','e','f','a','u','l','t','s',0};
397
398     if (!GetModuleFileNameA( 0, buffer, MAX_PATH ))
399     {
400         WARN( "could not get module file name loading %s\n", module );
401         return 0;
402     }
403     appname = (char *)get_basename( buffer );
404
405     TRACE( "searching '%s' in AppDefaults\\%s\\DllOverrides\n", module, appname );
406
407     attr.Length = sizeof(attr);
408     attr.RootDirectory = 0;
409     attr.ObjectName = &nameW;
410     attr.Attributes = 0;
411     attr.SecurityDescriptor = NULL;
412     attr.SecurityQualityOfService = NULL;
413     RtlInitUnicodeString( &nameW, AppDefaultsW );
414
415     if (NtOpenKey( &hkey, KEY_ALL_ACCESS, &attr )) return 0;
416     attr.RootDirectory = hkey;
417
418     /* open AppDefaults\\appname\\DllOverrides key */
419     strcat( appname, "\\DllOverrides" );
420     RtlCreateUnicodeStringFromAsciiz( &nameW, appname );
421     if (NtOpenKey( &appkey, KEY_ALL_ACCESS, &attr )) appkey = 0;
422     RtlFreeUnicodeString( &nameW );
423     NtClose( hkey );
424     return appkey;
425 }
426
427
428 /***************************************************************************
429  *      get_registry_value
430  *
431  * Load the registry loadorder value for a given module.
432  */
433 static BOOL get_registry_value( HKEY hkey, const char *module, enum loadorder_type lo[] )
434 {
435     UNICODE_STRING valueW;
436     char buffer[80];
437     DWORD count;
438     BOOL ret;
439
440     RtlCreateUnicodeStringFromAsciiz( &valueW, module );
441
442     if ((ret = !NtQueryValueKey( hkey, &valueW, KeyValuePartialInformation,
443                                  buffer, sizeof(buffer), &count )))
444     {
445         int i, n = 0;
446         WCHAR *str = (WCHAR *)((KEY_VALUE_PARTIAL_INFORMATION *)buffer)->Data;
447
448         while (*str)
449         {
450             enum loadorder_type type = LOADORDER_INVALID;
451
452             while (*str == ',' || isspaceW(*str)) str++;
453             if (!*str) break;
454
455             switch(tolowerW(*str))
456             {
457             case 'n': type = LOADORDER_DLL; break;
458             case 'b': type = LOADORDER_BI; break;
459             case 's': break;  /* no longer supported, ignore */
460             case 0:   break;  /* end of string */
461             default:
462                 ERR("Invalid load order module-type %s, ignored\n", debugstr_w(str));
463                 break;
464             }
465             if (type != LOADORDER_INVALID)
466             {
467                 for (i = 0; i < n; i++) if (lo[i] == type) break;  /* already specified */
468                 if (i == n) lo[n++] = type;
469             }
470             while (*str && *str != ',' && !isspaceW(*str)) str++;
471         }
472         lo[n] = LOADORDER_INVALID;
473     }
474     RtlFreeUnicodeString( &valueW );
475     return ret;
476 }
477
478
479 /***************************************************************************
480  *      MODULE_GetBuiltinPath
481  *
482  * Get the path of a builtin module when the native file does not exist.
483  */
484 BOOL MODULE_GetBuiltinPath( const char *libname, const char *ext, char *filename, UINT size )
485 {
486     char *p;
487     BOOL ret = FALSE;
488     UINT len = GetSystemDirectoryA( filename, size );
489
490     if (FILE_contains_path( libname ))
491     {
492         char *tmp;
493
494         /* if the library name contains a path and can not be found,
495          * return an error.
496          * exception: if the path is the system directory, proceed,
497          * so that modules which are not PE modules can be loaded.
498          * If the library name does not contain a path and can not
499          * be found, assume the system directory is meant */
500
501         if (strlen(libname) >= size) return FALSE;  /* too long */
502         if (strchr( libname, '/' ))  /* need to convert slashes */
503         {
504             if (!(tmp = HeapAlloc( GetProcessHeap(), 0, strlen(libname)+1 ))) return FALSE;
505             strcpy( tmp, libname );
506             for (p = tmp; *p; p++) if (*p == '/') *p = '\\';
507         }
508         else tmp = (char *)libname;
509
510         if (!FILE_strncasecmp( filename, tmp, len ) && tmp[len] == '\\')
511         {
512             strcpy( filename, tmp );
513             ret = TRUE;
514         }
515         if (tmp != libname) HeapFree( GetProcessHeap(), 0, tmp );
516         if (!ret) return FALSE;
517     }
518     else
519     {
520         if (strlen(libname) >= size - len - 1) return FALSE;
521         filename[len] = '\\';
522         strcpy( filename+len+1, libname );
523     }
524
525     /* if the filename doesn't have an extension, append the default */
526     if (!(p = strrchr( filename, '.')) || strchr( p, '/' ) || strchr( p, '\\'))
527     {
528         if (strlen(filename) + strlen(ext) >= size) return FALSE;
529         strcat( filename, ext );
530     }
531     return TRUE;
532 }
533
534
535 /***************************************************************************
536  *      MODULE_GetLoadOrder     (internal)
537  *
538  * Locate the loadorder of a module.
539  * Any path is stripped from the path-argument and so are the extension
540  * '.dll' and '.exe'. A lookup in the table can yield an override for
541  * the specific dll. Otherwise the default load order is returned.
542  */
543 void MODULE_GetLoadOrder( enum loadorder_type loadorder[], const char *path, BOOL win32 )
544 {
545     static const WCHAR DllOverridesW[] = {'M','a','c','h','i','n','e','\\',
546                                           'S','o','f','t','w','a','r','e','\\',
547                                           'W','i','n','e','\\',
548                                           'W','i','n','e','\\',
549                                           'C','o','n','f','i','g','\\',
550                                           'D','l','l','O','v','e','r','r','i','d','e','s',0};
551
552     static HKEY std_key = (HKEY)-1;  /* key to standard section, cached */
553     HKEY app_key = 0;
554     char *module, *basename;
555     int len;
556
557     TRACE("looking for %s\n", path);
558
559     loadorder[0] = LOADORDER_INVALID;  /* in case something bad happens below */
560
561     /* Strip path information if the module resides in the system directory
562      * (path is already stripped by caller for 16-bit modules)
563      */
564     if (win32)
565     {
566         char sysdir[MAX_PATH+1];
567         if (!GetSystemDirectoryA( sysdir, MAX_PATH )) return;
568         if (!FILE_strncasecmp( sysdir, path, strlen (sysdir) ))
569         {
570             path += strlen(sysdir);
571             while (*path == '\\' || *path == '/') path++;
572         }
573     }
574
575     if (!(len = strlen(path))) return;
576     if (!(module = HeapAlloc( GetProcessHeap(), 0, len + 2 ))) return;
577     strcpy( module+1, path );  /* reserve module[0] for the wildcard char */
578
579     if (len >= 4)
580     {
581         char *ext = module + 1 + len - 4;
582         if (!FILE_strcasecmp( ext, ".dll" )) *ext = 0;
583     }
584
585     /* check command-line first */
586     if (get_list_load_order( module+1, &cmdline_list, loadorder ))
587     {
588         TRACE( "got cmdline %s for %s\n",
589                debugstr_loadorder(loadorder), debugstr_a(path) );
590         goto done;
591     }
592
593     /* then explicit module name in AppDefaults */
594     app_key = open_app_key( module+1 );
595     if (app_key && get_registry_value( app_key, module+1, loadorder ))
596     {
597         TRACE( "got app defaults %s for %s\n",
598                debugstr_loadorder(loadorder), debugstr_a(path) );
599         goto done;
600     }
601
602     /* then explicit module name in standard section */
603     if (std_key == (HKEY)-1)
604     {
605         OBJECT_ATTRIBUTES attr;
606         UNICODE_STRING nameW;
607
608         attr.Length = sizeof(attr);
609         attr.RootDirectory = 0;
610         attr.ObjectName = &nameW;
611         attr.Attributes = 0;
612         attr.SecurityDescriptor = NULL;
613         attr.SecurityQualityOfService = NULL;
614         RtlInitUnicodeString( &nameW, DllOverridesW );
615
616         if (NtOpenKey( &std_key, KEY_ALL_ACCESS, &attr )) std_key = 0;
617     }
618
619     if (std_key && get_registry_value( std_key, module+1, loadorder ))
620     {
621         TRACE( "got standard entry %s for %s\n",
622                debugstr_loadorder(loadorder), debugstr_a(path) );
623         goto done;
624     }
625
626     /* then module basename preceded by '*' in AppDefaults */
627     basename = (char *)get_basename( module+1 );
628     basename[-1] = '*';
629     if (app_key && get_registry_value( app_key, basename-1, loadorder ))
630     {
631         TRACE( "got app defaults basename %s for %s\n",
632                debugstr_loadorder(loadorder), debugstr_a(path) );
633         goto done;
634     }
635
636     /* then module name preceded by '*' in standard section */
637     if (std_key && get_registry_value( std_key, basename-1, loadorder ))
638     {
639         TRACE( "got standard base name %s for %s\n",
640                debugstr_loadorder(loadorder), debugstr_a(path) );
641         goto done;
642     }
643
644     /* then base name matching compiled-in defaults */
645     if (get_list_load_order( basename, &default_list, loadorder ))
646     {
647         TRACE( "got compiled-in default %s for %s\n",
648                debugstr_loadorder(loadorder), debugstr_a(path) );
649         goto done;
650     }
651
652     if (basename == module+1)
653     {
654         /* then wildcard entry in AppDefaults (only if no explicit path) */
655         if (app_key && get_registry_value( app_key, "*", loadorder ))
656         {
657             TRACE( "got app defaults wildcard %s for %s\n",
658                    debugstr_loadorder(loadorder), debugstr_a(path) );
659             goto done;
660         }
661
662         /* then wildcard entry in standard section (only if no explicit path) */
663         if (std_key && get_registry_value( std_key, "*", loadorder ))
664         {
665             TRACE( "got standard wildcard %s for %s\n",
666                    debugstr_loadorder(loadorder), debugstr_a(path) );
667             goto done;
668         }
669
670         /* and last the hard-coded default */
671         memcpy( loadorder, default_loadorder, sizeof(default_loadorder) );
672         TRACE( "got hardcoded default %s for %s\n",
673                debugstr_loadorder(loadorder), debugstr_a(path) );
674     }
675     else  /* module contains an explicit path */
676     {
677         memcpy( loadorder, default_path_loadorder, sizeof(default_path_loadorder) );
678         TRACE( "got hardcoded path default %s for %s\n",
679                debugstr_loadorder(loadorder), debugstr_a(path) );
680     }
681
682  done:
683     if (app_key) NtClose( app_key );
684     HeapFree( GetProcessHeap(), 0, module );
685 }