Prevent setup from crashing if a section in the INF is empty (no
[wine] / dlls / setupapi / setupx_main.c
1 /*
2  *      SETUPX library
3  *
4  *      Copyright 1998,2000  Andreas Mohr
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  * FIXME: Rather non-functional functions for now.
21  *
22  * See:
23  * http://www.geocities.com/SiliconValley/Network/5317/drivers.html
24  * http://willemer.de/informatik/windows/inf_info.htm (German)
25  * http://www.microsoft.com/ddk/ddkdocs/win98ddk/devinst_12uw.htm
26  * DDK: setupx.h
27  * http://mmatrix.tripod.com/customsystemfolder/infsysntaxfull.html
28  * http://www.rdrop.com/~cary/html/inf_faq.html
29  * http://support.microsoft.com/support/kb/articles/q194/6/40.asp
30  *
31  * Stuff tested with:
32  * - rs405deu.exe (German Acroread 4.05 setup)
33  * - ie5setup.exe
34  * - Netmeeting
35  *
36  * FIXME:
37  * - string handling is... weird ;) (buflen etc.)
38  * - memory leaks ?
39  * - separate that mess (but probably only when it's done completely)
40  *
41  * SETUPX consists of several parts with the following acronyms/prefixes:
42  * Di   device installer (devinst.c ?)
43  * Gen  generic installer (geninst.c ?)
44  * Ip   .INF parsing (infparse.c)
45  * LDD  logical device descriptor (ldd.c ?)
46  * LDID logical device ID
47  * SU   setup (setup.c ?)
48  * Tp   text processing (textproc.c ?)
49  * Vcp  virtual copy module (vcp.c ?)
50  * ...
51  *
52  * The SETUPX DLL is NOT thread-safe. That's why many installers urge you to
53  * "close all open applications".
54  * All in all the design of it seems to be a bit weak.
55  * Not sure whether my implementation of it is better, though ;-)
56  */
57
58 #include <stdlib.h>
59 #include <stdio.h>
60 #include <string.h>
61 #include "winreg.h"
62 #include "wine/winuser16.h"
63 #include "setupx16.h"
64 #include "setupapi_private.h"
65 #include "winerror.h"
66 #include "wine/debug.h"
67
68 WINE_DEFAULT_DEBUG_CHANNEL(setupapi);
69
70 /***********************************************************************
71  *              SURegOpenKey (SETUPX.47)
72  */
73 DWORD WINAPI SURegOpenKey( HKEY hkey, LPCSTR lpszSubKey, LPHKEY retkey )
74 {
75     FIXME("(%x,%s,%p), semi-stub.\n",hkey,debugstr_a(lpszSubKey),retkey);
76     return RegOpenKeyA( hkey, lpszSubKey, retkey );
77 }
78
79 /***********************************************************************
80  *              SURegQueryValueEx (SETUPX.50)
81  */
82 DWORD WINAPI SURegQueryValueEx( HKEY hkey, LPSTR lpszValueName,
83                                 LPDWORD lpdwReserved, LPDWORD lpdwType,
84                                 LPBYTE lpbData, LPDWORD lpcbData )
85 {
86     FIXME("(%x,%s,%p,%p,%p,%ld), semi-stub.\n",hkey,debugstr_a(lpszValueName),
87           lpdwReserved,lpdwType,lpbData,lpcbData?*lpcbData:0);
88     return RegQueryValueExA( hkey, lpszValueName, lpdwReserved, lpdwType,
89                                lpbData, lpcbData );
90 }
91
92 /*
93  * Returns pointer to a string list with the first entry being number
94  * of strings.
95  *
96  * Hmm. Should this be InitSubstrData(), GetFirstSubstr() and GetNextSubstr()
97  * instead?
98  */
99 static LPSTR *SETUPX_GetSubStrings(LPSTR start, char delimiter)
100 {
101     LPSTR p, q;
102     LPSTR *res = NULL;
103     DWORD count = 0;
104     int len;
105
106     p = start;
107
108     while (1)
109     {
110         /* find beginning of real substring */
111         while ( (*p == ' ') || (*p == '\t') || (*p == '"') ) p++;
112
113         /* find end of real substring */
114         q = p;
115         while ( (*q)
116              && (*q != ' ') && (*q != '\t') && (*q != '"')
117              && (*q != ';') && (*q != delimiter) ) q++;
118         if (q == p)
119             break;
120         len = (int)q - (int)p;
121
122         /* alloc entry for new substring in steps of 32 units and copy over */
123         if (count % 32 == 0)
124         { /* 1 for count field + current count + 32 */
125             res = HeapReAlloc(GetProcessHeap(), 0, res, (1+count+32)*sizeof(LPSTR));
126         }
127         *(res+1+count) = HeapAlloc(GetProcessHeap(), 0, len+1);
128         strncpy(*(res+1+count), p, len);
129         (*(res+1+count))[len] = '\0';
130         count++;
131
132         /* we are still within last substring (before delimiter),
133          * so get out of it */
134         while ((*q) && (*q != ';') && (*q != delimiter)) q++;
135         if ((!*q) || (*q == ';'))
136             break;
137         p = q+1;
138     }
139
140     /* put number of entries at beginning of list */
141     *(DWORD *)res = count;
142     return res;
143 }
144
145 static void SETUPX_FreeSubStrings(LPSTR *substr)
146 {
147     DWORD count = *(DWORD *)substr;
148     LPSTR *pStrings = substr+1;
149     DWORD n;
150
151     for (n=0; n < count; n++)
152         HeapFree(GetProcessHeap(), 0, *pStrings++);
153
154     HeapFree(GetProcessHeap(), 0, substr);
155 }
156
157 static void SETUPX_IsolateSubString(LPSTR *begin, LPSTR *end)
158 {
159     LPSTR p, q;
160
161     p = *begin;
162     q = *end;
163
164     while ((p < q) && ((*p == ' ') || (*p == '\t'))) p++;
165     while ((p < q) && (*p == '"')) p++;
166
167     while ((q-1 >= p) && ((*(q-1) == ' ') || (*(q-1) == '\t'))) q--;
168     while ((q-1 >= p) && (*(q-1) == '"')) q--;
169
170     *begin = p;
171     *end = q;
172 }
173
174 /*
175  * Example: HKLM,"Software\Microsoft\Windows\CurrentVersion","ProgramFilesDir",,"C:\"
176  * FIXME: use SETUPX_GetSubStrings() instead.
177  *        Hmm, but on the other hand SETUPX_GetSubStrings() will probably
178  *        soon be replaced by InitSubstrData() etc. anyway.
179  *
180  */
181 static BOOL SETUPX_LookupRegistryString(LPSTR regstr, LPSTR buffer, DWORD buflen)
182 {
183     HANDLE heap = GetProcessHeap();
184     LPSTR items[5];
185     LPSTR p, q, next;
186     int len, n;
187     HKEY hkey, hsubkey;
188     DWORD dwType;
189
190     TRACE("retrieving '%s'\n", regstr);
191
192     p = regstr;
193
194     /* isolate root key, subkey, value, flag, defval */
195     for (n=0; n < 5; n++)
196     {
197         q = strchr(p, ',');
198         if (!q)
199         {
200             if (n == 4)
201                 q = p+strlen(p);
202             else
203                 return FALSE;
204         }
205         next = q+1;
206         if (q < regstr)
207             return FALSE;
208         SETUPX_IsolateSubString(&p, &q);
209         len = (int)q - (int)p;
210         items[n] = HeapAlloc(heap, 0, len+1);
211         strncpy(items[n], p, len);
212         items[n][len] = '\0';
213         p = next;
214     }
215     TRACE("got '%s','%s','%s','%s','%s'\n",
216                         items[0], items[1], items[2], items[3], items[4]);
217
218     /* check root key */
219     if (!strcasecmp(items[0], "HKCR"))
220         hkey = HKEY_CLASSES_ROOT;
221     else
222     if (!strcasecmp(items[0], "HKCU"))
223         hkey = HKEY_CURRENT_USER;
224     else
225     if (!strcasecmp(items[0], "HKLM"))
226         hkey = HKEY_LOCAL_MACHINE;
227     else
228     if (!strcasecmp(items[0], "HKU"))
229         hkey = HKEY_USERS;
230     else
231     { /* HKR ? -> relative to key passed to GenInstallEx */
232         FIXME("unsupported regkey '%s'\n", items[0]);
233         goto regfailed;
234     }
235
236     if (RegOpenKeyA(hkey, items[1], &hsubkey) != ERROR_SUCCESS)
237         goto regfailed;
238
239     if (RegQueryValueExA(hsubkey, items[2], NULL, &dwType, buffer, &buflen)
240                 != ERROR_SUCCESS)
241         goto regfailed;
242     goto done;
243
244 regfailed:
245     if (buffer) strcpy(buffer, items[4]); /* I don't care about buflen */
246 done:
247     for (n=0; n < 5; n++)
248         HeapFree(heap, 0, items[n]);
249     if (buffer)
250         TRACE("return '%s'\n", buffer);
251     return TRUE;
252 }
253
254 static LPSTR SETUPX_GetSections(LPCSTR filename)
255 {
256     LPSTR buf = NULL;
257     DWORD len = 1024, res;
258
259     do {
260         buf = HeapReAlloc(GetProcessHeap(), 0, buf, len);
261         res = GetPrivateProfileStringA(NULL, NULL, NULL, buf, len, filename);
262         len *= 2;
263     } while ((!res) && (len < 1048576));
264     if (!res)
265     {
266         HeapFree(GetProcessHeap(), 0, buf);
267         return NULL;
268     }
269     return buf;
270 }
271
272 static LPSTR SETUPX_GetSectionEntries(LPCSTR filename, LPCSTR section)
273 {
274     LPSTR buf = NULL;
275     DWORD len = 1024, res;
276
277     do {
278         buf = HeapReAlloc(GetProcessHeap(), 0, buf, len);
279         res = GetPrivateProfileSectionA(section, buf, len, filename);
280         len *= 2;
281     } while ((!res) && (len < 1048576));
282     if (!res)
283     {
284         HeapFree(GetProcessHeap(), 0, buf);
285         return NULL;
286     }
287     return buf;
288 }
289
290
291 /***********************************************************************
292  *              InstallHinfSection (SETUPX.527)
293  *
294  * hwnd = parent window
295  * hinst = instance of SETUPX.DLL
296  * lpszCmdLine = e.g. "DefaultInstall 132 C:\MYINSTALL\MYDEV.INF"
297  * Here "DefaultInstall" is the .inf file section to be installed (optional).
298  * The 132 value is made of the HOW_xxx flags and sometimes 128 (-> setupx16.h).
299  *
300  * nCmdShow = nCmdShow of CreateProcess
301  */
302 RETERR16 WINAPI InstallHinfSection16( HWND16 hwnd, HINSTANCE16 hinst, LPCSTR lpszCmdLine, INT16 nCmdShow)
303 {
304     LPSTR *pSub;
305     DWORD count;
306     HINF16 hInf = 0;
307     RETERR16 res = OK, tmp;
308     WORD wFlags;
309     BOOL reboot = FALSE;
310
311     TRACE("(%04x, %04x, %s, %d);\n", hwnd, hinst, lpszCmdLine, nCmdShow);
312
313     pSub = SETUPX_GetSubStrings((LPSTR)lpszCmdLine, ' ');
314
315     count = *(DWORD *)pSub;
316     if (count < 2) /* invalid number of arguments ? */
317         goto end;
318     if (IpOpen16(*(pSub+count), &hInf) != OK)
319     {
320         res = ERROR_FILE_NOT_FOUND; /* yes, correct */
321         goto end;
322     }
323     if (VcpOpen16(NULL, 0))
324         goto end;
325     if (GenInstall16(hInf, *(pSub+count-2), GENINSTALL_DO_ALL) != OK)
326         goto end;
327     wFlags = atoi(*(pSub+count-1)) & ~128;
328     switch (wFlags)
329     {
330         case HOW_ALWAYS_SILENT_REBOOT:
331         case HOW_SILENT_REBOOT:
332             reboot = TRUE;
333             break;
334         case HOW_ALWAYS_PROMPT_REBOOT:
335         case HOW_PROMPT_REBOOT:
336             if (MessageBoxA(hwnd, "You must restart Wine before the new settings will take effect.\n\nDo you want to exit Wine now ?", "Systems Settings Change", MB_YESNO|MB_ICONQUESTION) == IDYES)
337                 reboot = TRUE;
338             break;
339         default:
340             ERR("invalid flags %d !\n", wFlags);
341             goto end;
342     }
343
344     res = OK;
345 end:
346     tmp = VcpClose16(VCPFL_ALL, NULL);
347     if (tmp != OK)
348         res = tmp;
349     tmp = IpClose16(hInf);
350     if (tmp != OK)
351         res = tmp;
352     SETUPX_FreeSubStrings(pSub);
353     if (reboot)
354     {
355         /* FIXME: we should have a means of terminating all wine + wineserver */
356         MESSAGE("Program or user told me to restart. Exiting Wine...\n");
357         ExitProcess(1);
358     }
359
360     return res;
361 }
362
363 typedef struct
364 {
365     LPCSTR RegValName;
366     LPCSTR StdString; /* fallback string; sub dir of windows directory */
367 } LDID_DATA;
368
369 static const LDID_DATA LDID_Data[34] =
370 {
371     { /* 0 (LDID_NULL) -- not defined */
372         NULL,
373         NULL
374     },
375     { /* 1 (LDID_SRCPATH) = source of installation. hmm, what to do here ? */
376         "SourcePath", /* hmm, does SETUPX have to care about updating it ?? */
377         NULL
378     },
379     { /* 2 (LDID_SETUPTEMP) = setup temp dir */
380         "SetupTempDir",
381         NULL
382     },
383     { /* 3 (LDID_UNINSTALL) = uninstall backup dir */
384         "UninstallDir",
385         NULL
386     },
387     { /* 4 (LDID_BACKUP) = backup dir */
388         "BackupDir",
389         NULL
390     },
391     { /* 5 (LDID_SETUPSCRATCH) = setup scratch dir */
392         "SetupScratchDir",
393         NULL
394     },
395     { /* 6 -- not defined */
396         NULL,
397         NULL
398     },
399     { /* 7 -- not defined */
400         NULL,
401         NULL
402     },
403     { /* 8 -- not defined */
404         NULL,
405         NULL
406     },
407     { /* 9 -- not defined */
408         NULL,
409         NULL
410     },
411     { /* 10 (LDID_WIN) = windows dir */
412         "WinDir",
413         ""
414     },
415     { /* 11 (LDID_SYS) = system dir */
416         "SysDir",
417         NULL /* call GetSystemDirectory() instead */
418     },
419     { /* 12 (LDID_IOS) = IOSubSys dir */
420         NULL, /* FIXME: registry string ? */
421         "SYSTEM\\IOSUBSYS"
422     },
423     { /* 13 (LDID_CMD) = COMMAND dir */
424         NULL, /* FIXME: registry string ? */
425         "COMMAND"
426     },
427     { /* 14 (LDID_CPL) = control panel dir */
428         NULL,
429         ""
430     },
431     { /* 15 (LDID_PRINT) = windows printer dir */
432         NULL,
433         "SYSTEM" /* correct ?? */
434     },
435     { /* 16 (LDID_MAIL) = destination mail dir */
436         NULL,
437         ""
438     },
439     { /* 17 (LDID_INF) = INF dir */
440         "SetupScratchDir", /* correct ? */
441         "INF"
442     },
443     { /* 18 (LDID_HELP) = HELP dir */
444         NULL, /* ??? */
445         "HELP"
446     },
447     { /* 19 (LDID_WINADMIN) = Admin dir */
448         "WinAdminDir",
449         ""
450     },
451     { /* 20 (LDID_FONTS) = Fonts dir */
452         NULL, /* ??? */
453         "FONTS"
454     },
455     { /* 21 (LDID_VIEWERS) = Viewers */
456         NULL, /* ??? */
457         "SYSTEM\\VIEWERS"
458     },
459     { /* 22 (LDID_VMM32) = VMM32 dir */
460         NULL, /* ??? */
461         "SYSTEM\\VMM32"
462     },
463     { /* 23 (LDID_COLOR) = ICM dir */
464         "ICMPath",
465         "SYSTEM\\COLOR"
466     },
467     { /* 24 (LDID_APPS) = root of boot drive ? */
468         "AppsDir",
469         "C:\\"
470     },
471     { /* 25 (LDID_SHARED) = shared dir */
472         "SharedDir",
473         ""
474     },
475     { /* 26 (LDID_WINBOOT) = Windows boot dir */
476         "WinBootDir",
477         ""
478     },
479     { /* 27 (LDID_MACHINE) = machine specific files */
480         "MachineDir",
481         NULL
482     },
483     { /* 28 (LDID_HOST_WINBOOT) = Host Windows boot dir */
484         "HostWinBootDir",
485         NULL
486     },
487     { /* 29 -- not defined */
488         NULL,
489         NULL
490     },
491     { /* 30 (LDID_BOOT) = Root of boot drive */
492         "BootDir",
493         NULL
494     },
495     { /* 31 (LDID_BOOT_HOST) = Root of boot drive host */
496         "BootHost",
497         NULL
498     },
499     { /* 32 (LDID_OLD_WINBOOT) = subdir of root */
500         "OldWinBootDir",
501         NULL
502     },
503     { /* 33 (LDID_OLD_WIN) = old win dir */
504         "OldWinDir",
505         NULL
506     }
507     /* the rest (34-38) isn't too interesting, so I'll forget about it */
508 };
509
510 /*
511  * LDD  == Logical Device Descriptor
512  * LDID == Logical Device ID
513  *
514  * The whole LDD/LDID business might go into a separate file named
515  * ldd.c.
516  * At the moment I don't know what the hell these functions are really doing.
517  * That's why I added reporting stubs.
518  * The only thing I do know is that I need them for the LDD/LDID infrastructure.
519  * That's why I implemented them in a way that's suitable for my purpose.
520  */
521 static LDD_LIST *pFirstLDD = NULL;
522
523 static BOOL std_LDDs_done = FALSE;
524
525 void SETUPX_CreateStandardLDDs(void)
526 {
527     HKEY hKey = 0;
528     WORD n;
529     DWORD type, len;
530     LOGDISKDESC_S ldd;
531     char buffer[MAX_PATH];
532
533     /* has to be here, otherwise loop */
534     std_LDDs_done = TRUE;
535
536     RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup", &hKey);
537
538     for (n=0; n < sizeof(LDID_Data)/sizeof(LDID_DATA); n++)
539     {
540         buffer[0] = '\0';
541
542         len = MAX_PATH;
543         if ( (hKey) && (LDID_Data[n].RegValName)
544         &&   (RegQueryValueExA(hKey, LDID_Data[n].RegValName,
545                 NULL, &type, buffer, &len) == ERROR_SUCCESS)
546         &&   (type == REG_SZ) )
547         {
548             TRACE("found value '%s' for LDID %d\n", buffer, n);
549         }
550         else
551         switch(n)
552         {
553             case LDID_SRCPATH:
554                 FIXME("LDID_SRCPATH: what exactly do we have to do here ?\n");
555                 strcpy(buffer, "X:\\FIXME");
556                 break;
557             case LDID_SYS:
558                 GetSystemDirectoryA(buffer, MAX_PATH);
559                 break;
560             case LDID_APPS:
561             case LDID_MACHINE:
562             case LDID_HOST_WINBOOT:
563             case LDID_BOOT:
564             case LDID_BOOT_HOST:
565                 strcpy(buffer, "C:\\");
566                 break;
567             default:
568                 if (LDID_Data[n].StdString)
569                 {
570                     DWORD len = GetWindowsDirectoryA(buffer, MAX_PATH);
571                     LPSTR p;
572                     p = buffer + len;
573                     *p++ = '\\';
574                     strcpy(p, LDID_Data[n].StdString);
575                 }
576                 break;
577         }
578         if (buffer[0])
579         {
580             INIT_LDD(ldd, n);
581             ldd.pszPath = buffer;
582             TRACE("LDID %d -> '%s'\n", ldd.ldid, ldd.pszPath);
583             CtlSetLdd16(&ldd);
584         }
585     }
586     if (hKey) RegCloseKey(hKey);
587 }
588
589 /***********************************************************************
590  * CtlDelLdd            (SETUPX.37)
591  *
592  * RETURN
593  *   ERR_VCP_LDDINVALID if ldid < LDID_ASSIGN_START.
594  */
595 RETERR16 SETUPX_DelLdd(LOGDISKID16 ldid)
596 {
597     LDD_LIST *pCurr, *pPrev = NULL;
598
599     TRACE("(%d)\n", ldid);
600
601     if (!std_LDDs_done)
602         SETUPX_CreateStandardLDDs();
603
604     if (ldid < LDID_ASSIGN_START)
605         return ERR_VCP_LDDINVALID;
606
607     pCurr = pFirstLDD;
608     /* search until we find the appropriate LDD or hit the end */
609     while ((pCurr != NULL) && (ldid > pCurr->pldd->ldid))
610     {
611          pPrev = pCurr;
612          pCurr = pCurr->next;
613     }
614     if ( (pCurr == NULL) /* hit end of list */
615       || (ldid != pCurr->pldd->ldid) )
616         return ERR_VCP_LDDFIND; /* correct ? */
617
618     /* ok, found our victim: eliminate it */
619
620     if (pPrev)
621         pPrev->next = pCurr->next;
622
623     if (pCurr == pFirstLDD)
624         pFirstLDD = NULL;
625     HeapFree(GetProcessHeap(), 0, pCurr);
626
627     return OK;
628 }
629
630 /***********************************************************************
631  *              CtlDelLdd (SETUPX.37)
632  */
633 RETERR16 WINAPI CtlDelLdd16(LOGDISKID16 ldid)
634 {
635     FIXME("(%d); - please report to a.mohr@mailto.de !!!\n", ldid);
636     return SETUPX_DelLdd(ldid);
637 }
638
639 /***********************************************************************
640  * CtlFindLdd           (SETUPX.35)
641  *
642  * doesn't check pldd ptr validity: crash (W98SE)
643  *
644  * RETURN
645  *   ERR_VCP_LDDINVALID if pldd->cbSize != structsize
646  *   1 in all other cases ??
647  *
648  */
649 RETERR16 WINAPI CtlFindLdd16(LPLOGDISKDESC pldd)
650 {
651     LDD_LIST *pCurr, *pPrev = NULL;
652
653     TRACE("(%p)\n", pldd);
654
655     if (!std_LDDs_done)
656         SETUPX_CreateStandardLDDs();
657
658     if (pldd->cbSize != sizeof(LOGDISKDESC_S))
659         return ERR_VCP_LDDINVALID;
660
661     pCurr = pFirstLDD;
662     /* search until we find the appropriate LDD or hit the end */
663     while ((pCurr != NULL) && (pldd->ldid > pCurr->pldd->ldid))
664     {
665         pPrev = pCurr;
666         pCurr = pCurr->next;
667     }
668     if ( (pCurr == NULL) /* hit end of list */
669       || (pldd->ldid != pCurr->pldd->ldid) )
670         return ERR_VCP_LDDFIND; /* correct ? */
671
672     memcpy(pldd, pCurr->pldd, pldd->cbSize);
673     /* hmm, we probably ought to strcpy() the string ptrs here */
674
675     return 1; /* what is this ?? */
676 }
677
678 /***********************************************************************
679  * CtlSetLdd                    (SETUPX.33)
680  *
681  * Set an LDD entry.
682  *
683  * RETURN
684  *   ERR_VCP_LDDINVALID if pldd.cbSize != sizeof(LOGDISKDESC_S)
685  *
686  */
687 RETERR16 WINAPI CtlSetLdd16(LPLOGDISKDESC pldd)
688 {
689     LDD_LIST *pCurr, *pPrev = NULL;
690     LPLOGDISKDESC pCurrLDD;
691     HANDLE heap;
692     BOOL is_new = FALSE;
693
694     TRACE("(%p)\n", pldd);
695
696     if (!std_LDDs_done)
697         SETUPX_CreateStandardLDDs();
698
699     if (pldd->cbSize != sizeof(LOGDISKDESC_S))
700         return ERR_VCP_LDDINVALID;
701
702     heap = GetProcessHeap();
703     pCurr = pFirstLDD;
704     /* search until we find the appropriate LDD or hit the end */
705     while ((pCurr != NULL) && (pldd->ldid > pCurr->pldd->ldid))
706     {
707          pPrev = pCurr;
708          pCurr = pCurr->next;
709     }
710     if (pCurr == NULL) /* hit end of list */
711     {
712         is_new = TRUE;
713         pCurr = HeapAlloc(heap, 0, sizeof(LDD_LIST));
714         pCurr->pldd = HeapAlloc(heap, 0, sizeof(LOGDISKDESC_S));
715         pCurr->next = NULL;
716         pCurrLDD = pCurr->pldd;
717     }
718     else
719     {
720         pCurrLDD = pCurr->pldd;
721         if (pCurrLDD->pszPath)          HeapFree(heap, 0, pCurrLDD->pszPath);
722         if (pCurrLDD->pszVolLabel)      HeapFree(heap, 0, pCurrLDD->pszVolLabel);
723         if (pCurrLDD->pszDiskName)      HeapFree(heap, 0, pCurrLDD->pszDiskName);
724     }
725
726     memcpy(pCurrLDD, pldd, sizeof(LOGDISKDESC_S));
727
728     if (pldd->pszPath)
729     {
730         pCurrLDD->pszPath = HeapAlloc( heap, 0, strlen(pldd->pszPath)+1 );
731         strcpy( pCurrLDD->pszPath, pldd->pszPath );
732     }
733     if (pldd->pszVolLabel)
734     {
735         pCurrLDD->pszVolLabel = HeapAlloc( heap, 0, strlen(pldd->pszVolLabel)+1 );
736         strcpy( pCurrLDD->pszVolLabel, pldd->pszVolLabel );
737     }
738     if (pldd->pszDiskName)
739     {
740         pCurrLDD->pszDiskName = HeapAlloc( heap, 0, strlen(pldd->pszDiskName)+1 );
741         strcpy( pCurrLDD->pszDiskName, pldd->pszDiskName );
742     }
743
744     if (is_new) /* link into list */
745     {
746         if (pPrev)
747         {
748             pCurr->next = pPrev->next;
749             pPrev->next = pCurr;
750         }
751         if (!pFirstLDD)
752             pFirstLDD = pCurr;
753     }
754
755     return OK;
756 }
757
758
759 /***********************************************************************
760  * CtlAddLdd            (SETUPX.36)
761  *
762  * doesn't check pldd ptr validity: crash (W98SE)
763  *
764  */
765 static LOGDISKID16 ldid_to_add = LDID_ASSIGN_START;
766 RETERR16 WINAPI CtlAddLdd16(LPLOGDISKDESC pldd)
767 {
768     pldd->ldid = ldid_to_add++;
769     return CtlSetLdd16(pldd);
770 }
771
772 /***********************************************************************
773  * CtlGetLdd            (SETUPX.34)
774  *
775  * doesn't check pldd ptr validity: crash (W98SE)
776  * What the !@#$%&*( is the difference between CtlFindLdd() and CtlGetLdd() ??
777  *
778  * RETURN
779  *   ERR_VCP_LDDINVALID if pldd->cbSize != structsize
780  *
781  */
782 static RETERR16 SETUPX_GetLdd(LPLOGDISKDESC pldd)
783 {
784     LDD_LIST *pCurr, *pPrev = NULL;
785
786     if (!std_LDDs_done)
787         SETUPX_CreateStandardLDDs();
788
789     if (pldd->cbSize != sizeof(LOGDISKDESC_S))
790         return ERR_VCP_LDDINVALID;
791
792     pCurr = pFirstLDD;
793     /* search until we find the appropriate LDD or hit the end */
794     while ((pCurr != NULL) && (pldd->ldid > pCurr->pldd->ldid))
795     {
796          pPrev = pCurr;
797          pCurr = pCurr->next;
798     }
799     if (pCurr == NULL) /* hit end of list */
800         return ERR_VCP_LDDFIND; /* correct ? */
801
802     memcpy(pldd, pCurr->pldd, pldd->cbSize);
803     /* hmm, we probably ought to strcpy() the string ptrs here */
804
805     return OK;
806 }
807
808 /**********************************************************************/
809
810 RETERR16 WINAPI CtlGetLdd16(LPLOGDISKDESC pldd)
811 {
812     FIXME("(%p); - please report to a.mohr@mailto.de !!!\n", pldd);
813     return SETUPX_GetLdd(pldd);
814 }
815
816 /***********************************************************************
817  *              CtlGetLddPath           (SETUPX.38)
818  *
819  * Gets the path of an LDD.
820  * No crash if szPath == NULL.
821  * szPath has to be at least MAX_PATH_LEN bytes long.
822  * RETURN
823  *   ERR_VCP_LDDUNINIT if LDD for LDID not found.
824  */
825 RETERR16 WINAPI CtlGetLddPath16(LOGDISKID16 ldid, LPSTR szPath)
826 {
827     TRACE("(%d, %p);\n", ldid, szPath);
828
829     if (szPath)
830     {
831         LOGDISKDESC_S ldd;
832         INIT_LDD(ldd, ldid);
833         if (CtlFindLdd16(&ldd) == ERR_VCP_LDDFIND)
834             return ERR_VCP_LDDUNINIT;
835         SETUPX_GetLdd(&ldd);
836         strcpy(szPath, ldd.pszPath);
837         TRACE("ret '%s' for LDID %d\n", szPath, ldid);
838     }
839     return OK;
840 }
841
842 /***********************************************************************
843  *              CtlSetLddPath           (SETUPX.508)
844  *
845  * Sets the path of an LDD.
846  * Creates LDD for LDID if not existing yet.
847  */
848 RETERR16 WINAPI CtlSetLddPath16(LOGDISKID16 ldid, LPSTR szPath)
849 {
850     LOGDISKDESC_S ldd;
851     TRACE("(%d, '%s');\n", ldid, szPath);
852
853     INIT_LDD(ldd, ldid);
854     ldd.pszPath = szPath;
855     return CtlSetLdd16(&ldd);
856 }
857
858 /*
859  * Find the value of a custom LDID in a .inf file
860  * e.g. for 49301:
861  * 49300,49301=ProgramFilesDir,5
862  * -- profile section lookup -->
863  * [ProgramFilesDir]
864  * HKLM,"Software\Microsoft\Windows\CurrentVersion","ProgramFilesDir",,"%24%"
865  * -- GenFormStrWithoutPlaceHolders16 -->
866  * HKLM,"Software\Microsoft\Windows\CurrentVersion","ProgramFilesDir",,"C:\"
867  * -- registry lookup -->
868  * C:\Program Files (or C:\ if not found in registry)
869  *
870  * FIXME:
871  * - maybe we ought to add a caching array for speed ? - I don't care :)
872  * - not sure whether the processing is correct - sometimes there are equal
873  *   LDIDs for both install and removal sections.
874  * - probably the whole function can be removed as installers add that on their
875  *   own
876  */
877 static BOOL SETUPX_AddCustomLDID(int ldid, INT16 hInf)
878 {
879     char ldidstr[6];
880     LPSTR sectionbuf = NULL, entrybuf = NULL, regsectionbuf = NULL;
881     LPCSTR filename;
882     LPSTR pSec, pEnt, pEqual, p, *pSub = NULL;
883     BOOL ret = FALSE;
884     char buffer[MAX_PATH];
885     LOGDISKDESC_S ldd;
886
887     sprintf(ldidstr, "%d", ldid);
888     filename = IP_GetFileName(hInf);
889     if (!(sectionbuf = SETUPX_GetSections(filename)))
890     {
891         ERR("couldn't get sections !\n");
892         return FALSE;
893     }
894     for (pSec=sectionbuf; *pSec; pSec += strlen(pSec)+1)
895     {
896         if (!(entrybuf = SETUPX_GetSectionEntries(filename, pSec)))
897         {
898             ERR("couldn't get section entries !\n");
899             goto end;
900         }
901         for (pEnt=entrybuf; *pEnt; pEnt += strlen(pEnt)+1)
902         {
903             if (strstr(pEnt, ldidstr))
904             {
905                 pEqual = strchr(pEnt, '=');
906                 if (!pEqual) /* crippled entry ?? */
907                     continue;
908
909                 /* make sure we found the LDID on left side of the equation */
910                 if (pEnt+strlen(ldidstr) <= pEqual)
911                 { /* found */
912
913                     /* but we don't want entries in the strings section */
914                     if (!strcasecmp(pSec, "Strings")) continue;
915                     p = pEqual+1;
916                     goto found;
917                 }
918             }
919         }
920     }
921     goto end;
922 found:
923     TRACE("found entry '%s'\n", p);
924     pSub = SETUPX_GetSubStrings(p, ',');
925     if (*(DWORD *)pSub > 2)
926     {
927         ERR("malformed entry '%s' ?\n", p);
928         goto end;
929     }
930     TRACE("found section '%s'\n", *(pSub+1));
931     /* FIXME: what are the optional flags at the end of an entry used for ?? */
932
933     /* get the location of the registry key from that section */
934     if (!(regsectionbuf = SETUPX_GetSectionEntries(filename, *(pSub+1))))
935     {
936         ERR("couldn't get registry section entries !\n");
937         goto end;
938     }
939     /* sectionbuf is > 1024 bytes anyway, so use it */
940     GenFormStrWithoutPlaceHolders16(sectionbuf, regsectionbuf, hInf);
941     ret = SETUPX_LookupRegistryString(sectionbuf, buffer, MAX_PATH);
942     TRACE("return '%s'\n", buffer);
943     INIT_LDD(ldd, ldid);
944     ldd.pszPath = buffer;
945     CtlSetLdd16(&ldd);
946 end:
947     SETUPX_FreeSubStrings(pSub);
948     if (sectionbuf)     HeapFree(GetProcessHeap(), 0, sectionbuf);
949     if (entrybuf)       HeapFree(GetProcessHeap(), 0, entrybuf);
950     if (regsectionbuf)  HeapFree(GetProcessHeap(), 0, regsectionbuf);
951     return ret;
952 }
953
954 /*
955  * Translate a logical disk identifier (LDID) into its string representation
956  * I'm afraid this can be totally replaced by CtlGetLddPath().
957  */
958 static BOOL SETUPX_IP_TranslateLDID(int ldid, LPSTR *p, HINF16 hInf)
959 {
960     BOOL handled = FALSE;
961     LOGDISKDESC_S ldd;
962
963     ldd.cbSize = sizeof(LOGDISKDESC_S);
964     ldd.ldid = ldid;
965     if (CtlFindLdd16(&ldd) == ERR_VCP_LDDFIND)
966     {
967         /* hmm, it seems the installers already do the work for us
968          * (by calling CtlSetLddPath) that SETUPX_AddCustomLDID
969          * is supposed to do. Grmbl ;-)
970          * Well, I'll leave it here anyway, but print error... */
971         ERR("hmm, LDID %d not registered yet !?\n", ldid);
972         handled = SETUPX_AddCustomLDID(ldid, hInf);
973     }
974     else
975         handled = TRUE;
976
977     SETUPX_GetLdd(&ldd);
978
979     if (!handled)
980     {
981         FIXME("What is LDID %d ??\n", ldid);
982         *p = "LDID_FIXME";
983     }
984     else
985         *p = ldd.pszPath;
986
987     return handled;
988 }
989
990 /***********************************************************************
991  *              GenFormStrWithoutPlaceHolders (SETUPX.103)
992  *
993  * ought to be pretty much implemented, I guess...
994  */
995 void WINAPI GenFormStrWithoutPlaceHolders16( LPSTR szDst, LPCSTR szSrc, HINF16 hInf)
996 {
997     LPCSTR pSrc = szSrc, pSrcEnd = szSrc + strlen(szSrc);
998     LPSTR pDst = szDst, p, pPHBegin;
999     int count;
1000
1001     TRACE("(%p, '%s', %04x);\n", szDst, szSrc, hInf);
1002     while (pSrc < pSrcEnd)
1003     {
1004         p = strchr(pSrc, '%');
1005         if (p)
1006         {
1007             count = (int)p - (int)pSrc;
1008             strncpy(pDst, pSrc, count);
1009             pSrc += count;
1010             pDst += count;
1011             pPHBegin = p+1;
1012             p = strchr(pPHBegin, '%');
1013             if (p)
1014             {
1015                 char placeholder[80]; /* that really ought to be enough ;) */
1016                 int ldid;
1017                 BOOL done = TRUE;
1018                 count = (int)p - (int)pPHBegin;
1019                 strncpy(placeholder, pPHBegin, count);
1020                 placeholder[count] = '\0';
1021                 ldid = atoi(placeholder);
1022                 if (ldid)
1023                 {
1024                     LPSTR p;
1025                     done = SETUPX_IP_TranslateLDID(ldid, &p, hInf);
1026                     strcpy(pDst, p);
1027                     if (done)
1028                         pDst += strlen(pDst);
1029                 }
1030                 else
1031                 { /* hmm, string placeholder. Need to look up
1032                      in the [strings] section of the hInf */
1033                     DWORD ret;
1034                     char buf[256]; /* long enough ? */
1035
1036                     ret = GetPrivateProfileStringA("strings", placeholder, "",
1037                                         buf, 256, IP_GetFileName(hInf));
1038                     if (ret)
1039                     {
1040                         strcpy(pDst, buf);
1041                         pDst += strlen(buf);
1042                     }
1043                     else
1044                     {
1045                         ERR("placeholder string '%s' not found !\n", placeholder);
1046                         done = FALSE;
1047                     }
1048                 }
1049                 if (!done)
1050                 { /* copy raw placeholder string over */
1051                     count = (int)p - (int)pPHBegin + 2;
1052                     strncpy(pDst, pPHBegin-1, count);
1053                     pDst += count;
1054
1055                 }
1056                 pSrc = p+1;
1057                 continue;
1058             }
1059         }
1060
1061         /* copy the remaining source string over */
1062         strncpy(pDst, pSrc, (int)pSrcEnd - (int)pSrc + 1);
1063         break;
1064     }
1065     TRACE("ret '%s'\n", szDst);
1066 }
1067
1068 /*
1069  * Copy all items in a CopyFiles entry over to the destination
1070  *
1071  * - VNLP_xxx is what is given as flags for a .INF CopyFiles section
1072  */
1073 static BOOL SETUPX_CopyFiles(LPSTR *pSub, HINF16 hInf)
1074 {
1075     BOOL bSingle = FALSE;
1076     unsigned int n;
1077     LPCSTR filename = IP_GetFileName(hInf);
1078     LPSTR pCopyEntry;
1079     char pDstStr[MAX_PATH];
1080     LPSTR pSrcDir, pDstDir;
1081     LPSTR pFileEntries, p;
1082     WORD ldid;
1083     LOGDISKDESC_S ldd;
1084     LPSTR *pSubFile;
1085     LPSTR pSrcFile, pDstFile;
1086     WORD flag;
1087
1088     for (n=0; n < *(DWORD *)pSub; n++)
1089     {
1090         pCopyEntry = *(pSub+1+n);
1091         if (*pCopyEntry == '@')
1092         {
1093             pCopyEntry++;
1094             bSingle = TRUE;
1095         }
1096         else
1097             bSingle = FALSE;
1098
1099         /* get source directory for that entry */
1100         INIT_LDD(ldd, LDID_SRCPATH);
1101         SETUPX_GetLdd(&ldd);
1102         pSrcDir = ldd.pszPath;
1103
1104         /* get destination directory for that entry */
1105         if (!(GetPrivateProfileStringA("DestinationDirs", pCopyEntry, "",
1106                                         pDstStr, sizeof(pDstStr), filename)))
1107         {
1108             /* hmm, not found; try the default entry */
1109             if (!(GetPrivateProfileStringA("DestinationDirs", "DefaultDestDir", "", pDstStr, sizeof(pDstStr), filename)))
1110             {
1111                 WARN("DefaultDestDir not found.\n");
1112                 continue;
1113             }
1114         }
1115
1116         /* translate destination dir if given as LDID */
1117         ldid = atoi(pDstStr);
1118         if (ldid)
1119         {
1120             if (!(SETUPX_IP_TranslateLDID(ldid, &pDstDir, hInf)))
1121                 continue;
1122         }
1123         else
1124             pDstDir = pDstStr;
1125
1126         /* now that we have the destination dir, register file copying */
1127
1128         if (bSingle)
1129         {
1130             VcpQueueCopy16(pCopyEntry, pCopyEntry, pSrcDir, pDstDir, LDID_SRCPATH, ldid ? ldid : 0xffff, 0, VFNL_COPY, 0);
1131             return TRUE;
1132         }
1133
1134         /* entry wasn't a single file, so let's iterate over section */
1135         pFileEntries = SETUPX_GetSectionEntries(filename, pCopyEntry);
1136         if (pFileEntries == NULL) continue;
1137         for (p=pFileEntries; *p; p +=strlen(p)+1)
1138         {
1139             pSubFile = SETUPX_GetSubStrings(p, ',');
1140             pSrcFile = *(pSubFile+1);
1141             pDstFile = (*(DWORD *)pSubFile > 1) ? *(pSubFile+2) : pSrcFile;
1142             TRACE("copying file '%s\\%s' to '%s\\%s'\n", pSrcDir, pSrcFile, pDstDir, pDstFile);
1143             flag = 0;
1144             if (*(DWORD *)pSubFile > 2)
1145             {
1146                 if ((flag = atoi(*(pSubFile+3)))) /* ah, flag */
1147                 {
1148                     if (flag & 0x2c)
1149                     FIXME("VNLP_xxx flag %d not handled yet.\n", flag);
1150                 }
1151                 else
1152                 {
1153                     FIXME("temp file name '%s' given. Need to register in wininit.ini !\n", *(pSubFile+3));
1154                     /* we probably need to set VIRTNODE.vhstrDstFinalName to
1155                      * the final destination name, and the temp name is merely
1156                      * the copy destination */
1157                 }
1158             }
1159             VcpQueueCopy16(pSrcFile, pDstFile, pSrcDir, pDstDir, LDID_SRCPATH, ldid ? ldid : 0xffff, 0, VFNL_COPY|flag, 0);
1160             SETUPX_FreeSubStrings(pSubFile);
1161         }
1162     }
1163
1164     return TRUE;
1165 }
1166
1167 /***********************************************************************
1168  *              GenInstall (SETUPX.101)
1169  *
1170  * generic installer function for .INF file sections
1171  *
1172  * This is not perfect - patch whenever you can !
1173  *
1174  * wFlags == GENINSTALL_DO_xxx
1175  * e.g. NetMeeting:
1176  * first call GENINSTALL_DO_REGSRCPATH | GENINSTALL_DO_FILES,
1177  * second call GENINSTALL_DO_LOGCONFIG | CFGAUTO | INI2REG | REG | INI
1178  */
1179 RETERR16 WINAPI GenInstall16(HINF16 hInfFile, LPCSTR szInstallSection, WORD wFlags)
1180 {
1181     LPCSTR filename = IP_GetFileName(hInfFile);
1182     LPSTR pEntries, p, pEnd;
1183     DWORD len;
1184     LPSTR *pSub;
1185
1186     FIXME("(%04x, '%s', %04x), semi-stub. Please implement additional operations here !\n", hInfFile, szInstallSection, wFlags);
1187     pEntries = SETUPX_GetSectionEntries(filename, szInstallSection);
1188     if (!pEntries)
1189     {
1190         ERR("couldn't find entries for section '%s' !\n", szInstallSection);
1191         return ERR_IP_SECT_NOT_FOUND;
1192     }
1193     for (p=pEntries; *p; p +=strlen(p)+1)
1194     {
1195         pEnd = strchr(p, '=');
1196         if (!pEnd) continue;
1197         pSub = SETUPX_GetSubStrings(pEnd+1, ','); /* split entries after the '=' */
1198         SETUPX_IsolateSubString(&p, &pEnd);
1199         len = (int)pEnd - (int)p;
1200
1201         if (wFlags & GENINSTALL_DO_FILES)
1202         {
1203             if (!strncasecmp(p, "CopyFiles", len))
1204             {
1205                 SETUPX_CopyFiles(pSub, hInfFile);
1206                 continue;
1207             }
1208 #if IMPLEMENT_THAT
1209             else
1210             if (!strncasecmp(p, "DelFiles", len))
1211             {
1212                 SETUPX_DelFiles(filename, szInstallSection, pSub);
1213                 continue;
1214             }
1215 #endif
1216         }
1217         if (wFlags & GENINSTALL_DO_INI)
1218         {
1219 #if IMPLEMENT_THAT
1220             if (!strncasecmp(p, "UpdateInis", len))
1221             {
1222                 SETUPX_UpdateInis(filename, szInstallSection, pSub);
1223                 continue;
1224             }
1225 #endif
1226         }
1227         if (wFlags & GENINSTALL_DO_REG)
1228         {
1229 #if IMPLEMENT_THAT
1230             /* probably use SUReg*() functions here */
1231             if (!strncasecmp(p, "AddReg", len))
1232             {
1233                 SETUPX_AddReg(filename, szInstallSection, pSub);
1234                 continue;
1235             }
1236             else
1237             if (!strncasecmp(p, "DelReg", len))
1238             {
1239                 SETUPX_DelReg(filename, szInstallSection, pSub);
1240                 continue;
1241             }
1242 #endif
1243         }
1244
1245         SETUPX_FreeSubStrings(pSub);
1246     }
1247     HeapFree(GetProcessHeap(), 0, pEntries);
1248     return OK;
1249 }