grpfile.c 20.9 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1 2 3 4
/*
 * Program Manager
 *
 * Copyright 1996 Ulrich Schmid
Alexandre Julliard's avatar
Alexandre Julliard committed
5
 *           1997 Peter Schlaile
6 7 8 9 10 11 12 13 14 15 16 17 18
 *
 * 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
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
Alexandre Julliard's avatar
Alexandre Julliard committed
20 21
 */

22 23
#define WIN32_LEAN_AND_MEAN

Alexandre Julliard's avatar
Alexandre Julliard committed
24
#include "windows.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
25
#include "progman.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
26
#include "mmsystem.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40

#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)] = (s) & 0xff, (buffer)[(i)+1] = ((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);
41
static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group);
Alexandre Julliard's avatar
Alexandre Julliard committed
42 43 44 45 46 47 48 49 50 51 52

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

static VOID GRPFILE_ModifyFileName(LPSTR lpszNewName, LPCSTR lpszOrigName,
				   INT nSize, BOOL bModify)
{
53
  lstrcpynA(lpszNewName, lpszOrigName, nSize);
Alexandre Julliard's avatar
Alexandre Julliard committed
54 55
  lpszNewName[nSize-1] = '\0';
  if (!bModify) return;
56
  if (!lstrcmpiA(lpszNewName + strlen(lpszNewName) - 4, ".grp"))
Alexandre Julliard's avatar
Alexandre Julliard committed
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    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))
    {
Alexandre Julliard's avatar
Alexandre Julliard committed
84
      MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);
Alexandre Julliard's avatar
Alexandre Julliard committed
85 86 87 88 89 90
      return(0);
    }

  /* Interpret buffer */
  hGroup = GRPFILE_ScanGroup(LocalLock(hBuffer), size,
			     lpszPath, bFileNameModified);
Alexandre Julliard's avatar
Alexandre Julliard committed
91 92
  if (!hGroup)
    MAIN_MessageBoxIDS_s(IDS_GRPFILE_READ_ERROR_s, lpszPath, IDS_ERROR, MB_YESNO);
Alexandre Julliard's avatar
Alexandre Julliard committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106

  LocalFree(hBuffer);

  return(hGroup);
}

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

static BOOL GRPFILE_ReadFileToBuffer(LPCSTR path, HLOCAL *phBuffer,
				     INT *piSize)
{
107
  UINT    len, size;
Alexandre Julliard's avatar
Alexandre Julliard committed
108 109 110 111 112 113 114 115
  LPSTR  buffer;
  HLOCAL hBuffer, hNewBuffer;
  HFILE  file;

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

  size = 0;
116
  hBuffer = LocalAlloc(LMEM_FIXED, MALLOCHUNK + 1);
Alexandre Julliard's avatar
Alexandre Julliard committed
117 118 119 120 121 122 123 124
  if (!hBuffer) return FALSE;
  buffer = LocalLock(hBuffer);

  while ((len = _lread(file, buffer + size, MALLOCHUNK))
	 == MALLOCHUNK)
    {
      size += len;
      hNewBuffer = LocalReAlloc(hBuffer, size + MALLOCHUNK + 1,
125
				LMEM_MOVEABLE);
Alexandre Julliard's avatar
Alexandre Julliard committed
126 127 128 129 130 131 132 133 134 135 136
      if (!hNewBuffer)
	{
	  LocalFree(hBuffer);
	  return FALSE;
	}
      hBuffer = hNewBuffer;
      buffer = LocalLock(hBuffer);
    }

  _lclose(file);

137
  if (len == (UINT)HFILE_ERROR)
Alexandre Julliard's avatar
Alexandre Julliard committed
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    {
      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);

192
  /* unknown bytes 24 - 31 ignored */
Alexandre Julliard's avatar
Alexandre Julliard committed
193 194 195 196 197 198 199 200
  /*
    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);
    */
Alexandre Julliard's avatar
Alexandre Julliard committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

  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;
244
  UINT width, height, planes, bpp;
Alexandre Julliard's avatar
Alexandre Julliard committed
245 246 247 248 249 250 251 252 253 254

  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:
Alexandre Julliard's avatar
Alexandre Julliard committed
255 256
      MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s, lpszGrpFile,
			   IDS_WARNING, MB_OK);
Alexandre Julliard's avatar
Alexandre Julliard committed
257 258 259 260 261 262
    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);
263 264 265 266
      width           = GET_USHORT(iconinfo_ptr, 4);
      height          = GET_USHORT(iconinfo_ptr, 6);
      planes          = GET_USHORT(iconinfo_ptr, 10);
      bpp             = GET_USHORT(iconinfo_ptr, 11);
Alexandre Julliard's avatar
Alexandre Julliard committed
267 268 269 270 271 272 273
      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);
274 275 276 277
      width           = GET_USHORT(iconinfo_ptr, 4);
      height          = GET_USHORT(iconinfo_ptr, 6);
      planes          = GET_USHORT(iconinfo_ptr, 10);
      bpp             = GET_USHORT(iconinfo_ptr, 11);
Alexandre Julliard's avatar
Alexandre Julliard committed
278 279 280 281 282
    }

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

283
  hIcon = CreateIcon( Globals.hInstance, width, height, planes, bpp, iconANDbits_ptr, iconXORbits_ptr );
Alexandre Julliard's avatar
Alexandre Julliard committed
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

  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:
Alexandre Julliard's avatar
Alexandre Julliard committed
327 328
		  MAIN_MessageBoxIDS_s(IDS_UNKNOWN_FEATURE_s,
				       lpszGrpFile, IDS_WARNING, MB_OK);
Alexandre Julliard's avatar
Alexandre Julliard committed
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
		}
	    }
	  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];
350
  PROGGROUP *group = LocalLock(hGroup);
Alexandre Julliard's avatar
Alexandre Julliard committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
  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 */
Alexandre Julliard's avatar
Alexandre Julliard committed
371 372
	  MAIN_MessageBoxIDS_s(IDS_FILE_NOT_OVERWRITTEN_s, szPath,
			       IDS_INFO, MB_OK);
Alexandre Julliard's avatar
Alexandre Julliard committed
373 374 375
	  return FALSE;
	}
      /* Inform about the modified file name */
Alexandre Julliard's avatar
Alexandre Julliard committed
376 377 378
      if (IDCANCEL ==
	  MAIN_MessageBoxIDS_s(IDS_SAVE_GROUP_AS_s, szPath, IDS_INFO,
			       MB_OKCANCEL | MB_ICONINFORMATION))
Alexandre Julliard's avatar
Alexandre Julliard committed
379 380 381 382
	return FALSE;
    }

  {
Alexandre Julliard's avatar
Alexandre Julliard committed
383
    /* Warn about the (possible) incompatibility */
Alexandre Julliard's avatar
Alexandre Julliard committed
384
    CHAR msg[MAX_PATHNAME_LEN + 200];
385
    wsprintfA(msg,
Alexandre Julliard's avatar
Alexandre Julliard committed
386
	     "Group files written by this DRAFT Program Manager "
Alexandre Julliard's avatar
Alexandre Julliard committed
387
	     "possibly cannot be read by the Microsoft Program Manager!!\n"
Alexandre Julliard's avatar
Alexandre Julliard committed
388
	     "Are you sure to write %s?", szPath);
389 390
    if (IDOK != MessageBoxA(Globals.hMainWnd, msg, "WARNING",
                            MB_OKCANCEL | MB_DEFBUTTON2)) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
391 392 393
  }

  /* Open file */
394
  file = _lcreat(szPath, 0);
Alexandre Julliard's avatar
Alexandre Julliard committed
395 396 397 398 399 400 401
  if (file != HFILE_ERROR)
    {
      ret = GRPFILE_DoWriteGroupFile(file, group);
      _lclose(file);
    }
  else ret = FALSE;

Alexandre Julliard's avatar
Alexandre Julliard committed
402 403
  if (!ret)
    MAIN_MessageBoxIDS_s(IDS_FILE_WRITE_ERROR_s, szPath, IDS_ERROR, MB_OK);
Alexandre Julliard's avatar
Alexandre Julliard committed
404 405 406 407 408 409 410 411 412

  return(ret);
}

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

413 414
static VOID GRPFILE_CalculateSizes(PROGRAM *program, INT *Progs, INT *Icons,
                                   UINT *sizeAnd, UINT *sizeXor)
Alexandre Julliard's avatar
Alexandre Julliard committed
415
{
416 417 418 419 420 421 422 423 424 425
  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 );
Alexandre Julliard's avatar
Alexandre Julliard committed
426 427

  *Progs += 24;
428 429 430
  *Progs += strlen(LocalLock(program->hName)) + 1;
  *Progs += strlen(LocalLock(program->hCmdLine)) + 1;
  *Progs += strlen(LocalLock(program->hIconFile)) + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
431 432

  *Icons += 12; /* IconInfo */
433 434
  *Icons += *sizeAnd;
  *Icons += *sizeXor;
Alexandre Julliard's avatar
Alexandre Julliard committed
435 436
}

Alexandre Julliard's avatar
Alexandre Julliard committed
437 438 439 440 441 442 443 444 445
/***********************************************************************/
UINT16 GRPFILE_checksum;
BOOL GRPFILE_checksum_half_word;
BYTE GRPFILE_checksum_last_byte;
/***********************************************************************
 *
 *           GRPFILE_InitChecksum
 */

446
static void GRPFILE_InitChecksum(void)
Alexandre Julliard's avatar
Alexandre Julliard committed
447 448 449 450 451 452 453 454 455 456
{
	GRPFILE_checksum = 0;
	GRPFILE_checksum_half_word = 0;
}

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

457
static UINT16 GRPFILE_GetChecksum(void)
Alexandre Julliard's avatar
Alexandre Julliard committed
458 459 460 461 462 463 464
{
	return GRPFILE_checksum;
}

/***********************************************************************
 *
 *           GRPFILE_WriteWithChecksum
465
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
466
 * Looks crazier than it is:
467
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
468 469 470 471
 * chksum = 0;
 * chksum = cksum - 1. word;
 * chksum = cksum - 2. word;
 * ...
472
 *
Alexandre Julliard's avatar
Alexandre Julliard committed
473 474 475 476 477 478 479 480 481 482
 * 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) {
483
		GRPFILE_checksum -= GRPFILE_checksum_last_byte;
Alexandre Julliard's avatar
Alexandre Julliard committed
484 485 486 487 488 489 490 491 492
	}
	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;
	}
493

Alexandre Julliard's avatar
Alexandre Julliard committed
494 495 496 497
	if (GRPFILE_checksum_half_word) {
		GRPFILE_checksum_last_byte = str[size-1];
		GRPFILE_checksum += GRPFILE_checksum_last_byte;
	}
498

Alexandre Julliard's avatar
Alexandre Julliard committed
499 500 501 502
	return _lwrite(file, str, size);
}


Alexandre Julliard's avatar
Alexandre Julliard committed
503 504 505 506 507
/***********************************************************************
 *
 *           GRPFILE_DoWriteGroupFile
 */

508
static BOOL GRPFILE_DoWriteGroupFile(HFILE file, PROGGROUP *group)
Alexandre Julliard's avatar
Alexandre Julliard committed
509
{
Mike McCormack's avatar
Mike McCormack committed
510
  CHAR buffer[34];
Alexandre Julliard's avatar
Alexandre Julliard committed
511 512 513
  HLOCAL hProgram;
  INT    NumProg, Title, Progs, Icons, Extension;
  INT    CurrProg, CurrIcon, nCmdShow, ptr, seqnum;
514
  DWORD  sizeAnd, sizeXor;
Alexandre Julliard's avatar
Alexandre Julliard committed
515 516 517
  BOOL   need_extension;
  LPCSTR lpszTitle = LocalLock(group->hName);

Alexandre Julliard's avatar
Alexandre Julliard committed
518
  UINT16 checksum;
519

Alexandre Julliard's avatar
Alexandre Julliard committed
520
  GRPFILE_InitChecksum();
521

Alexandre Julliard's avatar
Alexandre Julliard committed
522 523 524 525 526 527 528 529 530 531 532 533
  /* 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++;
534
      GRPFILE_CalculateSizes(program, &Icons, &Extension, &sizeAnd, &sizeXor);
Alexandre Julliard's avatar
Alexandre Julliard committed
535 536 537 538 539 540 541 542

      /* 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;
543
  Progs      = Title + strlen(lpszTitle) + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
544 545 546 547 548 549
  Icons     += Progs;
  Extension += Icons;

  /* Header */
  buffer[0] = 'P';
  buffer[1] = 'M';
550
  buffer[2] = 'C';
Alexandre Julliard's avatar
Alexandre Julliard committed
551
  buffer[3] = 'C';
552

Alexandre Julliard's avatar
Alexandre Julliard committed
553
  PUT_SHORT(buffer,  4, 0); /* Checksum zero for now, written later */
Alexandre Julliard's avatar
Alexandre Julliard committed
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
  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);

573
  if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 34)) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
574 575 576 577 578 579 580 581 582 583

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

      PUT_SHORT(buffer, 0, CurrProg);
584
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 2))
Alexandre Julliard's avatar
Alexandre Julliard committed
585
	      return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
586

587
      GRPFILE_CalculateSizes(program, &CurrProg, &CurrIcon, &sizeAnd, &sizeXor);
Alexandre Julliard's avatar
Alexandre Julliard committed
588 589 590 591
      hProgram = program->hNext;
    }

  /* Title */
592
  if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszTitle, strlen(lpszTitle) + 1))
Alexandre Julliard's avatar
Alexandre Julliard committed
593 594 595 596 597 598 599 600 601 602 603 604
    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);
605 606
      INT next_prog = CurrProg;
      INT next_icon = CurrIcon;
Alexandre Julliard's avatar
Alexandre Julliard committed
607

608
      GRPFILE_CalculateSizes(program, &next_prog, &next_icon, &sizeAnd, &sizeXor);
Alexandre Julliard's avatar
Alexandre Julliard committed
609 610 611 612 613 614 615 616 617 618 619
      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);
620
      ptr += strlen(Name) + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
621
      PUT_SHORT(buffer, 20, ptr);
622
      ptr += strlen(CmdLine) + 1;
Alexandre Julliard's avatar
Alexandre Julliard committed
623 624
      PUT_SHORT(buffer, 22, ptr);

625
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 24) ||
626 627 628
	  (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))
Alexandre Julliard's avatar
Alexandre Julliard committed
629 630
	return FALSE;

631 632
      CurrProg = next_prog;
      CurrIcon = next_icon;
Alexandre Julliard's avatar
Alexandre Julliard committed
633 634 635 636
      hProgram = program->hNext;
    }

  /* Icons */
Alexandre Julliard's avatar
Alexandre Julliard committed
637
#if 0  /* FIXME: this is broken anyway */
Alexandre Julliard's avatar
Alexandre Julliard committed
638 639 640 641 642
  hProgram = group->hPrograms;
  while(hProgram)
    {
      PROGRAM *program = LocalLock(hProgram);
      CURSORICONINFO *iconinfo = LocalLock(program->hIcon);
Alexandre Julliard's avatar
Alexandre Julliard committed
643
      LPVOID XorBits, AndBits;
Alexandre Julliard's avatar
Alexandre Julliard committed
644 645
      INT sizeXor = iconinfo->nHeight * iconinfo->nWidthBytes;
      INT sizeAnd = iconinfo->nHeight * ((iconinfo->nWidth + 15) / 16 * 2);
646
      /* DumpIcon16(LocalLock(program->hIcon), 0, &XorBits, &AndBits);*/
Alexandre Julliard's avatar
Alexandre Julliard committed
647 648 649 650 651 652 653 654 655

      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;

656 657 658
      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;
Alexandre Julliard's avatar
Alexandre Julliard committed
659 660 661

      hProgram = program->hNext;
    }
Alexandre Julliard's avatar
Alexandre Julliard committed
662
#endif
Alexandre Julliard's avatar
Alexandre Julliard committed
663 664 665 666 667 668 669 670 671

  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';
672
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 10))
Alexandre Julliard's avatar
Alexandre Julliard committed
673
	      return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
674 675 676 677 678 679 680 681 682 683 684 685 686

      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);
687
	      PUT_SHORT(buffer, 4, 7 + strlen(lpszWorkDir));
688
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6) ||
689
		  (UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, lpszWorkDir, strlen(lpszWorkDir) + 1))
Alexandre Julliard's avatar
Alexandre Julliard committed
690 691 692 693 694 695 696 697 698 699
		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);
700
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
701 702 703 704 705 706 707 708 709
	    }

	  /* 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);
710
	      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 8)) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
711 712 713 714 715 716 717 718 719 720
	    }

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

      /* Write `End' extension */
      PUT_SHORT(buffer, 0, 0xffff);
      PUT_SHORT(buffer, 2, 0xffff);
      PUT_SHORT(buffer, 4, 0x0000);
721
      if ((UINT)HFILE_ERROR == GRPFILE_WriteWithChecksum(file, buffer, 6)) return FALSE;
Alexandre Julliard's avatar
Alexandre Julliard committed
722 723
    }

Alexandre Julliard's avatar
Alexandre Julliard committed
724 725 726 727 728
  checksum = GRPFILE_GetChecksum();
  _llseek(file, 4, SEEK_SET);
  PUT_SHORT(buffer, 0, checksum);
  _lwrite(file, buffer, 2);

Alexandre Julliard's avatar
Alexandre Julliard committed
729 730
  return TRUE;
}