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