Added an unknown VxD error code.
[wine] / programs / progman / grpfile.c
1 /*
2  * Program Manager
3  *
4  * Copyright 1996 Ulrich Schmid
5  *           1997 Peter Schlaile
6  */
7
8 #include "windows.h"
9 #include "wine/winuser16.h"
10 #include "progman.h"
11 #include "mmsystem.h"
12
13 #define MALLOCHUNK 1000
14
15 #define GET_USHORT(buffer, i)\
16   (((BYTE)((buffer)[(i)]) + 0x100 * (BYTE)((buffer)[(i)+1])))
17 #define GET_SHORT(buffer, i)\
18   (((BYTE)((buffer)[(i)]) + 0x100 * (signed char)((buffer)[(i)+1])))
19 #define PUT_SHORT(buffer, i, s)\
20   (((buffer)[(i)] = (s) & 0xff, (buffer)[(i)+1] = ((s) >> 8) & 0xff))
21
22 static BOOL   GRPFILE_ReadFileToBuffer(LPCSTR, HLOCAL*, INT*);
23 static HLOCAL GRPFILE_ScanGroup(LPCSTR, INT, LPCSTR, BOOL);
24 static HLOCAL GRPFILE_ScanProgram(LPCSTR, INT, LPCSTR, INT,
25                                   LPCSTR, HLOCAL,LPCSTR);
26 static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group);
27
28 /***********************************************************************
29  *
30  *           GRPFILE_ModifyFileName
31  *
32  *  Change extension `.grp' to `.gr'
33  */
34
35 static VOID GRPFILE_ModifyFileName(LPSTR lpszNewName, LPCSTR lpszOrigName,
36                                    INT nSize, BOOL bModify)
37 {
38   lstrcpyn(lpszNewName, lpszOrigName, nSize);
39   lpszNewName[nSize-1] = '\0';
40   if (!bModify) return;
41   if (!lstrcmpi(lpszNewName + strlen(lpszNewName) - 4, ".grp"))
42     lpszNewName[strlen(lpszNewName) - 1] = '\0';
43 }
44
45 /***********************************************************************
46  *
47  *           GRPFILE_ReadGroupFile
48  */
49
50 HLOCAL GRPFILE_ReadGroupFile(LPCSTR lpszPath)
51 {
52   CHAR   szPath_gr[MAX_PATHNAME_LEN];
53   BOOL   bFileNameModified = FALSE;
54   OFSTRUCT dummy;
55   HLOCAL hBuffer, hGroup;
56   INT    size;
57
58   /* if `.gr' file exists use that */
59   GRPFILE_ModifyFileName(szPath_gr, lpszPath, MAX_PATHNAME_LEN, TRUE);
60   if (OpenFile(szPath_gr, &dummy, OF_EXIST) != HFILE_ERROR)
61     {
62       lpszPath = szPath_gr;
63       bFileNameModified = TRUE;
64     }
65
66   /* Read the whole file into a buffer */
67   if (!GRPFILE_ReadFileToBuffer(lpszPath, &hBuffer, &size))
68     {
69       MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);
70       return(0);
71     }
72
73   /* Interpret buffer */
74   hGroup = GRPFILE_ScanGroup(LocalLock(hBuffer), size,
75                              lpszPath, bFileNameModified);
76   if (!hGroup)
77     MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);
78
79   LocalFree(hBuffer);
80
81   return(hGroup);
82 }
83
84 /***********************************************************************
85  *
86  *           GRPFILE_ReadFileToBuffer
87  */
88
89 static BOOL GRPFILE_ReadFileToBuffer(LPCSTR path, HLOCAL *phBuffer,
90                                      INT *piSize)
91 {
92   UINT    len, size;
93   LPSTR  buffer;
94   HLOCAL hBuffer, hNewBuffer;
95   HFILE  file;
96
97   file=_lopen(path, OF_READ);
98   if (file == HFILE_ERROR) return FALSE;
99
100   size = 0;
101   hBuffer = LocalAlloc(LMEM_FIXED, size + MALLOCHUNK + 1);
102   if (!hBuffer) return FALSE;
103   buffer = LocalLock(hBuffer);
104
105   while ((len = _lread(file, buffer + size, MALLOCHUNK))
106          == MALLOCHUNK)
107     {
108       size += len;
109       hNewBuffer = LocalReAlloc(hBuffer, size + MALLOCHUNK + 1,
110                                 LMEM_FIXED);
111       if (!hNewBuffer)
112         {
113           LocalFree(hBuffer);
114           return FALSE;
115         }
116       hBuffer = hNewBuffer;
117       buffer = LocalLock(hBuffer);
118     }
119
120   _lclose(file);
121
122   if (len == (UINT)HFILE_ERROR)
123     {
124       LocalFree(hBuffer);
125       return FALSE;
126     }
127
128   size += len;
129   buffer[size] = 0;
130
131   *phBuffer = hBuffer;
132   *piSize   = size;
133   return TRUE;
134 }
135
136 /***********************************************************************
137  *           GRPFILE_ScanGroup
138  */
139
140 static HLOCAL GRPFILE_ScanGroup(LPCSTR buffer, INT size,
141                                 LPCSTR lpszGrpFile,
142                                 BOOL bModifiedFileName)
143 {
144   HLOCAL  hGroup;
145   INT     i, seqnum;
146   LPCSTR  extension;
147   LPCSTR  lpszName;
148   INT     x, y, width, height, iconx, icony, nCmdShow;
149   INT     number_of_programs;
150   BOOL    bOverwriteFileOk;
151
152   if (buffer[0] != 'P' || buffer[1] != 'M') return(0);
153   if (buffer[2] == 'C' && buffer[3] == 'C')
154     /* original with checksum */
155     bOverwriteFileOk = FALSE;
156   else if (buffer[2] == 'X' && buffer[3] == 'X')
157     /* modified without checksum */
158     bOverwriteFileOk = TRUE;
159   else return(0);
160
161   /* checksum = GET_USHORT(buffer, 4)   (ignored) */
162
163   extension = buffer + GET_USHORT(buffer, 6);
164   if (extension == buffer + size) extension = 0;
165   else if (extension + 6 > buffer + size) return(0);
166
167   nCmdShow = GET_USHORT(buffer,  8);
168   x        = GET_SHORT(buffer,  10);
169   y        = GET_SHORT(buffer,  12);
170   width    = GET_USHORT(buffer, 14);
171   height   = GET_USHORT(buffer, 16);
172   iconx    = GET_SHORT(buffer,  18);
173   icony    = GET_SHORT(buffer,  20);
174   lpszName = buffer + GET_USHORT(buffer, 22);
175   if (lpszName >= buffer + size) return(0);
176
177   /* unknown bytes 24 - 31 ignored */ 
178   /*
179     Unknown bytes should be:
180     wLogPixelsX = GET_SHORT(buffer, 24);
181     wLogPixelsY = GET_SHORT(buffer, 26);
182     byBitsPerPixel = byte at 28;
183     byPlanes     = byte at 29;
184     wReserved   = GET_SHORT(buffer, 30);
185     */
186
187   hGroup = GROUP_AddGroup(lpszName, lpszGrpFile, nCmdShow, x, y,
188                           width, height, iconx, icony,
189                           bModifiedFileName, bOverwriteFileOk,
190                           TRUE);
191   if (!hGroup) return(0);
192
193   number_of_programs = GET_USHORT(buffer, 32);
194   if (2 * number_of_programs + 34 > size) return(0);
195   for (i=0, seqnum=0; i < number_of_programs; i++, seqnum++)
196     {
197       LPCSTR program_ptr = buffer + GET_USHORT(buffer, 34 + 2*i);
198       if (program_ptr + 24 > buffer + size) return(0);
199       if (!GET_USHORT(buffer, 34 + 2*i)) continue;
200       if (!GRPFILE_ScanProgram(buffer, size, program_ptr, seqnum,
201                                extension, hGroup, lpszGrpFile))
202         {
203           GROUP_DeleteGroup(hGroup);
204           return(0);
205         }
206     }
207
208   /* FIXME shouldn't be necessary */
209   GROUP_ShowGroupWindow(hGroup);
210
211   return hGroup;
212 }
213
214 /***********************************************************************
215  *           GRPFILE_ScanProgram
216  */
217
218 static HLOCAL GRPFILE_ScanProgram(LPCSTR buffer, INT size,
219                                   LPCSTR program_ptr, INT seqnum,
220                                   LPCSTR extension, HLOCAL hGroup,
221                                   LPCSTR lpszGrpFile)
222 {
223   INT    icontype;
224   HICON  hIcon;
225   LPCSTR lpszName, lpszCmdLine, lpszIconFile, lpszWorkDir;
226   LPCSTR iconinfo_ptr, iconANDbits_ptr, iconXORbits_ptr;
227   INT    x, y, nIconIndex, iconANDsize, iconXORsize;
228   INT    nHotKey, nCmdShow;
229   CURSORICONINFO iconinfo;
230
231   x               = GET_SHORT(program_ptr, 0);
232   y               = GET_SHORT(program_ptr, 2);
233   nIconIndex      = GET_USHORT(program_ptr, 4);
234
235   /* FIXME is this correct ?? */
236   icontype = GET_USHORT(program_ptr,  6);
237   switch (icontype)
238     {
239     default:
240       MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s, lpszGrpFile,
241                            IDS_WARNING, MB_OK);
242     case 0x048c:
243       iconXORsize     = GET_USHORT(program_ptr,  8);
244       iconANDsize     = GET_USHORT(program_ptr, 10) / 8;
245       iconinfo_ptr    = buffer + GET_USHORT(program_ptr, 12);
246       iconXORbits_ptr = buffer + GET_USHORT(program_ptr, 14);
247       iconANDbits_ptr = buffer + GET_USHORT(program_ptr, 16);
248       iconinfo.ptHotSpot.x   = GET_USHORT(iconinfo_ptr, 0);
249       iconinfo.ptHotSpot.y   = GET_USHORT(iconinfo_ptr, 2);
250       iconinfo.nWidth        = GET_USHORT(iconinfo_ptr, 4);
251       iconinfo.nHeight       = GET_USHORT(iconinfo_ptr, 6);
252       iconinfo.nWidthBytes   = GET_USHORT(iconinfo_ptr, 8);
253       iconinfo.bPlanes       = GET_USHORT(iconinfo_ptr, 10);
254       iconinfo.bBitsPerPixel = GET_USHORT(iconinfo_ptr, 11);
255       break;
256     case 0x000c:
257       iconANDsize     = GET_USHORT(program_ptr,  8);
258       iconXORsize     = GET_USHORT(program_ptr, 10);
259       iconinfo_ptr    = buffer + GET_USHORT(program_ptr, 12);
260       iconANDbits_ptr = buffer + GET_USHORT(program_ptr, 14);
261       iconXORbits_ptr = buffer + GET_USHORT(program_ptr, 16);
262       iconinfo.ptHotSpot.x   = GET_USHORT(iconinfo_ptr, 0);
263       iconinfo.ptHotSpot.y   = GET_USHORT(iconinfo_ptr, 2);
264       iconinfo.nWidth        = GET_USHORT(iconinfo_ptr, 4);
265       iconinfo.nHeight       = GET_USHORT(iconinfo_ptr, 6);
266       iconinfo.nWidthBytes = GET_USHORT(iconinfo_ptr, 8) * 8;
267       iconinfo.bPlanes       = GET_USHORT(iconinfo_ptr, 10);
268       iconinfo.bBitsPerPixel = GET_USHORT(iconinfo_ptr, 11);
269     }
270
271   if (iconANDbits_ptr + iconANDsize > buffer + size ||
272       iconXORbits_ptr + iconXORsize > buffer + size) return(0);
273
274   hIcon = CreateIcon( Globals.hInstance, iconinfo.nWidth, iconinfo.nHeight,
275                       iconinfo.bPlanes, iconinfo.bBitsPerPixel,
276                       iconANDbits_ptr, iconXORbits_ptr );
277
278   lpszName        = buffer + GET_USHORT(program_ptr, 18);
279   lpszCmdLine     = buffer + GET_USHORT(program_ptr, 20);
280   lpszIconFile    = buffer + GET_USHORT(program_ptr, 22);
281   if (iconinfo_ptr + 6 > buffer + size ||
282       lpszName         > buffer + size ||
283       lpszCmdLine      > buffer + size ||
284       lpszIconFile     > buffer + size) return(0);
285
286   /* Scan Extensions */
287   lpszWorkDir = "";
288   nHotKey     = 0;
289   nCmdShow    = SW_SHOWNORMAL;
290   if (extension)
291     {
292       LPCSTR ptr = extension;
293       while (ptr + 6 <= buffer + size)
294         {
295           UINT type   = GET_USHORT(ptr, 0);
296           UINT number = GET_USHORT(ptr, 2);
297           UINT skip   = GET_USHORT(ptr, 4);
298
299           if (number == seqnum)
300             {
301               switch (type)
302                 {
303                 case 0x8000:
304                   if (ptr + 10 > buffer + size) return(0);
305                   if (ptr[6] != 'P' || ptr[7] != 'M' ||
306                       ptr[8] != 'C' || ptr[9] != 'C') return(0);
307                   break;
308                 case 0x8101:
309                   lpszWorkDir = ptr + 6;
310                   break;
311                 case 0x8102:
312                   if (ptr + 8 > buffer + size) return(0);
313                   nHotKey = GET_USHORT(ptr, 6);
314                   break;
315                 case 0x8103:
316                   if (ptr + 8 > buffer + size) return(0);
317                   nCmdShow = GET_USHORT(ptr, 6);
318                   break;
319                 default:
320                   MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s,
321                                        lpszGrpFile, IDS_WARNING, MB_OK);
322                 }
323             }
324           if (!skip) break;
325           ptr += skip;
326         }
327     }
328
329   return (PROGRAM_AddProgram(hGroup, hIcon, lpszName, x, y,
330                              lpszCmdLine, lpszIconFile,
331                              nIconIndex, lpszWorkDir,
332                              nHotKey, nCmdShow));
333 }
334
335 /***********************************************************************
336  *
337  *           GRPFILE_WriteGroupFile
338  */
339
340 BOOL GRPFILE_WriteGroupFile(HLOCAL hGroup)
341 {
342   CHAR szPath[MAX_PATHNAME_LEN];
343   PROGGROUP *group = LocalLock(hGroup);
344   OFSTRUCT dummy;
345   HFILE file;
346   BOOL ret;
347
348   GRPFILE_ModifyFileName(szPath, LocalLock(group->hGrpFile),
349                          MAX_PATHNAME_LEN,
350                          group->bFileNameModified);
351
352   /* Try not to overwrite original files */
353
354   /* group->bOverwriteFileOk == TRUE only if a file has the modified format */
355   if (!group->bOverwriteFileOk &&
356       OpenFile(szPath, &dummy, OF_EXIST) != HFILE_ERROR)
357     {
358       /* Original file exists, try `.gr' extension */
359       GRPFILE_ModifyFileName(szPath, LocalLock(group->hGrpFile),
360                              MAX_PATHNAME_LEN, TRUE);
361       if (OpenFile(szPath, &dummy, OF_EXIST) != HFILE_ERROR)
362         {
363           /* File exists. Do not overwrite */
364           MAIN_MessageBoxIDS_s(IDS_FILE_NOT_OVERWRITTEN_s, szPath,
365                                IDS_INFO, MB_OK);
366           return FALSE;
367         }
368       /* Inform about the modified file name */
369       if (IDCANCEL ==
370           MAIN_MessageBoxIDS_s(IDS_SAVE_GROUP_AS_s, szPath, IDS_INFO,
371                                MB_OKCANCEL | MB_ICONINFORMATION))
372         return FALSE;
373     }
374
375   {
376     /* Warn about the (possible) incompatibility */
377     CHAR msg[MAX_PATHNAME_LEN + 200];
378     wsprintf(msg,
379              "Group files written by this DRAFT Program Manager "
380              "possibly cannot be read by the Microsoft Program Manager!!\n"
381              "Are you sure to write %s?", szPath);
382     if (IDOK != MessageBox(Globals.hMainWnd, msg, "WARNING",
383                            MB_OKCANCEL | MB_DEFBUTTON2)) return FALSE;
384   }
385
386   /* FIXME */
387   if (OpenFile(szPath, &dummy, OF_EXIST) == HFILE_ERROR)
388     {
389       CHAR msg[MAX_PATHNAME_LEN + 200];
390       wsprintf(msg, "Cause of a bug you must now touch the file %s\n", szPath);
391       MessageBox(Globals.hMainWnd, msg, "", MB_OK);
392     }
393
394   /* Open file */
395   file = _lopen(szPath, OF_WRITE);
396   if (file != HFILE_ERROR)
397     {
398       ret = GRPFILE_DoWriteGroupFile(file, group);
399       _lclose(file);
400     }
401   else ret = FALSE;
402
403   if (!ret)
404     MAIN_MessageBoxIDS_s(IDS_FILE_WRITE_ERROR_s, szPath, IDS_ERROR, MB_OK);
405
406   return(ret);
407 }
408
409 /***********************************************************************
410  *
411  *           GRPFILE_CalculateSizes
412  */
413
414 static VOID GRPFILE_CalculateSizes(PROGRAM *program,
415                                    INT *Progs, INT *Icons)
416 {
417   CURSORICONINFO *iconinfo = LocalLock(program->hIcon);
418   INT sizeXor = iconinfo->nHeight * iconinfo->nWidthBytes;
419   INT sizeAnd = iconinfo->nHeight * ((iconinfo->nWidth + 15) / 16 * 2);
420
421   *Progs += 24;
422   *Progs += lstrlen(LocalLock(program->hName)) + 1;
423   *Progs += lstrlen(LocalLock(program->hCmdLine)) + 1;
424   *Progs += lstrlen(LocalLock(program->hIconFile)) + 1;
425
426   *Icons += 12; /* IconInfo */
427   *Icons += sizeAnd;
428   *Icons += sizeXor;
429 }
430
431 /***********************************************************************/
432 UINT16 GRPFILE_checksum;
433 BOOL GRPFILE_checksum_half_word;
434 BYTE GRPFILE_checksum_last_byte;
435 /***********************************************************************
436  *
437  *           GRPFILE_InitChecksum
438  */
439
440 static void GRPFILE_InitChecksum()
441 {
442         GRPFILE_checksum = 0;
443         GRPFILE_checksum_half_word = 0;
444 }
445
446 /***********************************************************************
447  *
448  *           GRPFILE_GetChecksum
449  */
450
451 static UINT16 GRPFILE_GetChecksum()
452 {
453         return GRPFILE_checksum;
454 }
455
456 /***********************************************************************
457  *
458  *           GRPFILE_WriteWithChecksum
459  * 
460  * Looks crazier than it is:
461  * 
462  * chksum = 0;
463  * chksum = cksum - 1. word;
464  * chksum = cksum - 2. word;
465  * ...
466  * 
467  * if (filelen is even)
468  *      great I'm finished
469  * else
470  *      ignore last byte
471  */
472
473 static UINT GRPFILE_WriteWithChecksum(HFILE file, LPCSTR str, UINT size)
474 {
475         UINT i;
476         if (GRPFILE_checksum_half_word) {
477                 GRPFILE_checksum -= GRPFILE_checksum_last_byte; 
478         }
479         for (i=0; i < size; i++) {
480                 if (GRPFILE_checksum_half_word) {
481                         GRPFILE_checksum -= str[i] << 8;
482                 } else {
483                         GRPFILE_checksum -= str[i];
484                 }
485                 GRPFILE_checksum_half_word ^= 1;
486         }
487         
488         if (GRPFILE_checksum_half_word) {
489                 GRPFILE_checksum_last_byte = str[size-1];
490                 GRPFILE_checksum += GRPFILE_checksum_last_byte;
491         }
492         
493         return _lwrite(file, str, size);
494 }
495
496
497 /***********************************************************************
498  *
499  *           GRPFILE_DoWriteGroupFile
500  */
501
502 static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group)
503 {
504   BYTE buffer[34];
505   HLOCAL hProgram;
506   INT    NumProg, Title, Progs, Icons, Extension;
507   INT    CurrProg, CurrIcon, nCmdShow, ptr, seqnum;
508   BOOL   need_extension;
509   LPCSTR lpszTitle = LocalLock(group->hName);
510
511   UINT16 checksum;
512         
513   GRPFILE_InitChecksum();
514         
515   /* Calculate offsets */
516   NumProg = 0;
517   Icons   = 0;
518   Extension = 0;
519   need_extension = FALSE;
520   hProgram = group->hPrograms;
521   while(hProgram)
522     {
523       PROGRAM *program = LocalLock(hProgram);
524       LPCSTR lpszWorkDir = LocalLock(program->hWorkDir);
525
526       NumProg++;
527       GRPFILE_CalculateSizes(program, &Icons, &Extension);
528
529       /* Set a flag if an extension is needed */
530       if (lpszWorkDir[0] || program->nHotKey ||
531           program->nCmdShow != SW_SHOWNORMAL) need_extension = TRUE;
532
533       hProgram = program->hNext;
534     }
535   Title      = 34 + NumProg * 2;
536   Progs      = Title + lstrlen(lpszTitle) + 1;
537   Icons     += Progs;
538   Extension += Icons;
539
540   /* Header */
541   buffer[0] = 'P';
542   buffer[1] = 'M';
543   buffer[2] = 'C'; 
544   buffer[3] = 'C';
545         
546   PUT_SHORT(buffer,  4, 0); /* Checksum zero for now, written later */
547   PUT_SHORT(buffer,  6, Extension);
548   /* Update group->nCmdShow */
549   if (IsIconic(group->hWnd))      nCmdShow = SW_SHOWMINIMIZED;
550   else if (IsZoomed(group->hWnd)) nCmdShow = SW_SHOWMAXIMIZED;
551   else                            nCmdShow = SW_SHOWNORMAL;
552   PUT_SHORT(buffer,  8, nCmdShow);
553   PUT_SHORT(buffer, 10, group->x);
554   PUT_SHORT(buffer, 12, group->y);
555   PUT_SHORT(buffer, 14, group->width);
556   PUT_SHORT(buffer, 16, group->height);
557   PUT_SHORT(buffer, 18, group->iconx);
558   PUT_SHORT(buffer, 20, group->icony);
559   PUT_SHORT(buffer, 22, Title);
560   PUT_SHORT(buffer, 24, 0x0020); /* unknown */
561   PUT_SHORT(buffer, 26, 0x0020); /* unknown */
562   PUT_SHORT(buffer, 28, 0x0108); /* unknown */
563   PUT_SHORT(buffer, 30, 0x0000); /* unknown */
564   PUT_SHORT(buffer, 32, NumProg);
565
566   if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 34)) return FALSE;
567
568   /* Program table */
569   CurrProg = Progs;
570   CurrIcon = Icons;
571   hProgram = group->hPrograms;
572   while(hProgram)
573     {
574       PROGRAM *program = LocalLock(hProgram);
575
576       PUT_SHORT(buffer, 0, CurrProg);
577       if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 2)) 
578               return FALSE;
579
580       GRPFILE_CalculateSizes(program, &CurrProg, &CurrIcon);
581       hProgram = program->hNext;
582     }
583
584   /* Title */
585   if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszTitle, 
586                                                lstrlen(lpszTitle) + 1))
587     return FALSE;
588
589   /* Program entries */
590   CurrProg = Progs;
591   CurrIcon = Icons;
592   hProgram = group->hPrograms;
593   while(hProgram)
594     {
595       PROGRAM *program = LocalLock(hProgram);
596       CURSORICONINFO *iconinfo = LocalLock(program->hIcon);
597       LPCSTR Name     = LocalLock(program->hName);
598       LPCSTR CmdLine  = LocalLock(program->hCmdLine);
599       LPCSTR IconFile = LocalLock(program->hIconFile);
600       INT sizeXor = iconinfo->nHeight * iconinfo->nWidthBytes;
601       INT sizeAnd = iconinfo->nHeight * ((iconinfo->nWidth + 15) / 16 * 2);
602
603       PUT_SHORT(buffer,  0, program->x);
604       PUT_SHORT(buffer,  2, program->y);
605       PUT_SHORT(buffer,  4, program->nIconIndex);
606       PUT_SHORT(buffer,  6, 0x048c);            /* unknown */
607       PUT_SHORT(buffer,  8, sizeXor);
608       PUT_SHORT(buffer, 10, sizeAnd * 8);
609       PUT_SHORT(buffer, 12, CurrIcon);
610       PUT_SHORT(buffer, 14, CurrIcon + 12 + sizeAnd);
611       PUT_SHORT(buffer, 16, CurrIcon + 12);
612       ptr = CurrProg + 24;
613       PUT_SHORT(buffer, 18, ptr);
614       ptr += lstrlen(Name) + 1;
615       PUT_SHORT(buffer, 20, ptr);
616       ptr += lstrlen(CmdLine) + 1;
617       PUT_SHORT(buffer, 22, ptr);
618
619       if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 24) ||
620           (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, Name, lstrlen(Name) + 1) ||
621           (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, CmdLine, lstrlen(CmdLine) + 1) ||
622           (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, IconFile, lstrlen(IconFile) + 1))
623         return FALSE;
624
625       GRPFILE_CalculateSizes(program, &CurrProg, &CurrIcon);
626       hProgram = program->hNext;
627     }
628
629   /* Icons */
630   hProgram = group->hPrograms;
631   while(hProgram)
632     {
633       PROGRAM *program = LocalLock(hProgram);
634       CURSORICONINFO *iconinfo = LocalLock(program->hIcon);
635       LPVOID XorBits, AndBits;
636       INT sizeXor = iconinfo->nHeight * iconinfo->nWidthBytes;
637       INT sizeAnd = iconinfo->nHeight * ((iconinfo->nWidth + 15) / 16 * 2);
638       /* FIXME: this is broken anyway */
639       /* DumpIcon16(LocalLock(program->hIcon), 0, &XorBits, &AndBits);*/
640
641       PUT_SHORT(buffer, 0, iconinfo->ptHotSpot.x);
642       PUT_SHORT(buffer, 2, iconinfo->ptHotSpot.y);
643       PUT_SHORT(buffer, 4, iconinfo->nWidth);
644       PUT_SHORT(buffer, 6, iconinfo->nHeight);
645       PUT_SHORT(buffer, 8, iconinfo->nWidthBytes);
646       buffer[10] = iconinfo->bPlanes;
647       buffer[11] = iconinfo->bBitsPerPixel;
648
649       if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 12) ||
650           (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, AndBits, sizeAnd) ||
651           (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, XorBits, sizeXor)) return FALSE;
652
653       hProgram = program->hNext;
654     }
655
656   if (need_extension)
657     {
658       /* write `PMCC' extension */
659       PUT_SHORT(buffer, 0, 0x8000);
660       PUT_SHORT(buffer, 2, 0xffff);
661       PUT_SHORT(buffer, 4, 0x000a);
662       buffer[6] = 'P', buffer[7] = 'M';
663       buffer[8] = 'C', buffer[9] = 'C';
664       if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 10)) 
665               return FALSE;
666
667       seqnum = 0;
668       hProgram = group->hPrograms;
669       while(hProgram)
670         {
671           PROGRAM *program = LocalLock(hProgram);
672           LPCSTR lpszWorkDir = LocalLock(program->hWorkDir);
673
674           /* Working directory */
675           if (lpszWorkDir[0])
676             {
677               PUT_SHORT(buffer, 0, 0x8101);
678               PUT_SHORT(buffer, 2, seqnum);
679               PUT_SHORT(buffer, 4, 7 + lstrlen(lpszWorkDir));
680               if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6) ||
681                   (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszWorkDir, lstrlen(lpszWorkDir) + 1))
682                 return FALSE;
683             }
684
685           /* Hot key */
686           if (program->nHotKey)
687             {
688               PUT_SHORT(buffer, 0, 0x8102);
689               PUT_SHORT(buffer, 2, seqnum);
690               PUT_SHORT(buffer, 4, 8);
691               PUT_SHORT(buffer, 6, program->nHotKey);
692               if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
693             }
694
695           /* Show command */
696           if (program->nCmdShow)
697             {
698               PUT_SHORT(buffer, 0, 0x8103);
699               PUT_SHORT(buffer, 2, seqnum);
700               PUT_SHORT(buffer, 4, 8);
701               PUT_SHORT(buffer, 6, program->nCmdShow);
702               if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
703             }
704
705           seqnum++;
706           hProgram = program->hNext;
707         }
708
709       /* Write `End' extension */
710       PUT_SHORT(buffer, 0, 0xffff);
711       PUT_SHORT(buffer, 2, 0xffff);
712       PUT_SHORT(buffer, 4, 0x0000);
713       if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6)) return FALSE;
714     }
715
716   checksum = GRPFILE_GetChecksum();
717   _llseek(file, 4, SEEK_SET);
718   PUT_SHORT(buffer, 0, checksum);
719   _lwrite(file, buffer, 2);
720
721   return TRUE;
722 }
723
724 /* Local Variables:    */
725 /* c-file-style: "GNU" */
726 /* End:                */