Date: Wed, 20 Oct 1993 22:53:52 -0400 (EDT)
From: hshubs@BIX.com
Subject: Unfolder 2.0 source

This is the source code for Unfolder 2.0, so it might be named unfolder20.c,
and put in the same directory as unfolder20.hqx.

/*
 * The Unfolder - program to reconstitute MacBinary (II) encoded files.
 *
 * by various BIXen in the 'mac.hack/tutorial', started on 25 December 1989
 *
 * Originally written by Don Sample, Howard Shubs, and Bob Perkins on
 * the BYTE Information eXchange (BIX), this program is intended to allow
 * people with a C compiler and no way to transfer resources to their Mac
 * to create a way to deal with Mac files which have been downloaded to
 * non-Macintosh hardware.  This program will, when told to Unfold, 
 * reconstitute a MacBinary version 1 or 2 file to its original state.
 *
 * The program will work as-is under THINK C 5.0.2, with just the MacTraps
 * and ANSI-small libraries in the project file with it.  If you are using
 * other environments, you may need to modify the file.  If possible,
 * please use conditional compilation and send a diffs file to Howard Shubs
 * at hshubs@bix.com.
 *
 *
 *  #includes
 */
/*#include "Headers"*/
#include <stdio.h>
#include <stdarg.h>

#include <Traps.h>
#include <Errors.h>
#include <Types.h>
#include <GestaltEqu.h>
#include <OSUtils.h>
#include <Memory.h>
#include <Quickdraw.h>
#include <ToolUtils.h>
#include <Fonts.h>
#include <OSEvents.h>
#include <Menus.h>
#include <Events.h>
#include <Windows.h>
#include <Files.h>
#include <Resources.h>
#include <Dialogs.h>
#include <StandardFile.h>
#include <Desk.h>
#include <Finder.h>
#include <Pascal.h>
#include <TextEdit.h>


/*
 * #defines
 */

#define ALL_TYPES			-1L
#define BASE_RES_ID			400
#define NIL_POINTER			0L
#define MOVE_TO_FRONT		-1L
#define	REMOVE_ALL_EVENTS	0
#define MIN_SLEEP			0
#define NIL_MOUSE_REGION	0L
#define SUSPEND_RESUME_BIT	0x0001
#define RESUMING			1

#define APPLE_MENU_ID		128
#define FILE_MENU_ID		129
#define EDIT_MENU_ID		130

#define ABOUT_ITEM			1
#define UNFOLD_ITEM			1
#define QUIT_ITEM			2

#define MACBINARY_VERSION	129
#define SCROLL_WIDTH		15
#define NIL_STRING			"\p"

#define NUM_MASTER_BLOCKS	1		/* this number is semi-arbitrary */

#define	BF_OWN_APPL			0x0002	/* Some finder info flags not defined */
#define	F_INITED			0x0100	/* in FileMgr.h */
#define	F_CHANGED			0x0200
#define	F_BUSY				0x0400
/*
 *	MacBinary II Header #defines
 */

#define OLD_V_N			0
#define LEN_F_N			1
#define FILENAME		2
#define FILE_TYPE		65
#define FILE_CREA		69
#define ORIG_F_FLAGS	73
#define ZERO_FILL_I		74
#define FILE_V_POS		75
#define FILE_H_POS		77
#define WIN_FOL_ID		79
#define PROTECTED		81
#define ZERO_FILL_II	82
#define LEN_D_FORK		83
#define LEN_R_FORK		87
#define F_CREA_DATE		91
#define F_MOD_DATE		95
#define LEN_GET_INFO	99
#define FINDER_FLAGS	101
#define LEN_TOT_F		116
#define LEN_SEC_H		120
#define MBII_VERS_UP	122
#define MBII_VERS_READ	123
#define	CRC				124

/*
 *	Structures
 */

typedef struct Header				/* MacBinary header data */
	{
	unsigned char	oldVN;			/* old version number */
	char			fName[64];		/* file name */
	FInfo			finderInfo;		/* Original Finder information record */
	unsigned char	protected;
	long			lenDF;			/* length of data fork */
	long			lenRF;			/* length of resource fork */
	long			fCreaDate;		/* file creation date */
	long			fModDate;		/* file's last modified date */
	int				lenGetInfo;		/* length of Get Info comment */
	unsigned char	finderFlags;	/* Low byte of finder flags */
	long			lenTotF;		/* length of total files for unpacking */
	int				lenSecH;		/* length of any secondary header */
	unsigned char	mbIIVersUp;		/* Version of MB II used for upload */
	unsigned char	mbIIVersRead;	/* Version of MB II needed to read file */
	int				crc;
	} Header;

typedef struct DialogItem			/* Dialog item */
{
	long			pointer;
	Rect			boundsRect;
	unsigned char	itemType;
	unsigned char	itemData[3];
} DialogItem;

typedef struct DialogList			/* DITL record */
{
	short			itemCount;
	DialogItem		items[2];
} DialogList;


/*
 *	Function Prototypes:
 */

void			AboutAppl (void);
/*short int		CalcCRC(unsigned char *dataBuf, short int size);*/
short int		CalcCRC(register unsigned char *dataBuf, register int size);
OSErr			CreateFile(Str255 fName, int vRefNum, OSType creator,
				 OSType type);
Rect 			DeviceRect (void);
int				GetAnEvent(int eventMask, EventRecord *event, long sleep,
				 RgnHandle mouseRgn);
void 			HandleAppleChoice (short theItem);
void 			HandleFileChoice (short theItem);
void 			HandleEditChoice (short theItem);
void 			HandleEvent (EventRecord *theEvent);
void 			HandleMenuChoice (long menuChoice);
void 			HandleMouseDown (EventRecord *thisEvent);
unsigned char	HeaderCheck (short int, Header *);
void			Init (void);
void			MessageDialog(char *format, ...);
void			Pause(void);
void 			ProcessFile (void);
void			ToolBoxInit(void);
int				TrapAvailable(unsigned trapNumber, int trapType);
void 			Unfold (short int refNum, Header *thisHeader,
				 SFReply *outputFile);
int				WNEIsImplemented(void);


/*
 *  Globals
 *
 */
EventRecord		gTheEvent;
MenuHandle		gAppleMenu;
MenuHandle		gFileMenu;
MenuHandle		gEditMenu;
Point			gWhere;
Boolean			gDone;
Boolean			gWNEExists;
short			gQDVersion;


/*
 *	FUNCTION:
 *		ToolBoxInit
 *
 *	PURPOSE:
 *		Initialize the ToolBox Managers
 */

void	ToolBoxInit()
{
	InitGraf (&(qd.thePort));
	InitFonts ();
	FlushEvents (everyEvent, REMOVE_ALL_EVENTS);
	InitWindows ();
	InitMenus ();
	TEInit ();
	InitDialogs (NIL_POINTER);
	InitCursor ();
}

/*
 *	FUNCTION:
 *		Pause
 *
 *	PURPOSE:
 *		Wait for the user to click the mouse, or to press the return
 *		or Enter key.
 */

void	Pause()

{
	int			button;
	EventRecord	myEvent;
	
	button = false;
	while (!button)
	{
		if (GetAnEvent(everyEvent, &myEvent, 0L, NIL_POINTER))
		{
			if (myEvent.what == mouseDown)
				button = true;
			else if (myEvent.what == keyDown)
			{
				if (((myEvent.message & charCodeMask) == 0x0d) ||
				 ((myEvent.message & charCodeMask) == 0x03))
				 	button = true;
			}
		}
	}
	while (StillDown())
		;
}

/*
 *	FUNCTION:
 *		MessageDialog
 *
 *	PURPOSE:
 *		Display an error message and then pause until the user clicks the
 *		mouse button
 *
 *	INPUT:
 *		char	*format			- A NULL terminated format string, using 
 *								  the same rules as printf.
 *		...						- Other parameters as specified in the 
 *								  format string
 */
 
void	MessageDialog(char *format, ...)

{
	char			messageStr[256];
	va_list			data;
	OSErr			thisErr;
	
	va_start(data, format);
	vsprintf(messageStr, format, data);
	CtoPstr(messageStr);
	ParamText ((ConstStr255Param) messageStr, NIL_STRING, NIL_STRING, NIL_STRING);
	thisErr = CautionAlert (BASE_RES_ID, NIL_POINTER);
	va_end(data);
}

#define _Unimplemented	0xA89F
#define _WaitNextEvent	0xA860

/*
 *	FUNCTION:
 *		GetAnEvent
 *
 *	PURPOSE:
 *		Get an event from the input queue using whichever is appropriate:
 *		GetNextEvent, or WaitNextEvent
 *
 *	INPUTS:
 *		int			eventMask		- Types of events to get
 *		EventRecord	*event			- Where to put retreived event
 *		long		sleep			- amount of time to surrender to
 *									  background tasks
 *		RgnHandle	mouseRgn		- Global region containing mouse.
 *
 *	RETURNS:
 *		int			TRUE if an event was retrieved.
 */

enum		/* Which routine to call: Get... or WaitNextEvent			*/
	{
	notKnown,
	getNext,
	waitNext
	};
	

int		GetAnEvent(int eventMask, EventRecord *event, long sleep,
		 RgnHandle mouseRgn)

{
	int		result;
	static	int		callRoutine = notKnown;
	
	if (callRoutine == notKnown)
	{
		if (WNEIsImplemented())
			callRoutine = waitNext;
		else
			callRoutine = getNext;
	}
	if (callRoutine == waitNext)
		result = WaitNextEvent(eventMask, event, sleep, mouseRgn);
	else
	{
		SystemTask();
		result = GetNextEvent(eventMask, event);
	}
	return(result);
}

/*
 *	FUNCTION:
 *		TrapAvailable
 *
 *	PURPOSE:
 *		Determine if a specific trap routine is available
 *
 *	INPUTS:
 *		unsigned	tNumber		- trap number
 *		int			tType		- trap type (OS or Toolbox)
 *
 *	RETURNS:
 *		int			TRUE if trap is available
 *					false otherwise
 */

TrapAvailable(unsigned tNumber, int tType)

{
	/* Check and see if the trap exists. */
	
	return(NGetTrapAddress(tNumber, tType) !=
	 GetTrapAddress(_Unimplemented));
}

/*
 *	FUNCTION:
 *		WNEIsImplemented
 *
 *	PURPOSE:
 *		Determine if the WaitNextEvent routine is available
 *
 *	RETURNS:
 *		int		TRUE if WNE is available
 *				FALSE otherwise
 */
	
WNEIsImplemented()

{
	
	SysEnvRec theWorld; /* used to check if machine has new traps */
	
	/*
	 *	Since WaitNextEvent and HFSDispatch both have the same trap
	 *	number ($60), we can only call TrapAvailable for WaitNextEvent
	 *	if we are on a machine that supports separate OS and Toolbox
	 *	trap tables. We call SysEnvirons and check if machineType < 0.
	 */
	 
	SysEnvirons(1, &theWorld);
	
	/* Even if we got an error from SysEnvirons, the SysEnvirons glue
	   has set up machineType. */
	
	if (theWorld.machineType < 0)
	{
		/* this ROM doesn't have separate trap tables or WaitNextEvent */
		return(false);
	}
	else
	{
		/* check for WaitNextEvent */
		return(TrapAvailable(_WaitNextEvent, ToolTrap));
	}
}

/*
 *	FUNCTION:
 *		CreateFile
 *
 *	PURPOSE:
 *		Create a file, deleting any previous version if necessary.
 *
 *	INPUTS:
 *		Str255	fName		- File name
 *		int		vRefNum		- Directory to create file in
 *		OSType	creator		- File's creator ID
 *		OSType	type		- File type
 *
 *	RETURNS:
 *		OSErr	- noErr if successful.
 */

OSErr	CreateFile(Str255 fName, int vRefNum, OSType creator, OSType type)

{
	OSErr	thisErr;
	
	thisErr = Create (fName, vRefNum, creator, type);
	if (thisErr == dupFNErr)
	{
		thisErr = FSDelete (fName, vRefNum);
		if (thisErr == noErr)
			thisErr = Create (fName, vRefNum, creator, type);
	}
	return (thisErr);
}

/*
 *	FUNCTION:
 *		CalcCRC
 *
 *	PURPOSE:
 *		Calculate a CCITT CRC for a data buffer
 *
 *	INPUTS:
 *		unsigned char	*data	- pointer to data buffer to perform CRC on
 *		int				size	- size of data buffer
 *
 *	RETURNS:
 *		int				- CRC for data buffer.  If data buffer contains as its
 *						  last 2 bytes the CRC for the previous bytes the return
 *						  value is 0
 */

#define CCITT_CRC_GEN	0x1021

short int	CalcCRC(register unsigned char *dataBuf, register int size)

{
	register unsigned short	crc = 0;
	register unsigned short	dataByte;
	register int	i;
	
	while (size--)
	{
		dataByte = *dataBuf++ << 8;
		for (i = 8; i > 0; i--)
		{
			if ((dataByte ^ crc) & 0x8000)
				crc = (crc << 1) ^ CCITT_CRC_GEN;
			else
				crc <<= 1 ;
			dataByte <<= 1;
		}
	}
	return(crc);
}


/*
 *	FUNCTION:
 *		AboutAppl
 *
 *	PURPOSE:
 *		Tell about the program
 *
 */

void AboutAppl (void)
{
	Rect		bounds = {58, 108, 192, 400};
	WindowPtr	thisWindow;
	GrafPtr		oldPort;

	GetPort (&oldPort);
	thisWindow = NewWindow (NIL_POINTER, &bounds, "\p", true, 1, (WindowPtr) -1L,
							false, 0L);
	bounds = thisWindow->portRect;
	SetPort (thisWindow);
	/*		  123456789   0   123   4   567890123456789   0123456789	*/
	TextBox ("Unfolder\015\015by\015\015Howard S Shubs\015Don Sample\015Bob Perkins", 51L, &bounds, 1);
	Pause ();
	SetPort (oldPort);
	DisposeWindow (thisWindow);
}


/*
 *	FUNCTION:
 *		DeviceRect
 *
 *	PURPOSE:
 *		Get the bounds rect of the main screen.
 *
 *	RETURNS:
 *		Rect	the bounds rect of the main screen.
 *
 */

Rect DeviceRect (void)
{
	GDHandle		device;
	Rect			devRect;

	if (gQDVersion > 0)
	{
		device = GetMainDevice ();
		devRect = (**(device)).gdRect;
	}
	else
		devRect = qd.screenBits.bounds;
	
	return devRect;
}


/*
 *	FUNCTION:
 *		HandleAppleChoice
 *
 *	PURPOSE:
 *		Deal with selection from the Apple menu
 *
 *	INPUTS:
 *		short int	theItem: contains the selection from the menu
 *
 */

void HandleAppleChoice (short theItem)
{
	Str255		accName;
	int			accNumber;
	short int	itemNumber;
	DialogPtr	AboutDialog;
	
	switch (theItem)
	{
		case ABOUT_ITEM:
			AboutAppl ();
			break;
		default:
			GetItem (gAppleMenu, theItem, accName);
			accNumber = OpenDeskAcc (accName);
			break;		
	}
}

/*
 *	FUNCTION:
 *		HandleEditChoice
 *
 *	PURPOSE:
 *		Deal with selection from the Edit menu by passing them to the
 *		system.
 *
 *	INPUTS:
 *		short int	theItem: contains the selection from the menu
 *
 */

void HandleEditChoice (short theItem)
{
	if (!SystemEdit (theItem - 1))
		;
}


/*
 *	FUNCTION:
 *		HandleFileChoice
 *
 *	PURPOSE:
 *		Deal with selection from the File menu
 *
 *	INPUTS:
 *		short int	theItem: contains the selection from the menu
 *
 */
/*{}{}{}*/
void HandleEvent (EventRecord *thisEvent)
{
	char	theChar;

	if (gWNEExists)
		WaitNextEvent (everyEvent, thisEvent, MIN_SLEEP, NIL_MOUSE_REGION);
	else
	{
		SystemTask ();
		GetNextEvent (everyEvent, thisEvent);
	}
	
	switch (thisEvent->what)
	{
		case nullEvent:
			break;
		case mouseDown:
			HandleMouseDown (thisEvent);
			break;
		case keyDown:
		case autoKey:
			theChar = (thisEvent->message) & charCodeMask;
			if (( (thisEvent->modifiers) & cmdKey) != 0)
				HandleMenuChoice (MenuKey(theChar));
			break;
		case updateEvt:
/*			BeginUpdate ((WindowPtr) (thisEvent->message) );
			UpdateWindow ((WindowPtr) (thisEvent->message));
			EndUpdate ((WindowPtr) (thisEvent->message) );*/
			break;
		case app4Evt:
			if ( ( (thisEvent->message) & SUSPEND_RESUME_BIT) == RESUMING)
			{
/*					gState.applFlags.isSuspended =
									((thisEvent->message) & 0x01) == 0;*/
			}
			break;
	}
}

/*
 *	FUNCTION:
 *		HandleFileChoice
 *
 *	PURPOSE:
 *		Deal with selection from the File menu
 *
 *	INPUTS:
 *		short int	theItem: contains the selection from the menu
 *
 */

void HandleFileChoice (short theItem)
{
	switch (theItem)
	{
		case UNFOLD_ITEM:
			ProcessFile ();
			break;
		case QUIT_ITEM:
			gDone = true;
			break;
	}
}

/*
 *	FUNCTION:
 *		HandleMenuChoice
 *
 *	PURPOSE:
 *		Deal with selection of menu items
 *
 *	INPUTS:
 *		long int	menuChoice: contains the selection from a menu
 *
 *	RETURNS:
 *		unsigned char				- TRUE if the file is MacBinary
 *		
 */

void HandleMenuChoice (long menuChoice)
{
	short	theMenu;
	short	theItem;
	
	if (menuChoice != 0)
	{
		theMenu = HiWord (menuChoice);
		theItem = LoWord (menuChoice);
		switch (theMenu)
		{
			case APPLE_MENU_ID:
				HandleAppleChoice (theItem);
				break;
			case FILE_MENU_ID:
				HandleFileChoice (theItem);
				break;
			case EDIT_MENU_ID:
				HandleEditChoice (theItem);
				break;
		}
		HiliteMenu (0);
	}
}

/*
 *	FUNCTION:
 *		HandleMouseDown
 *
 *	PURPOSE:
 *		Deal with mouseDown events.
 *
 *	INPUTS:
 *		EventRecord	*thisEvent   points to the current event record.
 *
 */

void HandleMouseDown (EventRecord *thisEvent)
{
	Rect			devRect;
	long			menuChoice;
	short int		thePart;
	WindowPeek		window;
	long			windSize;
	
	thePart = FindWindow ((thisEvent->where), (WindowPtr *) &window);
	switch (thePart)
	{
		case inMenuBar:
			menuChoice = MenuSelect ((thisEvent->where));
			HandleMenuChoice (menuChoice);
			break;
		case inSysWindow:
			SystemClick (thisEvent, (WindowPtr) window);
			break;
		case inDrag:
			devRect = DeviceRect ();
			DragWindow ((WindowPtr) window, (thisEvent->where), &devRect);
			break;
		case inGrow:
			devRect = DeviceRect ();
			windSize = GrowWindow ((WindowPtr) window, (thisEvent->where),
								 &devRect);
			if (windSize > 0)
			{
				Rect	bad;
				Point	p;
			
				/* invalidate the part of the window with the grow icon */	
				bad = (window->port).portRect;
				bad.top = bad.bottom - SCROLL_WIDTH;
				bad.left = bad.right - SCROLL_WIDTH;
				InvalRect (&bad);
			}
			SizeWindow ((WindowPtr) window, LoWord (windSize),
						HiWord (windSize), true);
			break;
		case inGoAway:
			/*if (TrackGoAway ((WindowPtr) window, (thisEvent->where) ) )
				DoCloseWindow ((WindowPtr) window);*/
			break;
	}
}

/*
 *	FUNCTION:
 *		HeaderCheck
 *
 *	PURPOSE:
 *		Look at a selected file to determine if it is a MacBinary (II)
 *		file or not.
 *
 *	INPUTS:
 *		short int		refNum		- reference number of the opened file to be
 *								 	  inspected.
 *		Header			*thisHeader	- MacBinary header record to return data in.
 *
 *	RETURNS:
 *		unsigned char				- TRUE if the file is MacBinary
 *		
 */

unsigned char HeaderCheck (short int refNum, Header *thisHeader)
{
	long int		count = 128;
	OSErr			thisErr;
	short int		i;
	unsigned char	*buffer;
	unsigned char	soFarSoGood = false;

	/* Go to the beginning of the file pointed to by refNum and
	   read the first (count) bytes. */
	thisErr = SetFPos (refNum, fsFromStart, 0L);
	if (thisErr == noErr)
	{
		buffer = (unsigned char *) NewPtr (count);
		if ((thisErr = MemError()) == noErr)
		{
			thisErr = FSRead (refNum, &count, buffer);
			if (thisErr == noErr)
			{
				/* Transfer the data read from the file into thisHeader */
				/* First, check to make sure that the mbz bytes ARE zero */
				if (buffer[OLD_V_N] == 0 && buffer[ZERO_FILL_I] == 0)
					soFarSoGood = true;
				
				/* Transfer the file name, and make sure its length is > 0 */
				if (buffer[LEN_F_N] > 0 && buffer[LEN_F_N] < 64 && soFarSoGood)
				{
					BlockMove (&buffer[LEN_F_N], &(thisHeader->fName),
								buffer[LEN_F_N]+1);
					
					/* Get the finderInfo */
					BlockMove (&buffer[FILE_TYPE], &(thisHeader->finderInfo),
								sizeof(FInfo));
										
					/* Get the "protected" flag */
					thisHeader->protected = buffer[PROTECTED];
					
					/* Get the fork sizes */
					BlockMove (&buffer[LEN_D_FORK], &(thisHeader->lenDF),
								8);
					
					/* Get file's dates */
					BlockMove (&buffer[F_CREA_DATE], &(thisHeader->fCreaDate),
								8);

					/* Get length of "Get Info" comment */
					BlockMove (&buffer[LEN_GET_INFO], &(thisHeader->lenGetInfo),
								2);
					
					/* Low Finder flags */
					thisHeader->finderFlags = buffer[FINDER_FLAGS];
					
					/* Total size of files */
					BlockMove (&buffer[LEN_TOT_F], &(thisHeader->lenTotF),
								4);
					
					/* Get length of secondary header */
					BlockMove (&buffer[LEN_SEC_H], &(thisHeader->lenSecH),
								2);
					
					/* Get the versions of MacBinary II */
					thisHeader->mbIIVersUp = buffer[MBII_VERS_UP];
					thisHeader->mbIIVersRead = buffer[MBII_VERS_READ];
					
					/* Get the CRC of the header */
					BlockMove (&buffer[CRC], &(thisHeader->crc), 2);
					
					soFarSoGood = (CalcCRC(buffer, 124)
											== thisHeader->crc)
								&& (buffer[MBII_VERS_READ] == MACBINARY_VERSION);

					if (!soFarSoGood && (buffer[ZERO_FILL_II] == 0))
					{
						/* Make more comparisons to make sure that this
						   is really a MacBinary I file. */
						for (i = 101; buffer[i]==0 && i < 126; i++);
						
						/* If "i" is >= 126, the loop above completed
						   without finding a non-zero, so this is MacBinary I.  */
						if (soFarSoGood = i >= 126)
						{
							/* It is, it really _is_ MacBinary I */
							
							/* Low Finder flags */
							thisHeader->finderFlags = 0;
					
							/* Total size of files */
							thisHeader->lenTotF = 0;
					
							/* Get length of secondary header */
							thisHeader->lenSecH = 0;
					
							/* Get the versions of MacBinary II */
							thisHeader->mbIIVersUp = MACBINARY_VERSION;
							thisHeader->mbIIVersRead = MACBINARY_VERSION;
					
							/* Get the CRC of the header */
							thisHeader->crc = 0;
						}
					}
					
				}
				else
					soFarSoGood = false;
			};
			DisposPtr ((Ptr) buffer);
			thisErr = MemError ();
		}
	}
	if (thisErr != noErr)
		MessageDialog("Error: %d reading MacBinary header", thisErr);
	else if (!soFarSoGood)
		MessageDialog("This is not a MacBinary file");
	return soFarSoGood;
}


/*
 *	FUNCTION:
 *		Unfold
 *
 *	PURPOSE:
 *		Restore the forks of a MacBinary encoded file.
 *
 *	INPUTS:
 *		short int		refNum		- reference number of the opened file to be
 *								 	  used as input.
 *		Header			*thisHeader - MacBinary header information
 *		SFReply			*thisFile	- Information needed to access the created
 *									  file.
 *
 *	RETURNS:
 *		unsigned char				- TRUE if the file is MacBinary
 *		Header			*thisHeader	- MacBinary header record to return data in.
 *		
 */

void Unfold (short int refNum, Header *thisHeader, SFReply *thisFile)
{
	char		*buffer;
	char		bufferAllocated;
	long int	buffSize;
	long int	forkSize;
	long int	offSet;
/*	long int	grow;*/
	short int	outRefNum;		/* reference number for the output file */
	OSErr		thisErr;		/* status code */
	
	/* Allocate memory for a buffer by this rule:
		Never allocate less than MIN_BUFFER, or more than MAX_BUFFER.
		Between those two, allocate what's available less LEAVE_FREE,
		as long as that isn't less than MIN_BUFFER. */
#define MIN_BUFFER	4*1024L
#define MAX_BUFFER	256*1024L
#define LEAVE_FREE	32*1024L
	buffSize = MaxBlock ();  /*MaxMem (&grow);*/
	buffSize = buffSize > MAX_BUFFER + LEAVE_FREE ?
				MAX_BUFFER :
				buffSize < MIN_BUFFER ?
					MIN_BUFFER :
					buffSize - LEAVE_FREE < MIN_BUFFER ?
						MIN_BUFFER :
						buffSize - LEAVE_FREE;

	buffer = NewPtr (buffSize);
	bufferAllocated = (thisErr = MemError()) == noErr;
	
	if (!bufferAllocated)
		MessageDialog ("Couldn't allocate a work buffer.  Give this program more memory, then run it again.");
	
	/* Is there a data fork?  If there is, rebuild it. */
	if ((thisHeader->lenDF > 0) && bufferAllocated)
	{
		Ptr		source;
		Size	count;
		Str255	dest;
	
		source = (Ptr) &((*thisFile).fName);
		count = (char) *source;
		count = BitAnd (count, 0x000000ffL) + 1L;
		BlockMove (source, dest, count);	/* {}{}{} */
	
		forkSize = thisHeader->lenDF;
		if ((thisErr = FSOpen (dest, thisFile->vRefNum, &outRefNum))
		 == noErr)
		{
			/* position beyond the MacBinary header */
			if ((thisErr = SetFPos (refNum, fsFromStart, 128)) == noErr)
			{
				while (thisErr == noErr && forkSize > buffSize)
				{
					thisErr = FSRead (refNum, &buffSize, buffer);
					if (thisErr == noErr)
						thisErr = FSWrite (outRefNum, &buffSize, buffer);
					forkSize -= buffSize;
				}
				if (thisErr == noErr)
				{
					thisErr = FSRead(refNum, &forkSize, buffer);
					if (thisErr == noErr)
						thisErr = FSWrite (outRefNum, &forkSize, buffer);
				}
			}
			if (thisErr == noErr)
			{
				thisErr = FSClose (outRefNum);
				if (thisErr == noErr)
					thisErr = FlushVol (NIL_POINTER, thisFile->vRefNum);
				else
					FlushVol (NIL_POINTER, thisFile->vRefNum);
			}
			else
			{
				FSClose (outRefNum);
				FlushVol (NIL_POINTER, thisFile->vRefNum);
			}
		}
	}
	
	/* Is there a resource fork?  If there is, rebuild it. */
	if ((thisHeader->lenRF > 0) && bufferAllocated)
	{
		Ptr		source;
		Size	count;
		Str255	dest;
	
		source = (Ptr) &((*thisFile).fName);
		count = (char) *source;
		count = BitAnd (count, 0x000000ffL) + 1L;
		BlockMove (source, dest, count);	/* {}{}{} */
	
		forkSize = thisHeader->lenRF;
		if ((thisErr = OpenRF (dest, thisFile->vRefNum, &outRefNum))
		 == noErr)
		{
			/* position beyond the MacBinary header and the Data Fork */
			offSet = (128 - (thisHeader->lenDF % 128)) & 0x7f;
			if ((thisErr = SetFPos (refNum, fsFromStart,
			 (thisHeader->lenDF) + offSet + 128)) == noErr)
			{
				while (thisErr == noErr && forkSize > buffSize)
				{
					thisErr = FSRead (refNum, &buffSize, buffer);
					if (thisErr == noErr)
						thisErr = FSWrite (outRefNum, &buffSize, buffer);
					forkSize -= buffSize;
				}
				if (thisErr == noErr)
				{
					thisErr = FSRead(refNum, &forkSize, buffer);
					if (thisErr == noErr)
						thisErr = FSWrite (outRefNum, &forkSize, buffer);
				}
			}
			if (thisErr == noErr)
			{
				thisErr = FSClose (outRefNum);
				if (thisErr == noErr)
					thisErr = FlushVol (NIL_POINTER, thisFile->vRefNum);
				else
					FlushVol (NIL_POINTER, thisFile->vRefNum);
			}
			else
			{
				FSClose (outRefNum);
				FlushVol (NIL_POINTER, thisFile->vRefNum);
			}
		}
	}
	if (bufferAllocated)
		DisposPtr (buffer);
	if (thisErr != noErr)
		MessageDialog("Error: %d unfolding MacBinary file", thisErr);
}


/*
 *	FUNCTION:
 *		ProcessFile
 *
 *	PURPOSE:
 *		Determine our input and output files, then do the processing.
 *
 */

void ProcessFile (void)
{
	Boolean			running;
	int				i;
	SFTypeList		typeList;
	SFReply			inputFile, outputFile;
	Header			mbHeader;
	OSErr			thisErr;
	short int		refNum;
	ParamBlockRec	paramBlock;
	unsigned int	finderFlags;
	
	SFGetFile (gWhere, NIL_POINTER, NIL_POINTER, ALL_TYPES, typeList,
			NIL_POINTER, &inputFile);
	running = inputFile.good == true;
 		
	if (running)
	{
		if ((thisErr = FSOpen (inputFile.fName, inputFile.vRefNum,
		 &refNum)) != noErr)
		{
			PtoCstr((unsigned char *) inputFile.fName);
			MessageDialog("Error: %d opening file %s", thisErr, inputFile.fName);
		}

		/* read the MacBinary header.  If it's okay, decode the file. */
		else if (HeaderCheck (refNum, &mbHeader))
		{
			Str255	*string = (Str255 *) &(mbHeader.fName);
			
			SFPutFile (gWhere, NIL_POINTER, *string,
					NIL_POINTER, &outputFile);
			if (outputFile.good)
            {
               	if ((thisErr = CreateFile(outputFile.fName,
               	 outputFile.vRefNum, mbHeader.finderInfo.fdCreator,
				 mbHeader.finderInfo.fdType)) != noErr)
				{
				 	PtoCstr((unsigned char *) outputFile.fName);
				 	MessageDialog("Error: %d creating file %s", thisErr,
				 	 outputFile.fName);
					running = false;
				}
				else
				{
					/* copy data & resource forks from input file */
					Unfold (refNum, &mbHeader, &outputFile);
					
					paramBlock.fileParam.ioCompletion = NIL_POINTER;
					paramBlock.fileParam.ioNamePtr = (StringPtr) &outputFile.fName;
					paramBlock.fileParam.ioVRefNum = outputFile.vRefNum;
					paramBlock.fileParam.ioFVersNum = 0;
					/* Set the finder Info for the file */
					paramBlock.fileParam.ioFlFndrInfo = mbHeader.finderInfo;
					finderFlags = mbHeader.finderInfo.fdFlags;
					finderFlags |= mbHeader.finderFlags;
					finderFlags &= ~(fOnDesk | BF_OWN_APPL | F_INITED | 
					 F_CHANGED | F_BUSY);
					paramBlock.fileParam.ioFlFndrInfo.fdFlags = finderFlags;
					paramBlock.fileParam.ioFlFndrInfo.fdFldr = 0;
					paramBlock.fileParam.ioFlFndrInfo.fdLocation.h = 0;
					paramBlock.fileParam.ioFlFndrInfo.fdLocation.v = 0;
					/* Set the Creation and Modification dates on the file */
					paramBlock.fileParam.ioFlCrDat = mbHeader.fCreaDate;
					paramBlock.fileParam.ioFlMdDat = mbHeader.fModDate;
					thisErr = PBSetFInfo (&paramBlock, false);
					if (thisErr != noErr)
						MessageDialog("Error: %d, setting file attributes", thisErr);
				}
			}
		}
		thisErr = FSClose (refNum);
	}
}


/*
 *	FUNCTION:
 *		Init
 *
 *	PURPOSE:
 *		Initialize the environment, establish menus, and set variables.
 *
 *  SIDE EFFECTS:
 *		Initializes the environment, establishes menus, and initializes
 *		the global variables.
 *		
 */

void Init (void)
{
	long			response;
	Boolean			gestaltExists;
	Boolean			sysEnvironsExists;
	SysEnvRec		*theWorld;
	OSErr			thisErr;
	Handle			thisHandle;
	DialogList		errorDITL;
	AlertTemplate	thisAlert;


    /* Initialize everything                           */
    /* Init the toolbox routines */
	ToolBoxInit();
	
	/* Make menus */
	gAppleMenu = NewMenu (APPLE_MENU_ID, "\p\024");
	gFileMenu = NewMenu (FILE_MENU_ID, "\pFile");
	gEditMenu = NewMenu (EDIT_MENU_ID, "\pEdit");
	InsertMenu (gAppleMenu, 0);
	InsertMenu (gFileMenu, 0);
	InsertMenu (gEditMenu, 0);
	
	/* Add items to menus */
	AppendMenu (gAppleMenu, "\pAbout Unfolder...;(-");
	AddResMenu (gAppleMenu, 'DRVR');
	AppendMenu (gFileMenu, "\pUnfold.../U;Quit/Q");
	AppendMenu (gEditMenu, "\pUndo/Z;(-;Cut/X;Copy/C;Paste/X;Clear");

	/* Draw the menu bar */
	DrawMenuBar ();
		
	/* Specify the point at which the SF dialogs will appear */
	gWhere.v = 100;
	gWhere.h = 100;

	/* We're not done yet */
	gDone = false;
	
	/* Can we call WaitNextEvent? */
	gWNEExists = WNEIsImplemented();
	
	/* What kind of Quickdraw do we have? */
	gestaltExists = TrapAvailable (_GestaltDispatch, ToolTrap);
	sysEnvironsExists = TrapAvailable (_SysEnvirons, OSTrap);
	if (gestaltExists)
		thisErr = Gestalt ('qd  ', &response);
	else 
	{
		if (sysEnvironsExists)
		{
			theWorld = (SysEnvRec *) NewPtr (sizeof (SysEnvRec));
			if (theWorld == NIL_POINTER)
			{
				thisErr = MemError ();
				MessageDialog("Error: %d allocating memory", thisErr);
			}
			thisErr = SysEnvirons (1, theWorld);
			response = (long) theWorld->hasColorQD;
			DisposPtr ((Ptr) theWorld);
		}
		else
			response = 0L;
	}
	gQDVersion = LoWord (response) >> 8;
	
	
	/* Do the miscellaneous resources exist yet?  Check for one.  If
	   that doesn't exist, assume that none of them do and create them. */
	thisHandle = GetResource ('ALRT', BASE_RES_ID);
	if (thisHandle == NIL_POINTER)
	{
		/* Construct the error DITL    see IM I-427 */
		errorDITL.itemCount = 1;		/* total of two items */
	
		/* OK button */
		errorDITL.items[0].pointer = 0L;
		errorDITL.items[0].boundsRect.top = 96;
		errorDITL.items[0].boundsRect.left = 137;
		errorDITL.items[0].boundsRect.bottom = 116;
		errorDITL.items[0].boundsRect.right = 195;
		errorDITL.items[0].itemType = (char) btnCtrl | ctrlItem;
		errorDITL.items[0].itemData[0] = 2;
		errorDITL.items[0].itemData[1] = 'O';
		errorDITL.items[0].itemData[2] = 'K';
	
		/* Static Text */
		errorDITL.items[1].pointer = 0L;
		errorDITL.items[1].boundsRect.top = 13;
		errorDITL.items[1].boundsRect.left = 65;
		errorDITL.items[1].boundsRect.bottom = 82;
		errorDITL.items[1].boundsRect.right = 318;
		errorDITL.items[1].itemType = (char) statText;
		errorDITL.items[1].itemData[0] = 2;
		errorDITL.items[1].itemData[1] = '^';
		errorDITL.items[1].itemData[2] = '0';
	
		/* Make the DITL into a resource */
		thisHandle = NewHandle (sizeof (DialogList));
		BlockMove (&errorDITL, *thisHandle, sizeof (DialogList));
		AddResource (thisHandle, 'DITL', BASE_RES_ID, "\pError DITL");
		thisErr = ResError ();
	
	
		/* Construct the error ALRT   IM-I 426 */
		thisAlert.boundsRect.top = 40;
		thisAlert.boundsRect.left = 40;
		thisAlert.boundsRect.bottom = 172;
		thisAlert.boundsRect.right = 368;
		thisAlert.itemsID = BASE_RES_ID;
		thisAlert.stages = 0x7775;
	
		/* Make the ALRT into a resource */
		thisHandle = NewHandle (sizeof (AlertTemplate));
		BlockMove (&thisAlert, *thisHandle, sizeof (AlertTemplate));
		AddResource (thisHandle, 'ALRT', BASE_RES_ID, "\pError ALRT");
		thisErr = ResError ();
	}
	else
		ReleaseResource (thisHandle);
}


main()
{
	int				i;

	/* Maximize available memory */
	MaxApplZone ();

    /* Be sure to have plenty of master pointer blocks */
    for (i = 0; i < NUM_MASTER_BLOCKS; i++)
    	MoreMasters();
    
	Init ();

	while (!gDone)
	{
		HandleEvent (&gTheEvent);
	}
	
} /* main */