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