xref: /plan9/sys/src/cmd/gs/src/dwsetup.cpp (revision 593dc095aefb2a85c828727bbfa9da139a49bdf4)
1 /* Copyright (C) 1999-2003, Ghostgum Software Pty Ltd.  All rights reserved.
2 
3   This software is provided AS-IS with no warranty, either express or
4   implied.
5 
6   This software is distributed under license and may not be copied,
7   modified or distributed except as expressly authorized under the terms
8   of the license contained in the file LICENSE in this distribution.
9 
10   For more information about licensing, please refer to
11   http://www.ghostscript.com/licensing/. For information on
12   commercial licensing, go to http://www.artifex.com/licensing/ or
13   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
14   San Rafael, CA  94903, U.S.A., +1(415)492-9861.
15 */
16 
17 // $Id: dwsetup.cpp,v 1.11 2005/03/04 21:58:55 ghostgum Exp $
18 //
19 //
20 // This is the setup program for Win32 AFPL Ghostscript
21 //
22 // The starting point is a self extracting zip archive
23 // with the following contents:
24 //   setupgs.exe
25 //   uninstgs.exe
26 //   filelist.txt      (contains list of program files)
27 //   fontlist.txt      (contains list of font files)
28 //   gs#.##\*          (files listed in filelist.txt)
29 //   fonts\*           (fonts listed in fontlist.txt)
30 // This is the same as the zip file created by Aladdin Enterprises,
31 // with the addition of setupgs.exe, uninstgs.exe, filelist.txt and
32 // fontlist.txt.
33 //
34 // The first line of the files filelist.txt and fontlist.txt
35 // contains the uninstall name to be used.
36 // The second line contains name of the main directory where
37 // uninstall log files are to be placed.
38 // Subsequent lines contain files to be copied (but not directories).
39 // For example, filelist.txt might contain:
40 //   AFPL Ghostscript 6.50
41 //   gs6.50
42 //   gs6.50\bin\gsdll32.dll
43 //   gs6.50\lib\gs_init.ps
44 // The file fontlist.txt might contain:
45 //   AFPL Ghostscript Fonts
46 //   fonts
47 //   fonts\n019003l.pfb
48 //   fonts\n019023l.pfb
49 //
50 // The default install directory is c:\gs.
51 // The default Start Menu Folder is Ghostscript.
52 // These are set in the resources.
53 // The setup program will create the following uninstall log files
54 //   c:\gs\gs#.##\uninstal.txt
55 //   c:\gs\fonts\uninstal.txt
56 // The uninstall program (accessed through control panel) will not
57 // remove directories nor will it remove itself.
58 //
59 // If the install directory is the same as the current file
60 // location, no files will be copied, but the existence of each file
61 // will be checked.  This allows the archive to be unzipped, then
62 // configured in its current location.  Running the uninstall will not
63 // remove uninstgs.exe, setupgs.exe, filelist.txt or fontlist.txt.
64 
65 
66 #define STRICT
67 #include <windows.h>
68 #include <shellapi.h>
69 #include <objbase.h>
70 #include <shlobj.h>
71 #include <stdio.h>
72 #include <direct.h>
73 
74 #ifdef MAX_PATH
75 #define MAXSTR MAX_PATH
76 #else
77 #define MAXSTR 256
78 #endif
79 
80 #include "dwsetup.h"
81 #include "dwinst.h"
82 
83 extern "C" {
84 typedef HRESULT (WINAPI *PFN_SHGetFolderPath)(
85   HWND hwndOwner,
86   int nFolder,
87   HANDLE hToken,
88   DWORD dwFlags,
89   LPSTR pszPath);
90 
91 typedef BOOL (WINAPI *PFN_SHGetSpecialFolderPath)(
92   HWND hwndOwner,
93   LPTSTR lpszPath,
94   int nFolder,
95   BOOL fCreate);
96 }
97 
98 //#define DEBUG
99 
100 #define UNINSTALLPROG "uninstgs.exe"
101 
102 
103 /////////////////////////////////
104 // Globals
105 
106 CInstall cinst;
107 
108 // TRUE = Place Start Menu items in All Users.
109 // FALSE = Current User
110 BOOL g_bUseCommon;
111 
112 // TRUE = Destination is the same as Source, so don't copy files.
113 BOOL g_bNoCopy;
114 
115 // Source directory, usually a temporary directory created by
116 // unzip self extractor.
117 CHAR g_szSourceDir[MAXSTR];
118 
119 // Target directory for program and fonts.
120 // Default loaded from resources
121 CHAR g_szTargetDir[MAXSTR];
122 
123 // Target Group for shortcut.
124 // Default loaded from resources
125 CHAR g_szTargetGroup[MAXSTR];
126 
127 // Setup application name, loaded from resources
128 CHAR g_szAppName[MAXSTR];
129 
130 BOOL g_bInstallFonts = TRUE;
131 BOOL g_bCJKFonts = FALSE;
132 BOOL g_bAllUsers = FALSE;
133 
134 
135 HWND g_hMain;		// Main install dialog
136 HWND g_hWndText;	// Install log dialog
137 HINSTANCE g_hInstance;
138 
139 // If a directory is listed on the command line, g_bBatch will
140 // be TRUE and a silent install will occur.
141 BOOL g_bBatch = FALSE;
142 
143 BOOL g_bQuit = FALSE;	// TRUE = Get out of message loop.
144 BOOL g_bError = FALSE;	// TRUE = Install was not successful
145 BOOL is_winnt = FALSE;	// Disable "All Users" if not NT.
146 
147 #ifdef _WIN64
148 #define DLGRETURN INT_PTR
149 #else
150 #define DLGRETURN BOOL
151 #endif
152 
153 // Prototypes
154 DLGRETURN CALLBACK MainDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
155 void gs_addmess_count(const char *str, int count);
156 void gs_addmess(const char *str);
157 void gs_addmess_update(void);
158 BOOL init();
159 BOOL install_all();
160 BOOL install_prog();
161 BOOL install_fonts();
162 BOOL make_filelist(int argc, char *argv[]);
163 int get_font_path(char *path, unsigned int pathlen);
164 BOOL write_cidfmap(const char *gspath, const char *cidpath);
165 BOOL GetProgramFiles(LPTSTR path);
166 
167 
168 //////////////////////////////////////////////////////////////////////
169 // Entry point
170 //////////////////////////////////////////////////////////////////////
171 
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)172 int APIENTRY WinMain(HINSTANCE hInstance,
173                      HINSTANCE hPrevInstance,
174                      LPSTR     lpCmdLine,
175                      int       nCmdShow)
176 {
177 	MSG msg;
178 	g_hInstance = hInstance;
179 
180 	if (!init()) {
181 		MessageBox(HWND_DESKTOP, "Initialisation failed",
182 			g_szAppName, MB_OK);
183 		return 1;
184 	}
185 
186 	if (!g_bBatch) {
187 		while (GetMessage(&msg, (HWND)NULL, 0, 0)) {
188 			if (!IsDialogMessage(g_hWndText, &msg) &&
189 				!IsDialogMessage(g_hMain, &msg)) {
190 				TranslateMessage(&msg);
191 				DispatchMessage(&msg);
192 			}
193 		}
194 		DestroyWindow(g_hMain);
195 	}
196 
197 	return (g_bError ? 1 : 0);
198 }
199 
200 
201 
202 
203 //////////////////////////////////////////////////////////////////////
204 // Text log window
205 //////////////////////////////////////////////////////////////////////
206 
207 
208 #define TWLENGTH 32768
209 #define TWSCROLL 1024
210 char twbuf[TWLENGTH];
211 int twend;
212 
213 // Modeless Dialog Box
214 DLGRETURN CALLBACK
TextWinDlgProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)215 TextWinDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
216 {
217     switch(message) {
218 	case WM_INITDIALOG:
219 		EnableWindow(g_hMain, FALSE);
220 		return TRUE;
221 	case WM_COMMAND:
222 		switch(LOWORD(wParam)) {
223 		case IDC_TEXTWIN_COPY:
224 			{HGLOBAL hglobal;
225 			LPSTR p;
226 			DWORD result;
227 			int start, end;
228 			result = SendDlgItemMessage(hwnd, IDC_TEXTWIN_MLE, EM_GETSEL, (WPARAM)0, (LPARAM)0);
229 			start = LOWORD(result);
230 			end   = HIWORD(result);
231 			if (start == end) {
232 				start = 0;
233 				end = twend;
234 			}
235 			hglobal = GlobalAlloc(GHND | GMEM_SHARE, end-start+1);
236 			if (hglobal == (HGLOBAL)NULL) {
237 				MessageBeep(-1);
238 				return(FALSE);
239 			}
240 			p = (char *)GlobalLock(hglobal);
241 			if (p == (LPSTR)NULL) {
242 				MessageBeep(-1);
243 				return(FALSE);
244 			}
245 			lstrcpyn(p, twbuf+start, end-start);
246 			GlobalUnlock(hglobal);
247 			OpenClipboard(hwnd);
248 			EmptyClipboard();
249 			SetClipboardData(CF_TEXT, hglobal);
250 			CloseClipboard();
251 			}
252 			break;
253 		case IDCANCEL:
254 			g_bQuit = TRUE;
255 			DestroyWindow(hwnd);
256 			return TRUE;
257 		}
258 		break;
259 	case WM_CLOSE:
260 			DestroyWindow(hwnd);
261 			return TRUE;
262 	case WM_DESTROY:
263 			g_bQuit = TRUE;
264 			g_hWndText = (HWND)NULL;
265 			EnableWindow(g_hMain, TRUE);
266 			PostQuitMessage(0);
267 			break;
268 	}
269 	return FALSE;
270 }
271 
272 
273 
274 // Add string to log window
275 void
gs_addmess_count(const char * str,int count)276 gs_addmess_count(const char *str, int count)
277 {
278 	const char *s;
279 	char *p;
280 	int i, lfcount;
281 	MSG msg;
282 
283 	// we need to add \r after each \n, so count the \n's
284 	lfcount = 0;
285 	s = str;
286 	for (i=0; i<count; i++) {
287 		if (*s == '\n')
288 			lfcount++;
289 		s++;
290 	}
291 
292 	if (count + lfcount >= TWSCROLL)
293 		return;		// too large
294 	if (count + lfcount + twend >= TWLENGTH-1) {
295 		// scroll buffer
296 		twend -= TWSCROLL;
297 		memmove(twbuf, twbuf+TWSCROLL, twend);
298 	}
299 	p = twbuf+twend;
300 	for (i=0; i<count; i++) {
301 		if (*str == '\n') {
302 			*p++ = '\r';
303 		}
304 		*p++ = *str++;
305 	}
306 	twend += (count + lfcount);
307 	*(twbuf+twend) = '\0';
308 
309 
310 	// Update the dialog box
311 	if (g_bBatch)
312 		return;
313 
314 	gs_addmess_update();
315 	while (PeekMessage(&msg, (HWND)NULL, 0, 0, PM_REMOVE)) {
316 		if (!IsDialogMessage(g_hWndText, &msg) &&
317 			!IsDialogMessage(g_hMain, &msg)) {
318 			TranslateMessage(&msg);
319 			DispatchMessage(&msg);
320 		}
321 	}
322 }
323 
324 void
gs_addmess(const char * str)325 gs_addmess(const char *str)
326 {
327 	gs_addmess_count(str, lstrlen(str));
328 
329 }
330 
331 
332 void
gs_addmess_update(void)333 gs_addmess_update(void)
334 {
335 	HWND hwndmess = g_hWndText;
336 
337 	if (g_bBatch)
338 		return;
339 
340 	if (IsWindow(hwndmess)) {
341 		HWND hwndtext = GetDlgItem(hwndmess, IDC_TEXTWIN_MLE);
342 		DWORD linecount;
343 		SendMessage(hwndtext, WM_SETREDRAW, FALSE, 0);
344 		SetDlgItemText(hwndmess, IDC_TEXTWIN_MLE, twbuf);
345 		linecount = SendDlgItemMessage(hwndmess, IDC_TEXTWIN_MLE, EM_GETLINECOUNT, (WPARAM)0, (LPARAM)0);
346 		SendDlgItemMessage(hwndmess, IDC_TEXTWIN_MLE, EM_LINESCROLL, (WPARAM)0, (LPARAM)linecount-14);
347 		SendMessage(hwndtext, WM_SETREDRAW, TRUE, 0);
348 		InvalidateRect(hwndtext, (LPRECT)NULL, TRUE);
349 		UpdateWindow(hwndtext);
350 	}
351 }
352 
353 
354 //////////////////////////////////////////////////////////////////////
355 // Browse dialog box
356 //////////////////////////////////////////////////////////////////////
357 
358 // nasty GLOBALS
359 char szFolderName[MAXSTR];
360 char szDirName[MAXSTR];
361 
362 DLGRETURN CALLBACK
DirDlgProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)363 DirDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
364 {
365 	WORD notify_message;
366 
367 	switch(message) {
368 	case WM_INITDIALOG:
369 		DlgDirList(hwnd, szDirName, IDC_FILES, IDC_FOLDER,
370 			DDL_DRIVES | DDL_DIRECTORY);
371 		SetDlgItemText(hwnd, IDC_TARGET, szFolderName);
372 		return FALSE;
373     case WM_COMMAND:
374 		notify_message = HIWORD(wParam);
375 		switch (LOWORD(wParam)) {
376 		case IDC_FILES:
377 			if (notify_message == LBN_DBLCLK) {
378 				CHAR szPath[MAXSTR];
379 				DlgDirSelectEx(hwnd, szPath, sizeof(szPath), IDC_FILES);
380 				DlgDirList(hwnd, szPath, IDC_FILES, IDC_FOLDER,
381 					DDL_DRIVES | DDL_DIRECTORY);
382 			}
383 			return FALSE;
384 		case IDOK:
385 			GetDlgItemText(hwnd, IDC_FOLDER, szDirName, sizeof(szDirName));
386 			GetDlgItemText(hwnd, IDC_TARGET, szFolderName, sizeof(szFolderName));
387 			EndDialog(hwnd, TRUE);
388 			return TRUE;
389 		case IDCANCEL:
390 			EndDialog(hwnd, FALSE);
391 			return TRUE;
392 		}
393 		return FALSE;
394 	}
395 	return FALSE;
396 }
397 
398 
399 //////////////////////////////////////////////////////////////////////
400 // Initialisation and Main dialog box
401 //////////////////////////////////////////////////////////////////////
402 
403 void
message_box(const char * str)404 message_box(const char *str)
405 {
406 	MessageBox(HWND_DESKTOP, str, g_szAppName, MB_OK);
407 }
408 
409 
410 BOOL
init()411 init()
412 {
413 	DWORD dwVersion = GetVersion();
414 	// get source directory
415 	GetCurrentDirectory(sizeof(g_szSourceDir), g_szSourceDir);
416 
417 	// load strings
418 	LoadString(g_hInstance, IDS_APPNAME, g_szAppName, sizeof(g_szAppName));
419 	LoadString(g_hInstance, IDS_TARGET_GROUP,
420 		g_szTargetGroup, sizeof(g_szTargetGroup));
421 
422 	if (LOBYTE(LOWORD(dwVersion)) < 4) {
423         MessageBox(HWND_DESKTOP,
424 			"This install program needs Windows 4.0 or later",
425 			g_szAppName, MB_OK);
426 		return FALSE;
427 	}
428 	if ( (HIWORD(dwVersion) & 0x8000) == 0)
429 		is_winnt = TRUE;
430 
431 
432 	cinst.SetMessageFunction(message_box);
433 
434 #define MAXCMDTOKENS 128
435 
436 	int argc;
437 	LPSTR argv[MAXCMDTOKENS];
438 	LPSTR p;
439 	char command[256];
440 	char *args;
441 	char *d, *e;
442 
443 	p = GetCommandLine();
444 
445 	argc = 0;
446 	args = (char *)malloc(lstrlen(p)+1);
447 	if (args == (char *)NULL)
448 		return 1;
449 
450 	// Parse command line handling quotes.
451 	d = args;
452 	while (*p) {
453 		// for each argument
454 
455 		if (argc >= MAXCMDTOKENS - 1)
456 			break;
457 
458 		e = d;
459 		while ((*p) && (*p != ' ')) {
460 			if (*p == '\042') {
461 				// Remove quotes, skipping over embedded spaces.
462 				// Doesn't handle embedded quotes.
463 				p++;
464 				while ((*p) && (*p != '\042'))
465 					*d++ =*p++;
466 			}
467 			else
468 				*d++ = *p;
469 			if (*p)
470 				p++;
471 		}
472 		*d++ = '\0';
473 		argv[argc++] = e;
474 
475 		while ((*p) && (*p == ' '))
476 			p++;	// Skip over trailing spaces
477 	}
478 	argv[argc] = NULL;
479 
480 	if (strlen(argv[0]) == 0) {
481 		GetModuleFileName(g_hInstance, command, sizeof(command)-1);
482 		argv[0] = command;
483 	}
484 
485 	if (argc > 2) {
486 		// Probably creating filelist.txt
487 		return make_filelist(argc, argv);
488 	}
489 
490 
491 	// check if batch mode requested
492 	// get location of target directory from command line as argv[1]
493 	if (argc == 2) {
494 		strncpy(g_szTargetDir, argv[1], sizeof(g_szTargetDir));
495 		g_bBatch = TRUE;
496 		if (is_winnt)
497 			g_bAllUsers = TRUE;
498 	}
499 	if (g_bBatch) {
500 		if (!install_all()) {
501 			// display log showing error
502 			g_bBatch = FALSE;
503 			g_hWndText = CreateDialogParam(g_hInstance,
504 				MAKEINTRESOURCE(IDD_TEXTWIN),
505 				(HWND)HWND_DESKTOP, TextWinDlgProc,
506 				(LPARAM)NULL);
507 			gs_addmess_update();
508 		}
509 		return TRUE;
510 	}
511 
512 	// Interactive setup
513 	if (!GetProgramFiles(g_szTargetDir))
514 	    strcpy(g_szTargetDir, "C:\\Program Files");
515 	strcat(g_szTargetDir, "\\");
516 	LoadString(g_hInstance, IDS_TARGET_DIR,
517 	    g_szTargetDir+strlen(g_szTargetDir),
518 	    sizeof(g_szTargetDir)-strlen(g_szTargetDir));
519 
520 	// main dialog box
521 	g_hMain = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_MAIN), (HWND)NULL, MainDlgProc, (LPARAM)NULL);
522 	// centre dialog on screen
523 	int width = GetSystemMetrics(SM_CXFULLSCREEN);
524 	int height = GetSystemMetrics(SM_CYFULLSCREEN);
525 	RECT rect;
526 	GetWindowRect(g_hMain, &rect);
527 	MoveWindow(g_hMain, (width - (rect.right - rect.left))/2,
528 		(height - (rect.bottom - rect.top))/2,
529 		(rect.right - rect.left),
530 		(rect.bottom - rect.top), FALSE);
531 
532 	// initialize targets
533 	cinst.SetMessageFunction(message_box);
534 	if (!cinst.Init(g_szSourceDir, "filelist.txt"))
535 		return FALSE;
536 
537 	SetDlgItemText(g_hMain, IDC_TARGET_DIR, g_szTargetDir);
538 	SetDlgItemText(g_hMain, IDC_TARGET_GROUP, g_szTargetGroup);
539 	SetDlgItemText(g_hMain, IDC_PRODUCT_NAME, cinst.GetUninstallName());
540 	SendDlgItemMessage(g_hMain, IDC_INSTALL_FONTS, BM_SETCHECK, BST_CHECKED, 0);
541 	ShowWindow(g_hMain, SW_SHOWNORMAL);
542 
543 	return (g_hMain != (HWND)NULL); /* success */
544 }
545 
546 
547 // Main Modeless Dialog Box
548 DLGRETURN CALLBACK
MainDlgProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)549 MainDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
550 {
551 	switch(message) {
552 	case WM_INITDIALOG:
553 		EnableWindow(GetDlgItem(hwnd, IDC_ALLUSERS), is_winnt);
554 		return TRUE;
555 	case WM_COMMAND:
556 		switch(LOWORD(wParam)) {
557 		case IDC_README:
558 			{
559 			char buf[MAXSTR];
560 			sprintf(buf, "%s\\%s\\doc\\Readme.htm", g_szSourceDir,
561 				cinst.GetMainDir());
562 			ShellExecute(hwnd, NULL, buf, NULL, g_szSourceDir,
563 				SW_SHOWNORMAL);
564 			}
565 			return TRUE;
566 		case IDC_BROWSE_DIR:
567 			{ char dir[MAXSTR];
568 			char *p;
569 			GetDlgItemText(hwnd, IDC_TARGET_DIR, dir, sizeof(dir));
570 			strcpy(szDirName, dir);
571 			if ( (p = strrchr(szDirName, '\\')) != (char *)NULL ) {
572 				strcpy(szFolderName, p+1);
573 				if (p == szDirName+2)
574 					p++;	// step over c:\   //
575 				*p = '\0';
576 			}
577 			else {
578 				strcpy(szDirName, "c:\\");
579 				strcpy(szFolderName, dir);
580 			}
581 			if (DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIRDLG),
582 				hwnd, DirDlgProc)) {
583 				strcpy(dir, szDirName);
584 				if (strlen(dir) && (dir[strlen(dir)-1] != '\\'))
585 					strcat(dir, "\\");
586 				strcat(dir, szFolderName);
587 				SetDlgItemText(hwnd, IDC_TARGET_DIR, dir);
588 			}
589 			}
590 			return TRUE;
591 		case IDC_BROWSE_GROUP:
592 			{ char dir[MAXSTR];
593 			char programs[MAXSTR];
594 			char *p;
595 			GetDlgItemText(hwnd, IDC_TARGET_GROUP, dir, sizeof(dir));
596 			cinst.GetPrograms(
597 				SendDlgItemMessage(hwnd, IDC_ALLUSERS,
598 				BM_GETCHECK, 0, 0) == BST_CHECKED,
599 				programs, sizeof(programs));
600 			strcpy(szDirName, programs);
601 			strcpy(szFolderName, dir);
602 			if (DialogBox(g_hInstance, MAKEINTRESOURCE(IDD_DIRDLG),
603 				hwnd, DirDlgProc)) {
604 				strcpy(dir, szFolderName);
605 				p = szDirName;
606 				if (strnicmp(szDirName, programs,
607 					strlen(programs)) == 0) {
608 					p += strlen(programs);
609 					if (*p == '\\')
610 						p++;
611 					strcpy(dir, p);
612 					if (strlen(dir) &&
613 						(dir[strlen(dir)-1] != '\\'))
614 						strcat(dir, "\\");
615 					strcat(dir, szFolderName);
616 				}
617 				SetDlgItemText(hwnd, IDC_TARGET_GROUP, dir);
618 			}
619 			}
620 			return TRUE;
621 		case IDCANCEL:
622 			PostQuitMessage(0);
623 			return TRUE;
624 		case IDC_INSTALL:
625 			GetDlgItemText(hwnd, IDC_TARGET_DIR,
626 				g_szTargetDir, sizeof(g_szTargetDir));
627 			GetDlgItemText(hwnd, IDC_TARGET_GROUP,
628 				g_szTargetGroup, sizeof(g_szTargetGroup));
629 			g_bInstallFonts = (SendDlgItemMessage(g_hMain,
630 				IDC_INSTALL_FONTS, BM_GETCHECK, 0, 0)
631 				== BST_CHECKED);
632 			g_bCJKFonts = (SendDlgItemMessage(g_hMain,
633 				IDC_CJK_FONTS, BM_GETCHECK, 0, 0)
634 				== BST_CHECKED);
635 			g_bAllUsers = (SendDlgItemMessage(hwnd,
636 				IDC_ALLUSERS, BM_GETCHECK, 0, 0
637 				) == BST_CHECKED);
638 
639 			// install log dialog box
640 			g_hWndText = CreateDialogParam(g_hInstance,
641 				MAKEINTRESOURCE(IDD_TEXTWIN),
642 				(HWND)hwnd, TextWinDlgProc, (LPARAM)NULL);
643 			EnableWindow(GetDlgItem(hwnd, IDC_INSTALL), FALSE);
644 			if (install_all())
645 				PostQuitMessage(0);
646 			return TRUE;
647 		default:
648 			return(FALSE);
649 		}
650 		case WM_CLOSE:
651 			PostQuitMessage(0);
652 			return TRUE;
653     }
654     return FALSE;
655 }
656 
657 // install program and files
658 BOOL
install_all()659 install_all()
660 {
661 	gs_addmess("Source Directory=");
662 	gs_addmess(g_szSourceDir);
663 	gs_addmess("\n");
664 	gs_addmess("Target Directory=");
665 	gs_addmess(g_szTargetDir);
666 	gs_addmess("\n");
667 	gs_addmess("Target Shell Folder=");
668 	gs_addmess(g_szTargetGroup);
669 	gs_addmess("\n");
670 	gs_addmess(g_bAllUsers ? "  All users\n" : "  Current user\n");
671 
672 	if (stricmp(g_szSourceDir, g_szTargetDir) == 0) {
673 		// Don't copy files
674 		if (!g_bBatch)
675 			if (::MessageBox(g_hWndText, "Install location is the same as the current file location.  No files will be copied.", g_szAppName, MB_OKCANCEL)
676 				!= IDOK) {
677 				return FALSE;
678 			}
679 		g_bNoCopy = TRUE;
680 	}
681 
682 
683 	if (g_bQuit)
684 		return FALSE;
685 
686 	if (!install_prog()) {
687 		cinst.CleanUp();
688 		g_bError = TRUE;
689 		return FALSE;
690 	}
691 	if (g_bInstallFonts && !install_fonts()) {
692 		cinst.CleanUp();
693 		g_bError = TRUE;
694 		return FALSE;
695 	}
696 
697 	gs_addmess("Install successful\n");
698 
699 	// show start menu folder
700 	if (!g_bBatch) {
701 		char szFolder[MAXSTR];
702 		szFolder[0] = '\0';
703 		cinst.GetPrograms(g_bAllUsers, szFolder, sizeof(szFolder));
704 		strcat(szFolder, "\\");
705 		strcat(szFolder, g_szTargetGroup);
706 		ShellExecute(HWND_DESKTOP, "open", szFolder,
707 			NULL, NULL, SW_SHOWNORMAL);
708 	}
709 
710 #ifdef DEBUG
711 	return FALSE;
712 #endif
713 
714 	return TRUE;
715 }
716 
717 BOOL
install_prog()718 install_prog()
719 {
720 	char *regkey1 = "AFPL Ghostscript";
721 	char regkey2[16];
722 	char szDLL[MAXSTR];
723 	char szLIB[MAXSTR+MAXSTR];
724 	char szProgram[MAXSTR];
725 	char szArguments[MAXSTR];
726 	char szDescription[MAXSTR];
727 	char szDotVersion[MAXSTR];
728 	char szPlatformSuffix[MAXSTR];
729 	const char *pSuffix = "";
730 
731 	if (g_bQuit)
732 		return FALSE;
733 
734 	cinst.SetMessageFunction(gs_addmess);
735 	cinst.SetTargetDir(g_szTargetDir);
736 	cinst.SetTargetGroup(g_szTargetGroup);
737 	cinst.SetAllUsers(g_bAllUsers);
738 	if (!cinst.Init(g_szSourceDir, "filelist.txt"))
739 		return FALSE;
740 
741 	// Get GS version number
742 	gs_addmess("Installing Program...\n");
743 	int nGSversion = 0;
744 	const char *p = cinst.GetMainDir();
745 	while (*p && !isdigit(*p))	// skip over "gs" prefix
746 		p++;
747 	if (strlen(p) == 4)
748 		nGSversion = (p[0]-'0')*100 + (p[2]-'0')*10 + (p[3]-'0');
749 	else if (strlen(p) == 3)
750 		nGSversion = (p[0]-'0')*100 + (p[2]-'0')*10;
751         strncpy(szDotVersion, p, sizeof(szDotVersion));
752 	strncpy(regkey2, szDotVersion, sizeof(regkey2));
753 
754 	// copy files
755 	if (!cinst.InstallFiles(g_bNoCopy, &g_bQuit)) {
756 		gs_addmess("Program install failed\n");
757 		return FALSE;
758 	}
759 
760 	if (g_bQuit)
761 		return FALSE;
762 
763 	// write registry entries
764 	gs_addmess("Updating Registry\n");
765 	if (!cinst.UpdateRegistryBegin()) {
766 		gs_addmess("Failed to begin registry update\n");
767 		return FALSE;
768 	}
769 	if (!cinst.UpdateRegistryKey(regkey1, regkey2)) {
770 		gs_addmess("Failed to open/create registry application key\n");
771 		return FALSE;
772 	}
773 	strcpy(szDLL, g_szTargetDir);
774 	strcat(szDLL, "\\");
775 	strcat(szDLL, cinst.GetMainDir());
776 	strcat(szDLL, "\\bin\\gsdll32.dll");
777 	if (!cinst.UpdateRegistryValue(regkey1, regkey2, "GS_DLL", szDLL)) {
778 		gs_addmess("Failed to add registry value\n");
779 		return FALSE;
780 	}
781 	strcpy(szLIB, g_szTargetDir);
782 	strcat(szLIB, "\\");
783 	strcat(szLIB, cinst.GetMainDir());
784 	strcat(szLIB, "\\lib;");
785 	strcat(szLIB, g_szTargetDir);
786 	strcat(szLIB, "\\fonts;");
787 	strcat(szLIB, g_szTargetDir);
788 	strcat(szLIB, "\\");
789 	strcat(szLIB, cinst.GetMainDir());
790 	strcat(szLIB, "\\Resource");
791 	if (g_bCJKFonts) {
792 	    strcat(szLIB, ";");
793 	    get_font_path(szLIB+strlen(szLIB), sizeof(szLIB)-strlen(szLIB)-1);
794 	}
795 	if (!cinst.UpdateRegistryValue(regkey1, regkey2, "GS_LIB", szLIB)) {
796 		gs_addmess("Failed to add registry value\n");
797 		return FALSE;
798 	}
799 	if (!cinst.UpdateRegistryEnd()) {
800 		gs_addmess("Failed to end registry update\n");
801 		return FALSE;
802 	}
803 	if (g_bQuit)
804 		return FALSE;
805 
806 	// Add Start Menu items
807 	gs_addmess("Adding Start Menu items\n");
808 
809         memset(szPlatformSuffix, 0, sizeof(szPlatformSuffix));
810 	if (GetProgramFiles(szPlatformSuffix)) {
811 	    /* If ProgramFiles has a suffix like " (x86)" then use
812 	     * it for Start menu entries to distinguish between
813 	     * 32-bit and 64-bit programs.
814 	     */
815 	    for (pSuffix = szPlatformSuffix; *pSuffix; pSuffix++)
816 		if ((pSuffix[0] == ' ') && (pSuffix[1] == '('))
817 		    break;
818 	}
819 	else {
820 	    pSuffix = "";
821 	}
822 
823 
824 	if (!cinst.StartMenuBegin()) {
825 		gs_addmess("Failed to begin Start Menu update\n");
826 		return FALSE;
827 	}
828 	strcpy(szProgram, g_szTargetDir);
829 	strcat(szProgram, "\\");
830 	strcat(szProgram, cinst.GetMainDir());
831 	strcat(szProgram, "\\bin\\gswin32.exe");
832 	strcpy(szArguments, "\042-I");
833 	strcat(szArguments, szLIB);
834 	strcat(szArguments, "\042");
835 	sprintf(szDescription, "Ghostscript %s%s", szDotVersion, pSuffix);
836 	if (!cinst.StartMenuAdd(szDescription, szProgram, szArguments)) {
837 		gs_addmess("Failed to add Start Menu item\n");
838 		return FALSE;
839 	}
840 	strcpy(szProgram, g_szTargetDir);
841 	strcat(szProgram, "\\");
842 	strcat(szProgram, cinst.GetMainDir());
843 	strcat(szProgram, "\\doc\\Readme.htm");
844 	sprintf(szDescription, "Ghostscript Readme %s%s",
845 		szDotVersion, pSuffix);
846 	if (!cinst.StartMenuAdd(szDescription, szProgram, NULL)) {
847 		gs_addmess("Failed to add Start Menu item\n");
848 		return FALSE;
849 	}
850 	if (!cinst.StartMenuEnd()) {
851 		gs_addmess("Failed to end Start Menu update\n");
852 		return FALSE;
853 	}
854 
855         /* Create lib/cidfmap */
856 	if (g_bCJKFonts) {
857 		FILE *f;
858 		char szCIDFmap[MAXSTR];
859 		char szCIDFmap_bak[MAXSTR];
860 		char szGSPATH[MAXSTR];
861 
862 		/* backup old cidfmap */
863 		strcpy(szCIDFmap, g_szTargetDir);
864 		strcat(szCIDFmap, "\\");
865 		strcat(szCIDFmap, cinst.GetMainDir());
866 		strcat(szCIDFmap, "\\lib\\cidfmap");
867 		strcpy(szCIDFmap_bak, szCIDFmap);
868 		strcat(szCIDFmap_bak, ".bak");
869 		gs_addmess("Backing up\n  ");
870 		gs_addmess(szCIDFmap);
871 		gs_addmess("\nto\n  ");
872 		gs_addmess(szCIDFmap_bak);
873 		gs_addmess("\n");
874 		rename(szCIDFmap, szCIDFmap_bak);
875 
876 		/* mark backup for uninstall */
877 		cinst.AppendFileNew(szCIDFmap_bak);
878 
879 		/* write new cidfmap */
880 		gs_addmess("Writing cidfmap\n   ");
881 		gs_addmess(szCIDFmap);
882 		gs_addmess("\n");
883 		strcpy(szGSPATH, g_szTargetDir);
884 		strcat(szGSPATH, "\\");
885 		strcat(szGSPATH, cinst.GetMainDir());
886 		if (!write_cidfmap(szGSPATH, szCIDFmap)) {
887 			gs_addmess("Failed to write cidfmap\n");
888 			return FALSE;
889 		}
890 	}
891 
892 	// consolidate logs into one uninstall file
893 	if (cinst.MakeLog()) {
894 		// add uninstall entry for "Add/Remove Programs"
895 		gs_addmess("Adding uninstall program\n");
896 		if (!cinst.WriteUninstall(UNINSTALLPROG, g_bNoCopy)) {
897 			gs_addmess("Failed to write uninstall entry\n");
898 			return FALSE;
899 		}
900 	}
901 	else {
902 		gs_addmess("Failed to write uninstall log\n");
903 		// If batch install, files might be on a server
904 		// in a write protected directory.
905 		// Don't return an error for batch install.
906 		if (g_bBatch)
907 			return TRUE;
908 		return FALSE;
909 	}
910 
911 	gs_addmess("Program install successful\n");
912 	return TRUE;
913 }
914 
915 
916 BOOL
install_fonts()917 install_fonts()
918 {
919 	cinst.SetMessageFunction(gs_addmess);
920 	cinst.SetTargetDir(g_szTargetDir);
921 	cinst.SetTargetGroup(g_szTargetGroup);
922 	cinst.SetAllUsers(g_bAllUsers);
923 	if (!cinst.Init(g_szSourceDir, "fontlist.txt"))
924 		return FALSE;
925 
926 	// copy files
927 	if (!cinst.InstallFiles(g_bNoCopy, &g_bQuit)) {
928 		gs_addmess("Font install failed\n");
929 		return FALSE;
930 	}
931 
932 	if (g_bQuit)
933 		return FALSE;
934 
935 	if (g_bNoCopy) {
936 		// Don't write uninstall log or entry
937 		// since we didn't copy any files.
938 		cinst.CleanUp();
939 	}
940 	else {
941 		// consolidate logs into one uninstall file
942 		if (cinst.MakeLog()) {
943 			// add uninstall entry for "Add/Remove Programs"
944 			gs_addmess("Adding uninstall program\n");
945 			if (!cinst.WriteUninstall(UNINSTALLPROG, g_bNoCopy)) {
946 				gs_addmess("Failed to write uninstall entry\n");
947 				return FALSE;
948 			}
949 		}
950 		else {
951 			gs_addmess("Failed to write uninstall log\n");
952 			// If batch install, files might be on a server
953 			// in a write protected directory.
954 			// Don't return an error for batch install.
955 			if (g_bBatch)
956 				return TRUE;
957 			return FALSE;
958 		}
959 	}
960 
961 	gs_addmess("Font install successful\n");
962 	return TRUE;
963 }
964 
965 //////////////////////////////////////////////////////////////////////
966 // Create lib/cidfmap based on installed fonts
967 //////////////////////////////////////////////////////////////////////
968 
969 /* Get the path to enumerate for fonts */
970 int
get_font_path(char * path,unsigned int pathlen)971 get_font_path(char *path, unsigned int pathlen)
972 {
973     int i;
974     int len = GetWindowsDirectory(path, pathlen);
975     if (len == 0)
976        return -1;
977     if (pathlen - strlen(path) < 8)
978        return -1;
979     strncat(path, "/fonts", pathlen - strlen(path) - 7);
980     for (i = strlen(path)-1; i >= 0; i--)
981        if (path[i] == '\\')
982            path[i] = '/';
983     return len;
984 }
985 
write_cidfmap(const char * gspath,const char * cidpath)986 BOOL write_cidfmap(const char *gspath, const char *cidpath)
987 {
988     char fontpath[MAXSTR];
989     char buf[4*MAXSTR];
990     STARTUPINFO siStartInfo;
991     PROCESS_INFORMATION piProcInfo;
992 
993     get_font_path(fontpath, sizeof(fontpath)-1);
994 
995     strcpy(buf, "\042");
996     strcat(buf, gspath);
997     strcat(buf, "\\bin\\gswin32c.exe\042 -q -dBATCH \042-sFONTDIR=");
998     strcat(buf, fontpath);
999     strcat(buf, "\042 \042");
1000     strcat(buf, "-sCIDFMAP=");
1001     strcat(buf, cidpath);
1002     strcat(buf, "\042 \042");
1003     strcat(buf, gspath);
1004     strcat(buf, "\\lib\\mkcidfm.ps\042");
1005 
1006     siStartInfo.cb = sizeof(STARTUPINFO);
1007     siStartInfo.lpReserved = NULL;
1008     siStartInfo.lpDesktop = NULL;
1009     siStartInfo.lpTitle = NULL;  /* use executable name as title */
1010     siStartInfo.dwX = siStartInfo.dwY = CW_USEDEFAULT;		/* ignored */
1011     siStartInfo.dwXSize = siStartInfo.dwYSize = CW_USEDEFAULT;	/* ignored */
1012     siStartInfo.dwXCountChars = 80;
1013     siStartInfo.dwYCountChars = 25;
1014     siStartInfo.dwFillAttribute = 0;			/* ignored */
1015     siStartInfo.dwFlags = STARTF_USESHOWWINDOW;
1016     siStartInfo.wShowWindow = SW_HIDE;
1017     siStartInfo.cbReserved2 = 0;
1018     siStartInfo.lpReserved2 = NULL;
1019     siStartInfo.hStdInput = NULL;
1020     siStartInfo.hStdOutput = NULL;
1021     siStartInfo.hStdError = NULL;
1022 
1023     /* Create the child process. */
1024     if (!CreateProcess(NULL,
1025         (char *)buf,  /* command line                       */
1026         NULL,          /* process security attributes        */
1027         NULL,          /* primary thread security attributes */
1028         FALSE,         /* handles are not inherited          */
1029         0,             /* creation flags                     */
1030         NULL,          /* environment                        */
1031         NULL,          /* use parent's current directory     */
1032         &siStartInfo,  /* STARTUPINFO pointer                */
1033         &piProcInfo))  /* receives PROCESS_INFORMATION  */
1034 	    return FALSE;
1035 
1036     /* We don't care if ghostscript fails, so just return */
1037 
1038     CloseHandle(piProcInfo.hProcess);
1039     CloseHandle(piProcInfo.hThread);
1040 
1041     return TRUE;
1042 }
1043 
1044 
1045 //////////////////////////////////////////////////////////////////////
1046 // Create file list
1047 //////////////////////////////////////////////////////////////////////
1048 
1049 FILE *fList;
1050 
1051 typedef int (*PFN_dodir)(const char *name);
1052 
1053 /* Called once for each directory */
1054 int
dodir(const char * filename)1055 dodir(const char *filename)
1056 {
1057     return 0;
1058 }
1059 
1060 /* Called once for each file */
1061 int
dofile(const char * filename)1062 dofile(const char *filename)
1063 {
1064     if (fList != (FILE *)NULL) {
1065 		fputs(filename, fList);
1066 		fputs("\n", fList);
1067     }
1068 
1069     return 0;
1070 }
1071 
1072 
1073 /* Walk through directory 'path', calling dodir() for given directory
1074  * and dofile() for each file.
1075  * If recurse=1, recurse into subdirectories, calling dodir() for
1076  * each directory.
1077  */
1078 int
dirwalk(char * path,int recurse,PFN_dodir dodir,PFN_dodir dofile)1079 dirwalk(char *path, int recurse, PFN_dodir dodir, PFN_dodir dofile)
1080 {
1081 	WIN32_FIND_DATA find_data;
1082 	HANDLE find_handle;
1083 	char pattern[MAXSTR];	/* orig pattern + modified pattern */
1084 	char base[MAXSTR];
1085 	char name[MAXSTR];
1086 	BOOL bMore = TRUE;
1087 	char *p;
1088 
1089 
1090 	if (path) {
1091 		strcpy(pattern, path);
1092 		if (strlen(pattern) != 0)  {
1093 			p = pattern + strlen(pattern) -1;
1094 			if (*p == '\\')
1095 				*p = '\0';		// truncate trailing backslash
1096 		}
1097 
1098 		strcpy(base, pattern);
1099 		if (strchr(base, '*') != NULL) {
1100 			// wildcard already included
1101 			// truncate it from the base path
1102 			if ( (p = strrchr(base, '\\')) != NULL )
1103 				*(++p) = '\0';
1104 		}
1105 		else if (isalpha(pattern[0]) &&
1106 			pattern[1]==':' && pattern[2]=='\0')  {
1107 			strcat(pattern, "\\*");		// search entire disk
1108 			strcat(base, "\\");
1109 		}
1110 		else {
1111 			// wildcard NOT included
1112 			// check to see if path is a directory
1113 			find_handle = FindFirstFile(pattern, &find_data);
1114 			if (find_handle != INVALID_HANDLE_VALUE) {
1115 				FindClose(find_handle);
1116 				if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1117 					strcat(pattern, "\\*");		// yes, search files
1118 					strcat(base, "\\");
1119 				}
1120 				else {
1121 					dofile(path);				// no, return just this file
1122 					return 0;
1123 				}
1124 			}
1125 			else
1126 				return 1;	// path invalid
1127 		}
1128 	}
1129 	else {
1130 		base[0] = '\0';
1131 		strcpy(pattern, "*");
1132 	}
1133 
1134 	find_handle = FindFirstFile(pattern,  &find_data);
1135 	if (find_handle == INVALID_HANDLE_VALUE)
1136 		return 1;
1137 
1138 	while (bMore) {
1139 		strcpy(name, base);
1140 		strcat(name, find_data.cFileName);
1141 		if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
1142 			if ( strcmp(find_data.cFileName, ".") &&
1143 				strcmp(find_data.cFileName, "..") ) {
1144 				dodir(name);
1145 				if (recurse)
1146 					dirwalk(name, recurse, dodir, dofile);
1147 			}
1148 		}
1149 		else {
1150 			dofile(name);
1151 		}
1152 		bMore = FindNextFile(find_handle, &find_data);
1153 	}
1154 	FindClose(find_handle);
1155 
1156 	return 0;
1157 }
1158 
1159 
1160 
1161 // This is used when creating a file list.
1162 
make_filelist(int argc,char * argv[])1163 BOOL make_filelist(int argc, char *argv[])
1164 {
1165     char *title = NULL;
1166     char *dir = NULL;
1167     char *list = NULL;
1168     int i;
1169     g_bBatch = TRUE;	// Don't run message loop
1170 
1171     for (i=1; i<argc; i++) {
1172 		if (strcmp(argv[i], "-title") == 0) {
1173 			i++;
1174 			title = argv[i];
1175 		}
1176 		else if (strcmp(argv[i], "-dir") == 0) {
1177 			i++;
1178 			dir = argv[i];
1179 		}
1180 		else if (strcmp(argv[i], "-list") == 0) {
1181 			i++;
1182 			list = argv[i];
1183 		}
1184 		else {
1185 		    if ((title == NULL) || (strlen(title) == 0) ||
1186 			(dir == NULL) || (strlen(dir) == 0) ||
1187 			(list == NULL) || (strlen(list) == 0)) {
1188 			message_box("Usage: setupgs -title \042AFPL Ghostscript #.##\042 -dir \042gs#.##\042 -list \042filelist.txt\042 spec1 spec2 specn\n");
1189 			return FALSE;
1190 		    }
1191 		    if (fList == (FILE *)NULL) {
1192 			    if ( (fList = fopen(list, "w")) == (FILE *)NULL ) {
1193 					message_box("Can't write list file\n");
1194 					return FALSE;
1195 			    }
1196 			    fputs(title, fList);
1197 			    fputs("\n", fList);
1198 			    fputs(dir, fList);
1199 			    fputs("\n", fList);
1200 		    }
1201 		    if (argv[i][0] == '@') {
1202 			// Use @filename with list of files/directories
1203 			// to avoid DOS command line limit
1204 			FILE *f;
1205 			char buf[MAXSTR];
1206 			int j;
1207 			if ( (f = fopen(&(argv[i][1]), "r")) != (FILE *)NULL) {
1208 			    while (fgets(buf, sizeof(buf), f)) {
1209 				// remove trailing newline and spaces
1210 				while ( ((j = strlen(buf)-1) >= 0) &&
1211 				    ((buf[j] == '\n') || (buf[j] == ' ')) )
1212 				    buf[j] = '\0';
1213 			        dirwalk(buf, TRUE, &dodir, &dofile);
1214 			    }
1215 			    fclose(f);
1216 			}
1217 			else {
1218 				wsprintf(buf, "Can't open @ file \042%s\042",
1219 				    &argv[i][1]);
1220 				message_box(buf);
1221 			}
1222 		    }
1223 		    else
1224 		        dirwalk(argv[i], TRUE, &dodir, &dofile);
1225 		}
1226     }
1227 
1228     if (fList != (FILE *)NULL) {
1229         fclose(fList);
1230 	fList = NULL;
1231     }
1232     return TRUE;
1233 }
1234 
1235 //////////////////////////////////////////////////////////////////////
1236 
1237 #ifndef CSIDL_PROGRAM_FILES
1238 #define CSIDL_PROGRAM_FILES 0x0026
1239 #endif
1240 #ifndef CSIDL_FLAG_CREATE
1241 #define CSIDL_FLAG_CREATE 0x8000
1242 #endif
1243 #ifndef SHGFP_TYPE_CURRENT
1244 #define SHGFP_TYPE_CURRENT 0
1245 #endif
1246 
1247 BOOL
GetProgramFiles(LPTSTR path)1248 GetProgramFiles(LPTSTR path)
1249 {
1250     PFN_SHGetSpecialFolderPath PSHGetSpecialFolderPath = NULL;
1251     PFN_SHGetFolderPath PSHGetFolderPath = NULL;
1252     HMODULE hModuleShell32 = NULL;
1253     HMODULE hModuleShfolder = NULL;
1254     BOOL fOk = FALSE;
1255     hModuleShfolder = LoadLibrary("shfolder.dll");
1256     hModuleShell32 = LoadLibrary("shell32.dll");
1257 
1258     if (hModuleShfolder) {
1259 	PSHGetFolderPath = (PFN_SHGetFolderPath)
1260 	    GetProcAddress(hModuleShfolder, "SHGetFolderPathA");
1261 	if (PSHGetFolderPath) {
1262 	    fOk = (PSHGetFolderPath(HWND_DESKTOP,
1263 		CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE,
1264 		NULL, SHGFP_TYPE_CURRENT, path) == S_OK);
1265 	}
1266     }
1267 
1268     if (!fOk && hModuleShell32) {
1269 	PSHGetFolderPath = (PFN_SHGetFolderPath)
1270 	    GetProcAddress(hModuleShell32, "SHGetFolderPathA");
1271 	if (PSHGetFolderPath) {
1272 	    fOk = (PSHGetFolderPath(HWND_DESKTOP,
1273 		CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE,
1274 		NULL, SHGFP_TYPE_CURRENT, path) == S_OK);
1275 	}
1276     }
1277 
1278     if (!fOk && hModuleShell32) {
1279 	PSHGetSpecialFolderPath = (PFN_SHGetSpecialFolderPath)
1280 	    GetProcAddress(hModuleShell32, "SHGetSpecialFolderPathA");
1281 	if (PSHGetSpecialFolderPath) {
1282 	    fOk = PSHGetSpecialFolderPath(HWND_DESKTOP, path,
1283 		CSIDL_PROGRAM_FILES, TRUE);
1284 	}
1285     }
1286 
1287     if (!fOk) {
1288 	/* If all else fails (probably Win95), try the registry */
1289 	LONG rc;
1290 	HKEY hkey;
1291 	DWORD cbData;
1292 	DWORD keytype;
1293 	rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
1294 	    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", 0, KEY_READ, &hkey);
1295 	if (rc == ERROR_SUCCESS) {
1296 	    cbData = MAX_PATH;
1297 	    keytype =  REG_SZ;
1298 	    if (rc == ERROR_SUCCESS)
1299 		rc = RegQueryValueEx(hkey, "ProgramFilesDir", 0, &keytype,
1300 		    (LPBYTE)path, &cbData);
1301 	    RegCloseKey(hkey);
1302 	}
1303 	fOk = (rc == ERROR_SUCCESS);
1304     }
1305     return fOk;
1306 }
1307 
1308 
1309 //////////////////////////////////////////////////////////////////////
1310