/*
 * Program Manager
 *
 * Copyright 1996 Ulrich Schmid
 *           1997 Peter Schlaile
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#define WIN32_LEAN_AND_MEAN

#include "windows.h"
#include "progman.h"
#include "mmsystem.h"

#define MALLOCHUNK 1000

#define GET_USHORT(buffer, i)\
  (((BYTE)((buffer)[(i)]) + 0x100 * (BYTE)((buffer)[(i)+1])))
#define GET_SHORT(buffer, i)\
  (((BYTE)((buffer)[(i)]) + 0x100 * (signed char)((buffer)[(i)+1])))
#define PUT_SHORT(buffer, i, s)\
  (((buffer)[(i)] = (BYTE)((s) & 0xff), (buffer)[(i)+1] = (BYTE)(((s) >> 8) & 0xff)))

static BOOL   GRPFILE_ReadFileToBuffer(LPCSTR, HLOCAL*, INT*);
static HLOCAL GRPFILE_ScanGroup(LPCSTR, INT, LPCSTR, BOOL);
static HLOCAL GRPFILE_ScanProgram(LPCSTR, INT, LPCSTR, INT,
				  LPCSTR, HLOCAL,LPCSTR);
static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group);

/***********************************************************************
 *
 *           GRPFILE_ModifyFileName
 *
 *  Change extension `.grp' to `.gr'
 */

static VOID GRPFILE_ModifyFileName(LPSTR lpszNewName, LPCSTR lpszOrigName,
				   INT nSize, BOOL bModify)
{
  lstrcpynA(lpszNewName, lpszOrigName, nSize);
  lpszNewName[nSize-1] = '\0';
  if (!bModify) return;
  if (!lstrcmpiA(lpszNewName + strlen(lpszNewName) - 4, ".grp"))
    lpszNewName[strlen(lpszNewName) - 1] = '\0';
}

/***********************************************************************
 *
 *           GRPFILE_ReadGroupFile
 */

HLOCAL GRPFILE_ReadGroupFile(LPCSTR lpszPath)
{
  CHAR   szPath_gr[MAX_PATHNAME_LEN];
  BOOL   bFileNameModified = FALSE;
  OFSTRUCT dummy;
  HLOCAL hBuffer, hGroup;
  INT    size;

  /* if `.gr' file exists use that */
  GRPFILE_ModifyFileName(szPath_gr, lpszPath, MAX_PATHNAME_LEN, TRUE);
  if (OpenFile(szPath_gr, &dummy, OF_EXIST) != HFILE_ERROR)
    {
      lpszPath = szPath_gr;
      bFileNameModified = TRUE;
    }

  /* Read the whole file into a buffer */
  if (!GRPFILE_ReadFileToBuffer(lpszPath, &hBuffer, &size))
    {
      MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);
      return(0);
    }

  /* Interpret buffer */
  hGroup = GRPFILE_ScanGroup(LocalLock(hBuffer), size,
			     lpszPath, bFileNameModified);
  if (!hGroup)
    MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);

  LocalFree(hBuffer);

  return(hGroup);
}

/***********************************************************************
 *
 *           GRPFILE_ReadFileToBuffer
 */

static BOOL GRPFILE_ReadFileToBuffer(LPCSTR path, HLOCAL *phBuffer,
				     INT *piSize)
{
  UINT    len, size;
  LPSTR  buffer;
  HLOCAL hBuffer, hNewBuffer;
  HFILE  file;

  file=_lopen(path, OF_READ);
  if (file == HFILE_ERROR) return FALSE;

  size = 0;
  hBuffer = LocalAlloc(LMEM_FIXED, MALLOCHUNK + 1);
  if (!hBuffer) return FALSE;
  buffer = LocalLock(hBuffer);

  while ((len = _lread(file, buffer + size, MALLOCHUNK))
	 == MALLOCHUNK)
    {
      size += len;
      hNewBuffer = LocalReAlloc(hBuffer, size + MALLOCHUNK + 1,
				LMEM_MOVEABLE);
      if (!hNewBuffer)
	{
	  LocalFree(hBuffer);
	  return FALSE;
	}
      hBuffer = hNewBuffer;
      buffer = LocalLock(hBuffer);
    }

  _lclose(file);

  if (len == (UINT)HFILE_ERROR)
    {
      LocalFree(hBuffer);
      return FALSE;
    }

  size += len;
  buffer[size] = 0;

  *phBuffer = hBuffer;
  *piSize   = size;
  return TRUE;
}

/***********************************************************************
 *           GRPFILE_ScanGroup
 */

static HLOCAL GRPFILE_ScanGroup(LPCSTR buffer, INT size,
				LPCSTR lpszGrpFile,
				BOOL bModifiedFileName)
{
  HLOCAL  hGroup;
  INT     i, seqnum;
  LPCSTR  extension;
  LPCSTR  lpszName;
  INT     x, y, width, height, iconx, icony, nCmdShow;
  INT     number_of_programs;
  BOOL    bOverwriteFileOk;

  if (buffer[0] != 'P' || buffer[1] != 'M') return(0);
  if (buffer[2] == 'C' && buffer[3] == 'C')
    /* original with checksum */
    bOverwriteFileOk = FALSE;
  else if (buffer[2] == 'X' && buffer[3] == 'X')
    /* modified without checksum */
    bOverwriteFileOk = TRUE;
  else return(0);

  /* checksum = GET_USHORT(buffer, 4)   (ignored) */

  extension = buffer + GET_USHORT(buffer, 6);
  if (extension == buffer + size) extension = 0;
  else if (extension + 6 > buffer + size) return(0);

  nCmdShow = GET_USHORT(buffer,  8);
  x        = GET_SHORT(buffer,  10);
  y        = GET_SHORT(buffer,  12);
  width    = GET_USHORT(buffer, 14);
  height   = GET_USHORT(buffer, 16);
  iconx    = GET_SHORT(buffer,  18);
  icony    = GET_SHORT(buffer,  20);
  lpszName = buffer + GET_USHORT(buffer, 22);
  if (lpszName >= buffer + size) return(0);

  /* unknown bytes 24 - 31 ignored */
  /*
    Unknown bytes should be:
    wLogPixelsX = GET_SHORT(buffer, 24);
    wLogPixelsY = GET_SHORT(buffer, 26);
    byBitsPerPixel = byte at 28;
    byPlanes     = byte at 29;
    wReserved   = GET_SHORT(buffer, 30);
    */

  hGroup = GROUP_AddGroup(lpszName, lpszGrpFile, nCmdShow, x, y,
			  width, height, iconx, icony,
			  bModifiedFileName, bOverwriteFileOk,
			  TRUE);
  if (!hGroup) return(0);

  number_of_programs = GET_USHORT(buffer, 32);
  if (2 * number_of_programs + 34 > size) return(0);
  for (i=0, seqnum=0; i < number_of_programs; i++, seqnum++)
    {
      LPCSTR program_ptr = buffer + GET_USHORT(buffer, 34 + 2*i);
      if (program_ptr + 24 > buffer + size) return(0);
      if (!GET_USHORT(buffer, 34 + 2*i)) continue;
      if (!GRPFILE_ScanProgram(buffer, size, program_ptr, seqnum,
			       extension, hGroup, lpszGrpFile))
	{
	  GROUP_DeleteGroup(hGroup);
	  return(0);
	}
    }

  /* FIXME shouldn't be necessary */
  GROUP_ShowGroupWindow(hGroup);

  return hGroup;
}

/***********************************************************************
 *           GRPFILE_ScanProgram
 */

static HLOCAL GRPFILE_ScanProgram(LPCSTR buffer, INT size,
				  LPCSTR program_ptr, INT seqnum,
				  LPCSTR extension, HLOCAL hGroup,
				  LPCSTR lpszGrpFile)
{
  INT    icontype;
  HICON  hIcon;
  LPCSTR lpszName, lpszCmdLine, lpszIconFile, lpszWorkDir;
  LPCSTR iconinfo_ptr, iconANDbits_ptr, iconXORbits_ptr;
  INT    x, y, nIconIndex, iconANDsize, iconXORsize;
  INT    nHotKey, nCmdShow;
  UINT width, height, planes, bpp;

  x               = GET_SHORT(program_ptr, 0);
  y               = GET_SHORT(program_ptr, 2);
  nIconIndex      = GET_USHORT(program_ptr, 4);

  /* FIXME is this correct ?? */
  icontype = GET_USHORT(program_ptr,  6);
  switch (icontype)
    {
    default:
      MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s, lpszGrpFile,
			   IDS_WARNING, MB_OK);
    case 0x048c:
      iconXORsize     = GET_USHORT(program_ptr,  8);
      iconANDsize     = GET_USHORT(program_ptr, 10) / 8;
      iconinfo_ptr    = buffer + GET_USHORT(program_ptr, 12);
      iconXORbits_ptr = buffer + GET_USHORT(program_ptr, 14);
      iconANDbits_ptr = buffer + GET_USHORT(program_ptr, 16);
      width           = GET_USHORT(iconinfo_ptr, 4);
      height          = GET_USHORT(iconinfo_ptr, 6);
      planes          = GET_USHORT(iconinfo_ptr, 10);
      bpp             = GET_USHORT(iconinfo_ptr, 11);
      break;
    case 0x000c:
      iconANDsize     = GET_USHORT(program_ptr,  8);
      iconXORsize     = GET_USHORT(program_ptr, 10);
      iconinfo_ptr    = buffer + GET_USHORT(program_ptr, 12);
      iconANDbits_ptr = buffer + GET_USHORT(program_ptr, 14);
      iconXORbits_ptr = buffer + GET_USHORT(program_ptr, 16);
      width           = GET_USHORT(iconinfo_ptr, 4);
      height          = GET_USHORT(iconinfo_ptr, 6);
      planes          = GET_USHORT(iconinfo_ptr, 10);
      bpp             = GET_USHORT(iconinfo_ptr, 11);
    }

  if (iconANDbits_ptr + iconANDsize > buffer + size ||
      iconXORbits_ptr + iconXORsize > buffer + size) return(0);

  hIcon = CreateIcon( Globals.hInstance, width, height, planes, bpp, iconANDbits_ptr, iconXORbits_ptr );

  lpszName        = buffer + GET_USHORT(program_ptr, 18);
  lpszCmdLine     = buffer + GET_USHORT(program_ptr, 20);
  lpszIconFile    = buffer + GET_USHORT(program_ptr, 22);
  if (iconinfo_ptr + 6 > buffer + size ||
      lpszName         > buffer + size ||
      lpszCmdLine      > buffer + size ||
      lpszIconFile     > buffer + size) return(0);

  /* Scan Extensions */
  lpszWorkDir = "";
  nHotKey     = 0;
  nCmdShow    = SW_SHOWNORMAL;
  if (extension)
    {
      LPCSTR ptr = extension;
      while (ptr + 6 <= buffer + size)
	{
	  UINT type   = GET_USHORT(ptr, 0);
	  UINT number = GET_USHORT(ptr, 2);
	  UINT skip   = GET_USHORT(ptr, 4);

	  if (number == seqnum)
	    {
	      switch (type)
		{
		case 0x8000:
		  if (ptr + 10 > buffer + size) return(0);
		  if (ptr[6] != 'P' || ptr[7] != 'M' ||
		      ptr[8] != 'C' || ptr[9] != 'C') return(0);
		  break;
		case 0x8101:
		  lpszWorkDir = ptr + 6;
		  break;
		case 0x8102:
		  if (ptr + 8 > buffer + size) return(0);
		  nHotKey = GET_USHORT(ptr, 6);
		  break;
		case 0x8103:
		  if (ptr + 8 > buffer + size) return(0);
		  nCmdShow = GET_USHORT(ptr, 6);
		  break;
		default:
		  MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s,
				       lpszGrpFile, IDS_WARNING, MB_OK);
		}
	    }
	  if (!skip) break;
	  ptr += skip;
	}
    }

  return (PROGRAM_AddProgram(hGroup, hIcon, lpszName, x, y,
			     lpszCmdLine, lpszIconFile,
			     nIconIndex, lpszWorkDir,
			     nHotKey, nCmdShow));
}

/***********************************************************************
 *
 *           GRPFILE_WriteGroupFile
 */

BOOL GRPFILE_WriteGroupFile(HLOCAL hGroup)
{
  CHAR szPath[MAX_PATHNAME_LEN];
  PROGGROUP *group = LocalLock(hGroup);
  OFSTRUCT dummy;
  HFILE file;
  BOOL ret;

  GRPFILE_ModifyFileName(szPath, LocalLock(group->hGrpFile),
			 MAX_PATHNAME_LEN,
			 group->bFileNameModified);

  /* Try not to overwrite original files */

  /* group->bOverwriteFileOk == TRUE only if a file has the modified format */
  if (!group->bOverwriteFileOk &&
      OpenFile(szPath, &dummy, OF_EXIST) != HFILE_ERROR)
    {
      /* Original file exists, try `.gr' extension */
      GRPFILE_ModifyFileName(szPath, LocalLock(group->hGrpFile),
			     MAX_PATHNAME_LEN, TRUE);
      if (OpenFile(szPath, &dummy, OF_EXIST) != HFILE_ERROR)
	{
	  /* File exists. Do not overwrite */
	  MAIN_MessageBoxIDS_s(IDS_FILE_NOT_OVERWRITTEN_s, szPath,
			       IDS_INFO, MB_OK);
	  return FALSE;
	}
      /* Inform about the modified file name */
      if (IDCANCEL ==
	  MAIN_MessageBoxIDS_s(IDS_SAVE_GROUP_AS_s, szPath, IDS_INFO,
			       MB_OKCANCEL | MB_ICONINFORMATION))
	return FALSE;
    }

  {
    /* Warn about the (possible) incompatibility */
    CHAR msg[MAX_PATHNAME_LEN + 200];
    wsprintfA(msg,
	     "Group files written by this DRAFT Program Manager "
	     "possibly cannot be read by the Microsoft Program Manager!!\n"
	     "Are you sure to write %s?", szPath);
    if (IDOK != MessageBoxA(Globals.hMainWnd, msg, "WARNING",
                            MB_OKCANCEL | MB_DEFBUTTON2)) return FALSE;
  }

  /* Open file */
  file = _lcreat(szPath, 0);
  if (file != HFILE_ERROR)
    {
      ret = GRPFILE_DoWriteGroupFile(file, group);
      _lclose(file);
    }
  else ret = FALSE;

  if (!ret)
    MAIN_MessageBoxIDS_s(IDS_FILE_WRITE_ERROR_s, szPath, IDS_ERROR, MB_OK);

  return(ret);
}

/***********************************************************************
 *
 *           GRPFILE_CalculateSizes
 */

static VOID GRPFILE_CalculateSizes(PROGRAM *program, INT *Progs, INT *Icons,
                                   UINT *sizeAnd, UINT *sizeXor)
{
  ICONINFO info;
  BITMAP bmp;

  GetIconInfo( program->hIcon, &info );
  GetObjectW( info.hbmMask, sizeof(bmp), &bmp );
  *sizeAnd = bmp.bmHeight * ((bmp.bmWidth + 15) / 16 * 2);
  GetObjectW( info.hbmColor, sizeof(bmp), &bmp );
  *sizeXor = bmp.bmHeight * bmp.bmWidthBytes;
  DeleteObject( info.hbmMask );
  DeleteObject( info.hbmColor );

  *Progs += 24;
  *Progs += strlen(LocalLock(program->hName)) + 1;
  *Progs += strlen(LocalLock(program->hCmdLine)) + 1;
  *Progs += strlen(LocalLock(program->hIconFile)) + 1;

  *Icons += 12; /* IconInfo */
  *Icons += *sizeAnd;
  *Icons += *sizeXor;
}

/***********************************************************************/
UINT16 GRPFILE_checksum;
BOOL GRPFILE_checksum_half_word;
BYTE GRPFILE_checksum_last_byte;
/***********************************************************************
 *
 *           GRPFILE_InitChecksum
 */

static void GRPFILE_InitChecksum(void)
{
	GRPFILE_checksum = 0;
	GRPFILE_checksum_half_word = 0;
}

/***********************************************************************
 *
 *           GRPFILE_GetChecksum
 */

static UINT16 GRPFILE_GetChecksum(void)
{
	return GRPFILE_checksum;
}

/***********************************************************************
 *
 *           GRPFILE_WriteWithChecksum
 *
 * Looks crazier than it is:
 *
 * chksum = 0;
 * chksum = cksum - 1. word;
 * chksum = cksum - 2. word;
 * ...
 *
 * if (filelen is even)
 *      great I'm finished
 * else
 *      ignore last byte
 */

static UINT GRPFILE_WriteWithChecksum(HFILE file, LPCSTR str, UINT size)
{
	UINT i;
	if (GRPFILE_checksum_half_word) {
		GRPFILE_checksum -= GRPFILE_checksum_last_byte;
	}
	for (i=0; i < size; i++) {
		if (GRPFILE_checksum_half_word) {
			GRPFILE_checksum -= str[i] << 8;
		} else {
			GRPFILE_checksum -= str[i];
		}
		GRPFILE_checksum_half_word ^= 1;
	}

	if (GRPFILE_checksum_half_word) {
		GRPFILE_checksum_last_byte = str[size-1];
		GRPFILE_checksum += GRPFILE_checksum_last_byte;
	}

	return _lwrite(file, str, size);
}


/***********************************************************************
 *
 *           GRPFILE_DoWriteGroupFile
 */

static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group)
{
  CHAR buffer[34];
  HLOCAL hProgram;
  INT    NumProg, Title, Progs, Icons, Extension;
  INT    CurrProg, CurrIcon, nCmdShow, ptr, seqnum;
  DWORD  sizeAnd, sizeXor;
  BOOL   need_extension;
  LPCSTR lpszTitle = LocalLock(group->hName);

  UINT16 checksum;

  GRPFILE_InitChecksum();

  /* Calculate offsets */
  NumProg = 0;
  Icons   = 0;
  Extension = 0;
  need_extension = FALSE;
  hProgram = group->hPrograms;
  while(hProgram)
    {
      PROGRAM *program = LocalLock(hProgram);
      LPCSTR lpszWorkDir = LocalLock(program->hWorkDir);

      NumProg++;
      GRPFILE_CalculateSizes(program, &Icons, &Extension, &sizeAnd, &sizeXor);

      /* Set a flag if an extension is needed */
      if (lpszWorkDir[0] || program->nHotKey ||
	  program->nCmdShow != SW_SHOWNORMAL) need_extension = TRUE;

      hProgram = program->hNext;
    }
  Title      = 34 + NumProg * 2;
  Progs      = Title + strlen(lpszTitle) + 1;
  Icons     += Progs;
  Extension += Icons;

  /* Header */
  buffer[0] = 'P';
  buffer[1] = 'M';
  buffer[2] = 'C';
  buffer[3] = 'C';

  PUT_SHORT(buffer,  4, 0); /* Checksum zero for now, written later */
  PUT_SHORT(buffer,  6, Extension);
  /* Update group->nCmdShow */
  if (IsIconic(group->hWnd))      nCmdShow = SW_SHOWMINIMIZED;
  else if (IsZoomed(group->hWnd)) nCmdShow = SW_SHOWMAXIMIZED;
  else                            nCmdShow = SW_SHOWNORMAL;
  PUT_SHORT(buffer,  8, nCmdShow);
  PUT_SHORT(buffer, 10, group->x);
  PUT_SHORT(buffer, 12, group->y);
  PUT_SHORT(buffer, 14, group->width);
  PUT_SHORT(buffer, 16, group->height);
  PUT_SHORT(buffer, 18, group->iconx);
  PUT_SHORT(buffer, 20, group->icony);
  PUT_SHORT(buffer, 22, Title);
  PUT_SHORT(buffer, 24, 0x0020); /* unknown */
  PUT_SHORT(buffer, 26, 0x0020); /* unknown */
  PUT_SHORT(buffer, 28, 0x0108); /* unknown */
  PUT_SHORT(buffer, 30, 0x0000); /* unknown */
  PUT_SHORT(buffer, 32, NumProg);

  if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 34)) return FALSE;

  /* Program table */
  CurrProg = Progs;
  CurrIcon = Icons;
  hProgram = group->hPrograms;
  while(hProgram)
    {
      PROGRAM *program = LocalLock(hProgram);

      PUT_SHORT(buffer, 0, CurrProg);
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 2))
	      return FALSE;

      GRPFILE_CalculateSizes(program, &CurrProg, &CurrIcon, &sizeAnd, &sizeXor);
      hProgram = program->hNext;
    }

  /* Title */
  if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszTitle, strlen(lpszTitle) + 1))
    return FALSE;

  /* Program entries */
  CurrProg = Progs;
  CurrIcon = Icons;
  hProgram = group->hPrograms;
  while(hProgram)
    {
      PROGRAM *program = LocalLock(hProgram);
      LPCSTR Name     = LocalLock(program->hName);
      LPCSTR CmdLine  = LocalLock(program->hCmdLine);
      LPCSTR IconFile = LocalLock(program->hIconFile);
      INT next_prog = CurrProg;
      INT next_icon = CurrIcon;

      GRPFILE_CalculateSizes(program, &next_prog, &next_icon, &sizeAnd, &sizeXor);
      PUT_SHORT(buffer,  0, program->x);
      PUT_SHORT(buffer,  2, program->y);
      PUT_SHORT(buffer,  4, program->nIconIndex);
      PUT_SHORT(buffer,  6, 0x048c);            /* unknown */
      PUT_SHORT(buffer,  8, sizeXor);
      PUT_SHORT(buffer, 10, sizeAnd * 8);
      PUT_SHORT(buffer, 12, CurrIcon);
      PUT_SHORT(buffer, 14, CurrIcon + 12 + sizeAnd);
      PUT_SHORT(buffer, 16, CurrIcon + 12);
      ptr = CurrProg + 24;
      PUT_SHORT(buffer, 18, ptr);
      ptr += strlen(Name) + 1;
      PUT_SHORT(buffer, 20, ptr);
      ptr += strlen(CmdLine) + 1;
      PUT_SHORT(buffer, 22, ptr);

      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 24) ||
	  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, Name, strlen(Name) + 1) ||
	  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, CmdLine, strlen(CmdLine) + 1) ||
	  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, IconFile, strlen(IconFile) + 1))
	return FALSE;

      CurrProg = next_prog;
      CurrIcon = next_icon;
      hProgram = program->hNext;
    }

  /* Icons */
#if 0  /* FIXME: this is broken anyway */
  hProgram = group->hPrograms;
  while(hProgram)
    {
      PROGRAM *program = LocalLock(hProgram);
      CURSORICONINFO *iconinfo = LocalLock(program->hIcon);
      LPVOID XorBits, AndBits;
      INT sizeXor = iconinfo->nHeight * iconinfo->nWidthBytes;
      INT sizeAnd = iconinfo->nHeight * ((iconinfo->nWidth + 15) / 16 * 2);
      /* DumpIcon16(LocalLock(program->hIcon), 0, &XorBits, &AndBits);*/

      PUT_SHORT(buffer, 0, iconinfo->ptHotSpot.x);
      PUT_SHORT(buffer, 2, iconinfo->ptHotSpot.y);
      PUT_SHORT(buffer, 4, iconinfo->nWidth);
      PUT_SHORT(buffer, 6, iconinfo->nHeight);
      PUT_SHORT(buffer, 8, iconinfo->nWidthBytes);
      buffer[10] = iconinfo->bPlanes;
      buffer[11] = iconinfo->bBitsPerPixel;

      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 12) ||
	  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, AndBits, sizeAnd) ||
	  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, XorBits, sizeXor)) return FALSE;

      hProgram = program->hNext;
    }
#endif

  if (need_extension)
    {
      /* write `PMCC' extension */
      PUT_SHORT(buffer, 0, 0x8000);
      PUT_SHORT(buffer, 2, 0xffff);
      PUT_SHORT(buffer, 4, 0x000a);
      buffer[6] = 'P'; buffer[7] = 'M';
      buffer[8] = 'C'; buffer[9] = 'C';
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 10))
	      return FALSE;

      seqnum = 0;
      hProgram = group->hPrograms;
      while(hProgram)
	{
	  PROGRAM *program = LocalLock(hProgram);
	  LPCSTR lpszWorkDir = LocalLock(program->hWorkDir);

	  /* Working directory */
	  if (lpszWorkDir[0])
	    {
	      PUT_SHORT(buffer, 0, 0x8101);
	      PUT_SHORT(buffer, 2, seqnum);
	      PUT_SHORT(buffer, 4, 7 + strlen(lpszWorkDir));
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6) ||
		  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszWorkDir, strlen(lpszWorkDir) + 1))
		return FALSE;
	    }

	  /* Hot key */
	  if (program->nHotKey)
	    {
	      PUT_SHORT(buffer, 0, 0x8102);
	      PUT_SHORT(buffer, 2, seqnum);
	      PUT_SHORT(buffer, 4, 8);
	      PUT_SHORT(buffer, 6, program->nHotKey);
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
	    }

	  /* Show command */
	  if (program->nCmdShow)
	    {
	      PUT_SHORT(buffer, 0, 0x8103);
	      PUT_SHORT(buffer, 2, seqnum);
	      PUT_SHORT(buffer, 4, 8);
	      PUT_SHORT(buffer, 6, program->nCmdShow);
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
	    }

	  seqnum++;
	  hProgram = program->hNext;
	}

      /* Write `End' extension */
      PUT_SHORT(buffer, 0, 0xffff);
      PUT_SHORT(buffer, 2, 0xffff);
      PUT_SHORT(buffer, 4, 0x0000);
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6)) return FALSE;
    }

  checksum = GRPFILE_GetChecksum();
  _llseek(file, 4, SEEK_SET);
  PUT_SHORT(buffer, 0, checksum);
  _lwrite(file, buffer, 2);

  return TRUE;
}
