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