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