1 /*	SCCS Id: @(#)mrecover.c	3.4		1996/07/24	  */
2 /*      Copyright (c) David Hairston, 1993.                       */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 /* Macintosh Recovery Application */
6 
7 /* based on code in util/recover.c.  the significant differences are:
8  * - GUI vs. CLI.  the vast majority of code here supports the GUI.
9  * - Mac toolbox equivalents are used in place of ANSI functions.
10  * - void restore_savefile(void) is event driven.
11  * - integral type substitutions here and there.
12  */
13 
14 /*
15  * Think C 5.0.4 project specs:
16  * signature: 'nhRc'
17  * SIZE (-1) info: flags: 0x5880, size: 65536L/65536L (64k/64k)
18  * libraries: MacTraps [yes], MacTraps2 (HFileStuff) [yes], ANSI [no]
19  * compatibility: system 6 and system 7
20  * misc: sizeof(int): 2, "\p": unsigned char, enum size varies,
21  *   prototypes required, type checking enforced, no optimizers,
22  *   FAR CODE [no], FAR DATA [no], SEPARATE STRS [no], single segment,
23  *   short macsbug symbols
24  */
25 
26 /*
27  * To do (maybe, just maybe):
28  * - Merge with the code in util/recover.c.
29  * - Document launch (e.g. GUI equivalent of 'recover basename').
30  * - Drag and drop.
31  * - Internal memory tweaks (stack and heap usage).
32  * - Use status file to allow resuming aborted recoveries.
33  * - Bundle 'LEVL' files with recover (easier document launch).
34  * - Prohibit recovering games "in progress".
35  * - Share AppleEvents with NetHack to auto-recover crashed games.
36  */
37 
38 
39 #include "config.h"
40 
41 /**** Toolbox defines ****/
42 
43 /* MPW C headers (99.44% pure) */
44 #include <Errors.h>
45 #include <OSUtils.h>
46 #include <Resources.h>
47 #include <Files.h>
48 #ifdef applec
49 #include <SysEqu.h>
50 #endif
51 #include <Menus.h>
52 #include <Devices.h>
53 #include <Events.h>
54 #include <DiskInit.h>
55 #include <Notification.h>
56 #include <Packages.h>
57 #include <Script.h>
58 #include <StandardFile.h>
59 #include <ToolUtils.h>
60 #include <Processes.h>
61 #include <Fonts.h>
62 #include <TextUtils.h>
63 
64 #ifdef THINK	/* glue for System 7 Icon Family call (needed by Think C 5.0.4) */
65 pascal OSErr GetIconSuite(Handle *theIconSuite, short theResID, long selector)
66 	= {0x303C, 0x0501, 0xABC9};
67 #endif
68 
69 #if defined(MAC_MPW)
70 QDGlobals qd;
71 #endif
72 
73 #define ResetAlrtStage ResetAlertStage
74 #define SetDItem SetDialogItem
75 #define GetDItem GetDialogItem
76 #define GetItem GetMenuItemText
77 #define DisposHandle DisposeHandle
78 #define AddResMenu AppendResMenu
79 #define DisposPtr DisposePtr
80 #define ResrvMem ReserveMem
81 
82 /**** Application defines ****/
83 
84 /* Memory */
85 typedef struct memBytes	/* format of 'memB' resource, preloaded/locked */
86 {
87 	short	memReserved;
88 	short	memCleanup;	/* 4   - memory monitor activity limit */
89 	long	memPreempt;	/* 32k - start iff FreeMem() > */
90 	long	memWarning;	/* 12k - warn if MaxMem() < */
91 	long	memAbort;	/* 4k  - abort if MaxMem() < */
92 	long	memIOBuf;	/* 16k - read/write buffer size */
93 } memBytes, *memBytesPtr, **memBytesHandle;
94 
95 #define membID			128				/* 'memB' resource ID */
96 
97 
98 /* Cursor */
99 #define CURS_FRAME		4L				/* 1/15 second - spin cursor */
100 #define CURS_LATENT		60L				/* pause before spin cursor */
101 #define curs_Init		(-1)			/* token for set arrow */
102 #define curs_Total		8				/* maybe 'acur' would be better */
103 #define cursorOffset	128				/* GetCursor(cursorOffset + i) */
104 
105 
106 /* Menu */
107 enum
108 {
109 	mbar_Init = -1,
110 	mbarAppl,							/* normal mode */
111 	mbarRecover,						/* in recovery mode */
112 	mbarDA								/* DA in front mode */
113 };
114 enum
115 {
116 	menuApple,
117 	menuFile,
118 	menuEdit,
119 	menu_Total,
120 
121 	muidApple = 128,
122 	muidFile,
123 	muidEdit
124 };
125 enum
126 {
127 	/* Apple menu */
128 	mitmAbout = 1,
129 	mitmHelp,
130 	____128_1,
131 
132 	/* File menu */
133 	mitmOpen = 1,
134 	____129_1,
135 	mitmClose_DA,
136 	____129_2,
137 	mitmQuit
138 
139 	/* standard minimum required Edit menu */
140 };
141 
142 
143 /* Alerts */
144 enum
145 {
146 	alrtNote,							/* general messages */
147 	alrtHelp,							/* help message */
148 	alrt_Total,
149 
150 	alertAppleMenu = 127,				/* menuItem to alert ID offset */
151 	alidNote,
152 	alidHelp
153 };
154 
155 #define aboutBufSize	80				/* i.e. 2 lines of 320 pixels */
156 
157 
158 /* Notification */
159 #define nmBufSize		(32 + aboutBufSize + 32)
160 typedef struct notifRec
161 {
162 	NMRec				nmr;
163 	struct notifRec	*	nmNext;
164 	short				nmDispose;
165 	unsigned char		nmBuf[nmBufSize];
166 } notifRec, *notifPtr;
167 
168 #define nmPending		nmRefCon			/* &in.Notify */
169 #define iconNotifyID	128
170 #define ics_1_and_4		0x00000300
171 
172 /* Dialogs */
173 enum
174 {
175 	dlogProgress = 256
176 };
177 enum
178 {
179 	uitmThermo = 1
180 };
181 enum
182 {
183 	initItem,
184 	invalItem,
185 	drawItem
186 };
187 
188 
189 /* Miscellaneous */
190 typedef struct modeFlags
191 {
192 	short	Front;			/* fg/bg event handling */
193 	short	Notify;			/* level of pending NM notifications */
194 	short	Dialog;			/* a modeless dialog is open */
195 	short	Recover;		/* restoration progress index */
196 } modeFlags;
197 
198 /* convenient define to allow easier (for me) parsing of 'vers' resource */
199 typedef struct versXRec
200 {
201 	NumVersion		numVers;
202 	short			placeCode;
203 	unsigned char	versStr[];	/* (small string)(large string) */
204 } versXRec, *versXPtr, **versXHandle;
205 
206 
207 
208 /**** Global variables ****/
209 modeFlags		in = {1};				/* in Front */
210 EventRecord		wnEvt;
211 SysEnvRec		sysEnv;
212 unsigned char	aboutBuf[aboutBufSize];	/* vers 1 "Get Info" string */
213 memBytesPtr		pBytes;					/* memory management */
214 unsigned short	memActivity;			/* more memory management */
215 MenuHandle		mHnd[menu_Total];
216 CursPtr			cPtr[curs_Total];		/* busy cursors */
217 unsigned long	timeCursor;				/* next cursor frame time */
218 short			oldCursor = curs_Init;	/* see adjustGUI() below */
219 notifPtr		pNMQ;					/* notification queue pointer */
220 notifRec		nmt;					/* notification template */
221 DialogTHndl		thermoTHnd;
222 DialogRecord	dlgThermo;				/* progress thermometer */
223 #define DLGTHM	((DialogPtr) &dlgThermo)
224 #define WNDTHM	((WindowPtr) &dlgThermo)
225 #define GRFTHM	((GrafPtr) &dlgThermo)
226 
227 Point			sfGetWhere;				/* top left corner of get file dialog */
228 Ptr				pIOBuf;					/* read/write buffer pointer */
229 short			vRefNum;				/* SFGetFile working directory/volume refnum */
230 long			dirID;					/* directory i.d. */
231 NMUPP			nmCompletionUPP;		/* UPP for nmCompletion */
232 FileFilterUPP	basenameFileFilterUPP;	/* UPP for basenameFileFilter */
233 UserItemUPP		drawThermoUPP;			/* UPP for progress callback */
234 
235 #define MAX_RECOVER_COUNT	256
236 
237 #define APP_NAME_RES_ID		(-16396)	/* macfile.h */
238 #define PLAYER_NAME_RES_ID	1001		/* macfile.h */
239 
240 /* variables from util/recover.c */
241 #define SAVESIZE	FILENAMELEN
242 unsigned char	savename[SAVESIZE];		/* originally a C string */
243 unsigned char	lock[256];				/* pascal string */
244 
245 int			hpid;					/* NetHack (unix-style) process i.d. */
246 short			saveRefNum;				/* save file descriptor */
247 short			gameRefNum;				/* level 0 file descriptor */
248 short			levRefNum;				/* level n file descriptor */
249 
250 
251 /**** Prototypes ****/
252 static	void warmup(void);
253 static	Handle alignTemplate(ResType, short, short, short, Point *);
254 pascal	void nmCompletion(NMRec *);
255 static	void noteErrorMessage(unsigned char *, unsigned char *);
256 static	void note(short, short, unsigned char *);
257 static	void adjustGUI(void);
258 static	void adjustMemory(void);
259 static	void optionMemStats(void);
260 static	void RecoverMenuEvent(long);
261 static	void eventLoop(void);
262 static	void cooldown(void);
263 
264 pascal	void drawThermo(WindowPtr, short);
265 static	void itemizeThermo(short);
266 pascal	Boolean basenameFileFilter(ParmBlkPtr);
267 static	void beginRecover(void);
268 static	void continueRecover(void);
269 static	void endRecover(void);
270 static	short saveRezStrings(void);
271 
272 /* analogous prototypes from util/recover.c */
273 static	void set_levelfile_name(long);
274 static	short open_levelfile(long);
275 static	short create_savefile(unsigned char *);
276 static	void copy_bytes(short, short);
277 static	void restore_savefile(void);
278 
279 /* auxiliary prototypes */
280 static	long read_levelfile(short, Ptr, long);
281 static	long write_savefile(short, Ptr, long);
282 static	void close_file(short *);
283 static	void unlink_file(unsigned char *);
284 
285 
286 /**** Routines ****/
287 
main()288 main()
289 {
290 	/* heap adjust */
291 	MaxApplZone();
292 	MoreMasters();
293 	MoreMasters();
294 
295 	/* manager initialization */
296 	InitGraf(&qd.thePort);
297 	InitFonts();
298 	InitWindows();
299 	InitMenus();
300 	TEInit();
301 	InitDialogs(0L);
302 	InitCursor();
303 	nmCompletionUPP = NewNMProc(nmCompletion);
304 	basenameFileFilterUPP = NewFileFilterProc(basenameFileFilter);
305 	drawThermoUPP = NewUserItemProc(drawThermo);
306 
307 	/* get system environment, notification requires 6.0 or better */
308 	(void) SysEnvirons(curSysEnvVers, &sysEnv);
309 	if (sysEnv.systemVersion < 0x0600)
310 	{
311 		ParamText("\pAbort: System 6.0 is required", "\p", "\p", "\p");
312 		(void) Alert(alidNote, (ModalFilterUPP) 0L);
313 		ExitToShell();
314 	}
315 
316 	warmup();
317 	eventLoop();
318 
319 	/* normally these routines are never reached from here */
320 	cooldown();
321 	ExitToShell();
322 	return 0;
323 }
324 
325 static void
warmup()326 warmup()
327 {
328 	short		i;
329 
330 	/* pre-System 7 MultiFinder hack for smooth launch */
331 	for (i = 0; i < 10; i++)
332 	{
333 		if (WaitNextEvent(osMask, &wnEvt, 2L, (RgnHandle) 0L))
334 			if (((wnEvt.message & osEvtMessageMask) >> 24) == suspendResumeMessage)
335 				in.Front = (wnEvt.message & resumeFlag);
336 	}
337 
338 #if 0 // ???
339 	/* clear out the Finder info */
340 	{
341 		short	message, count;
342 
343 		CountAppFiles(&message, &count);
344 		while(count)
345 			ClrAppFiles(count--);
346 	}
347 #endif
348 
349 	/* fill out the notification template */
350 	nmt.nmr.qType = nmType;
351 	nmt.nmr.nmMark = 1;
352 	nmt.nmr.nmSound = (Handle) -1L;		/* system beep */
353 	nmt.nmr.nmStr = nmt.nmBuf;
354 	nmt.nmr.nmResp = nmCompletionUPP;
355 	nmt.nmr.nmPending = (long) &in.Notify;
356 
357 
358 #if 1
359 	{
360 		/* get the app name */
361 		ProcessInfoRec info;
362 		ProcessSerialNumber psn;
363 
364 		info.processInfoLength = sizeof(info);
365 		info.processName = nmt.nmBuf;
366 		info.processAppSpec = NULL;
367 		GetCurrentProcess(&psn);
368 		GetProcessInformation(&psn, &info);
369 	}
370 #else
371 	/* prepend app name (31 chars or less) to notification buffer */
372 	{
373 		short	apRefNum;
374 		Handle	apParams;
375 
376 		GetAppParms(* (Str255 *) &nmt.nmBuf, &apRefNum, &apParams);
377 	}
378 #endif
379 
380 	/* add formatting (two line returns) */
381 	nmt.nmBuf[++(nmt.nmBuf[0])] = '\r';
382 	nmt.nmBuf[++(nmt.nmBuf[0])] = '\r';
383 
384 	/**** note() is usable now but not aesthetically complete ****/
385 
386 	/* get notification icon */
387 	if (sysEnv.systemVersion < 0x0700)
388 	{
389 		if (! (nmt.nmr.nmIcon = GetResource('SICN', iconNotifyID)))
390 			note(nilHandleErr, 0, "\pNil SICN Handle");
391 	}
392 	else
393 	{
394 		if (GetIconSuite(&nmt.nmr.nmIcon, iconNotifyID, ics_1_and_4))
395 			note(nilHandleErr, 0, "\pBad Icon Family");
396 	}
397 
398 	/* load and align various dialog/alert templates */
399 	(void) alignTemplate('ALRT', alidNote, 0, 4, (Point *) 0L);
400 	(void) alignTemplate('ALRT', alidHelp, 0, 4, (Point *) 0L);
401 
402 	thermoTHnd = (DialogTHndl) alignTemplate('DLOG', dlogProgress, 20, 8, (Point *) 0L);
403 
404 	(void) alignTemplate('DLOG', getDlgID, 0, 6, (Point *) &sfGetWhere);
405 
406 	/* get the "busy cursors" (preloaded/locked) */
407 	for (i = 0; i < curs_Total; i++)
408 	{
409 		CursHandle		cHnd;
410 
411 		if (! (cHnd = GetCursor(i + cursorOffset)))
412 			note(nilHandleErr, 0, "\pNil CURS Handle");
413 
414 		cPtr[i] = *cHnd;
415 	}
416 
417 	/* get the 'vers' 1 long (Get Info) string - About Recover... */
418 	{
419 		versXHandle		vHnd;
420 
421 		if (! (vHnd = (versXHandle) GetResource('vers', 1)))
422 			note(nilHandleErr, 0, "\pNil vers Handle");
423 
424 		i = (**vHnd).versStr[0] + 1;		/* offset to Get Info pascal string */
425 
426 		if ((aboutBuf[0] = (**vHnd).versStr[i]) > (aboutBufSize - 1))
427 			aboutBuf[0] = aboutBufSize - 1;
428 
429 		i++;
430 
431 		MoveHHi((Handle) vHnd);			/* DEE - Fense ... */
432 		HLock((Handle) vHnd);
433 		BlockMove(&((**vHnd).versStr[i]), &(aboutBuf[1]), aboutBuf[0]);
434 		ReleaseResource((Handle) vHnd);
435 	}
436 
437 	/* form the menubar */
438 	for (i = 0; i < menu_Total; i++)
439 	{
440 		if (! (mHnd[i] = GetMenu(i + muidApple)))
441 			note(nilHandleErr, 0, "\pNil MENU Handle");
442 
443 		/* expand the apple menu */
444 		if (i == menuApple)
445 			AddResMenu(mHnd[menuApple], 'DRVR');
446 
447 		InsertMenu(mHnd[i], 0);
448 	}
449 
450 	/* pre-emptive memory check */
451 	{
452 		memBytesHandle	hBytes;
453 		Size			grow;
454 
455 		if (! (hBytes = (memBytesHandle) GetResource('memB', membID)))
456 			note(nilHandleErr, 0, "\pNil Memory Handle");
457 
458 		pBytes = *hBytes;
459 
460 		if (MaxMem(&grow) < pBytes->memPreempt)
461 			note(memFullErr, 0, "\pMore Memory Required\rTry adding 16k");
462 
463 		memActivity = pBytes->memCleanup;		/* force initial cleanup */
464 	}
465 
466 	/* get the I/O buffer */
467 	if (! (pIOBuf = NewPtr(pBytes->memIOBuf)))
468 		note(memFullErr, 0, "\pNil I/O Pointer");
469 }
470 
471 /* align a window-related template to the main screen */
472 static Handle
alignTemplate(ResType rezType,short rezID,short vOff,short vDenom,Point * pPt)473 alignTemplate(ResType rezType, short rezID, short vOff, short vDenom, Point *pPt)
474 {
475 	Handle	rtnHnd;
476 	Rect	*pRct;
477 
478 	vOff += GetMBarHeight();
479 
480 	if (! (rtnHnd = GetResource(rezType, rezID)))
481 		note(nilHandleErr, 0, "\pNil Template Handle");
482 
483 	pRct = (Rect *) *rtnHnd;
484 
485 	/* don't move memory while aligning rect */
486 	pRct->right -= pRct->left;		/* width */
487 	pRct->bottom -= pRct->top;		/* height */
488 	pRct->left = (qd.screenBits.bounds.right - pRct->right) / 2;
489 	pRct->top = (qd.screenBits.bounds.bottom - pRct->bottom - vOff) / vDenom;
490 	pRct->top += vOff;
491 	pRct->right += pRct->left;
492 	pRct->bottom += pRct->top;
493 
494 	if (pPt)
495 		*pPt = * (Point *) pRct;	/* top left corner */
496 
497 	return rtnHnd;
498 }
499 
500 /* notification completion routine */
501 pascal void
nmCompletion(NMRec * pNMR)502 nmCompletion(NMRec * pNMR)
503 {
504 	(void) NMRemove(pNMR);
505 
506 	(* (short *) (pNMR->nmPending))--;	/* decrement pending note level */
507 	((notifPtr) pNMR)->nmDispose = 1;	/* allow DisposPtr() */
508 }
509 
510 /*
511  * handle errors inside of note().  the error message is appended to the
512  * given message but on a separate line and must fit within nmBufSize.
513  */
514 static void
noteErrorMessage(unsigned char * msg,unsigned char * errMsg)515 noteErrorMessage(unsigned char *msg, unsigned char *errMsg)
516 {
517 	short	i = nmt.nmBuf[0] + 1;		/* insertion point */
518 
519 	BlockMove(&msg[1], &nmt.nmBuf[i], msg[0]);
520 	nmt.nmBuf[i + msg[0]] = '\r';
521 	nmt.nmBuf[0] += (msg[0] + 1);
522 
523 	note(memFullErr, 0, errMsg);
524 }
525 
526 /*
527  * display messages using Notification Manager or an alert.
528  * no run-length checking is done.  the messages are created to fit
529  * in the allocated space (nmBufSize and aboutBufSize).
530  */
531 static void
note(short errorSignal,short alertID,unsigned char * msg)532 note(short errorSignal, short alertID, unsigned char *msg)
533 {
534 	if (! errorSignal)
535 	{
536 		Size	grow;
537 
538 		if (MaxMem(&grow) < pBytes->memAbort)
539 			noteErrorMessage(msg, "\pOut of Memory");
540 	}
541 
542 	if (errorSignal || !in.Front)
543 	{
544 		notifPtr	pNMR;
545 		short		i = nmt.nmBuf[0] + 1;	/* insertion point */
546 
547 		if (errorSignal)		/* use notification template */
548 		{
549 			pNMR = &nmt;
550 
551 			/* we're going to abort so add in this prefix */
552 			BlockMove("Abort: ", &nmt.nmBuf[i], 7);
553 			i += 7;
554 			nmt.nmBuf[0] += 7;
555 		}
556 		else					/* allocate a notification record */
557 		{
558 			if (! (pNMR = (notifPtr) NewPtr(sizeof(notifRec))))
559 				noteErrorMessage(msg, "\pNil New Pointer");
560 
561 			/* initialize it */
562 			*pNMR = nmt;
563 			pNMR->nmr.nmStr = (StringPtr) &(pNMR->nmBuf);
564 
565 			/* update the notification queue */
566 			if (!pNMQ)
567 				pNMQ = pNMR;
568 			else
569 			{
570 				notifPtr	pNMX;
571 
572 				/* find the end of the queue */
573 				for (pNMX = pNMQ; pNMX->nmNext; pNMX = pNMX->nmNext)
574 					;
575 
576 				pNMX->nmNext = pNMR;
577 			}
578 		}
579 
580 		/* concatenate the message */
581 		BlockMove(&msg[1], &((pNMR->nmBuf)[i]), msg[0]);
582 		(pNMR->nmBuf)[0] += msg[0];
583 
584 		in.Notify++;			/* increase note pending level */
585 
586 		NMInstall((NMRec *) pNMR);
587 
588 		if (errorSignal)
589 			cooldown();
590 
591 		return;
592 	}
593 
594 	/* in front and no error so use an alert */
595 	ParamText(msg, "\p", "\p", "\p");
596 	(void) Alert(alertID, (ModalFilterUPP) 0L);
597 	ResetAlrtStage();
598 
599 	memActivity++;
600 }
601 
602 static void
adjustGUI()603 adjustGUI()
604 {
605 	static short	oldMenubar = mbar_Init;	/* force initial update */
606 	short			newMenubar;
607 	WindowPeek		frontWindow;
608 
609 	/* oldCursor is external so it can be reset in endRecover() */
610 	static short	newCursor = curs_Init;
611 	unsigned long	timeNow;
612 	short			useArrow;
613 
614 	/* adjust menubar 1st */
615 	newMenubar = in.Recover ? mbarRecover : mbarAppl;
616 
617 	/* desk accessories take precedence */
618 	if (frontWindow = (WindowPeek) FrontWindow())
619 		if (frontWindow->windowKind < 0)
620 			newMenubar = mbarDA;
621 
622 	if (newMenubar != oldMenubar)
623 	{
624 		/* adjust menus */
625 		switch (oldMenubar = newMenubar)
626 		{
627 		case mbarAppl:
628 			EnableItem(mHnd[menuFile], mitmOpen);
629 			SetItemMark(mHnd[menuFile], mitmOpen, noMark);
630 			DisableItem(mHnd[menuFile], mitmClose_DA);
631 			DisableItem(mHnd[menuEdit], 0);
632 			break;
633 
634 		case mbarRecover:
635 			DisableItem(mHnd[menuFile], mitmOpen);
636 			SetItemMark(mHnd[menuFile], mitmOpen, checkMark);
637 			DisableItem(mHnd[menuFile], mitmClose_DA);
638 			DisableItem(mHnd[menuEdit], 0);
639 			break;
640 
641 		case mbarDA:
642 			DisableItem(mHnd[menuFile], mitmOpen);
643 			EnableItem(mHnd[menuFile], mitmClose_DA);
644 			EnableItem(mHnd[menuEdit], 0);
645 			break;
646 		}
647 
648 		DrawMenuBar();
649 	}
650 
651 	/* now adjust the cursor */
652 	if (useArrow = (!in.Recover || (newMenubar == mbarDA)))
653 		newCursor = curs_Init;
654 	else if ((timeNow = TickCount()) >= timeCursor)		/* spin cursor */
655 	{
656 		timeCursor = timeNow + CURS_FRAME;
657 		if (++newCursor >= curs_Total)
658 			newCursor = 0;
659 	}
660 
661 	if (newCursor != oldCursor)
662 	{
663 		oldCursor = newCursor;
664 
665 		SetCursor(useArrow ? &qd.arrow : cPtr[newCursor]);
666 	}
667 }
668 
669 static void
adjustMemory()670 adjustMemory()
671 {
672 	Size		grow;
673 
674 	memActivity = 0;
675 
676 	if (MaxMem(&grow) < pBytes->memWarning)
677 		note(noErr, alidNote, "\pWarning: Memory is running low");
678 
679 	(void) ResrvMem((Size) FreeMem());		/* move all handles high */
680 }
681 
682 /* show memory stats: FreeMem, MaxBlock, PurgeSpace, and StackSpace */
683 static void
optionMemStats()684 optionMemStats()
685 {
686 	unsigned char	*pFormat = "\pFree:#k  Max:#k  Purge:#k  Stack:#k";
687 	char			*pSub = "#";		/* not a pascal string */
688 	unsigned char	nBuf[16];
689 	long			nStat, contig;
690 	Handle			strHnd;
691 	long			nOffset;
692 	short			i;
693 
694 	if (wnEvt.modifiers & shiftKey)
695 		adjustMemory();
696 
697 	if (! (strHnd = NewHandle((Size) 128)))
698 	{
699 		note(noErr, alidNote, "\pOops: Memory stats unavailable!");
700 		return;
701 	}
702 
703 	SetString((StringHandle) strHnd, pFormat);
704 	nOffset = 1L;
705 
706 	for (i = 1; i <= 4; i++)
707 	{
708 		/* get the replacement number stat */
709 		switch (i)
710 		{
711 		case 1: nStat = FreeMem();				break;
712 		case 2: nStat = MaxBlock();				break;
713 		case 3: PurgeSpace(&nStat, &contig);	break;
714 		case 4: nStat = StackSpace();			break;
715 		}
716 
717 		NumToString((nStat >> 10), * (Str255 *) &nBuf);
718 
719 		**strHnd += nBuf[0] - 1;
720 		nOffset = Munger(strHnd, nOffset, (Ptr) pSub, 1L, (Ptr) &nBuf[1], nBuf[0]);
721 	}
722 
723 	MoveHHi(strHnd);
724 	HLock(strHnd);
725 	note(noErr, alidNote, (unsigned char *) *strHnd);
726 	DisposHandle(strHnd);
727 }
728 
729 static void
RecoverMenuEvent(long menuEntry)730 RecoverMenuEvent(long menuEntry)
731 {
732 	short menuID = HiWord(menuEntry);
733 	short menuItem = LoWord(menuEntry);
734 
735 	switch (menuID)
736 	{
737 	case muidApple:
738 		switch (menuItem)
739 		{
740 		case mitmAbout:
741 			if (wnEvt.modifiers & optionKey)
742 				optionMemStats();
743 			/* fall thru */
744 		case mitmHelp:
745 			note(noErr, (alertAppleMenu + menuItem), aboutBuf);
746 			break;
747 
748 		default:	/* DA's or apple menu items */
749 			{
750 				unsigned char	daName[32];
751 
752 				GetItem(mHnd[menuApple], menuItem, * (Str255 *) &daName);
753 				(void) OpenDeskAcc(daName);
754 
755 				memActivity++;
756 			}
757 			break;
758 		}
759 		break;
760 
761 	case muidFile:
762 		switch (menuItem)
763 		{
764 		case mitmOpen:
765 			beginRecover();
766 			break;
767 
768 		case mitmClose_DA:
769 			{
770 				WindowPeek	frontWindow;
771 				short		refNum;
772 
773 				if (frontWindow = (WindowPeek) FrontWindow())
774 					if ((refNum = frontWindow->windowKind) < 0)
775 						CloseDeskAcc(refNum);
776 
777 				memActivity++;
778 			}
779 			break;
780 
781 		case mitmQuit:
782 			cooldown();
783 			break;
784 		}
785 		break;
786 
787 	case muidEdit:
788 		(void) SystemEdit(menuItem - 1);
789 		break;
790 	}
791 
792 	HiliteMenu(0);
793 }
794 
795 static void
eventLoop()796 eventLoop()
797 {
798 	short	wneMask = (in.Front ? everyEvent : (osMask + updateMask));
799 	long	wneSleep = (in.Front ? 0L : 3L);
800 
801 	while (1)
802 	{
803 		if (in.Front)
804 			adjustGUI();
805 
806 		if (memActivity >= pBytes->memCleanup)
807 			adjustMemory();
808 
809 		(void) WaitNextEvent(wneMask, &wnEvt, wneSleep, (RgnHandle) 0L);
810 
811 		if (in.Dialog)
812 			(void) IsDialogEvent(&wnEvt);
813 
814 		switch (wnEvt.what)
815 		{
816 		case osEvt:
817 			if (((wnEvt.message & osEvtMessageMask) >> 24) == suspendResumeMessage)
818 			{
819 				in.Front = (wnEvt.message & resumeFlag);
820 				wneMask = (in.Front ? everyEvent : (osMask + updateMask));
821 				wneSleep = (in.Front ? 0L : 3L);
822 			}
823 			break;
824 
825 		case nullEvent:
826 			/* adjust the FIFO notification queue */
827 			if (pNMQ && pNMQ->nmDispose)
828 			{
829 				notifPtr pNMX = pNMQ->nmNext;
830 
831 				DisposPtr((Ptr) pNMQ);
832 				pNMQ = pNMX;
833 
834 				memActivity++;
835 			}
836 
837 			if (in.Recover)
838 				continueRecover();
839 			break;
840 
841 		case mouseDown:
842 			{
843 				WindowPtr	whichWindow;
844 
845 				switch(FindWindow( wnEvt . where , &whichWindow))
846 				{
847 				case inMenuBar:
848 					RecoverMenuEvent(MenuSelect( wnEvt . where ));
849 					break;
850 
851 				case inSysWindow:
852 					SystemClick(&wnEvt, whichWindow);
853 					break;
854 
855 				case inDrag:
856 					{
857 						Rect	boundsRect = qd.screenBits.bounds;
858 						Point	offsetPt;
859 
860 						InsetRect(&boundsRect, 4, 4);
861 						boundsRect.top += GetMBarHeight();
862 
863 						DragWindow(whichWindow, * ((Point *) &wnEvt.where), &boundsRect);
864 
865 						boundsRect = whichWindow->portRect;
866 						offsetPt = * (Point *) &(whichWindow->portBits.bounds);
867 						OffsetRect(&boundsRect, -offsetPt.h, -offsetPt.v);
868 
869 						* (Rect *) *thermoTHnd = boundsRect;
870 					}
871 					break;
872 				}
873 			}
874 			break;
875 
876 		case keyDown:
877 			{
878 				char	key = (wnEvt.message & charCodeMask);
879 
880 				if (wnEvt.modifiers & cmdKey)
881 				{
882 					if (key == '.')
883 					{
884 						if (in.Recover)
885 						{
886 							endRecover();
887 							note(noErr, alidNote, "\pSorry: Recovery aborted");
888 						}
889 					}
890 					else
891 						RecoverMenuEvent(MenuKey(key));
892 				}
893 			}
894 			break;
895 
896 		/* without windows these events belong to our thermometer */
897 		case updateEvt:
898 		case activateEvt:
899 		{
900 			DialogPtr	dPtr;
901 			short		itemHit;
902 
903 			(void) DialogSelect(&wnEvt, &dPtr, &itemHit);
904 			break;
905 		}
906 
907 		case diskEvt:
908 			if (HiWord(wnEvt.message))
909 			{
910 				Point	pt = {60, 60};
911 
912 				(void) DIBadMount(pt, wnEvt.message);
913 				DIUnload();
914 
915 				memActivity++;
916 			}
917 			break;
918 		}			/* switch (wnEvt.what) */
919 	}				/* while (1) */
920 }
921 
922 static void
cooldown()923 cooldown()
924 {
925 	if (in.Recover)
926 		endRecover();
927 
928 	/* wait for pending notifications to complete */
929 	while (in.Notify)
930 		(void) WaitNextEvent(0, &wnEvt, 3L, (RgnHandle) 0L);
931 
932 	ExitToShell();
933 }
934 
935 /* draw the progress thermometer and frame.  1 level <=> 1 horiz. pixel */
936 pascal void
drawThermo(WindowPtr wPtr,short inum)937 drawThermo(WindowPtr wPtr, short inum)
938 {
939 	itemizeThermo(drawItem);
940 }
941 
942 /* manage progress thermometer dialog */
943 static void
itemizeThermo(short itemMode)944 itemizeThermo(short itemMode)
945 {
946 	short	iTyp, iTmp;
947 	Handle	iHnd;
948 	Rect	iRct;
949 
950 	GetDItem(DLGTHM, uitmThermo, &iTyp, &iHnd, &iRct);
951 
952 	switch(itemMode)
953 	{
954 	case initItem:
955 		SetDItem(DLGTHM, uitmThermo, iTyp, (Handle) drawThermoUPP, &iRct);
956 		break;
957 
958 	case invalItem:
959 		{
960 			GrafPtr	oldPort;
961 
962 			GetPort(&oldPort);
963 			SetPort(GRFTHM);
964 
965 			InsetRect(&iRct, 1, 1);
966 			InvalRect(&iRct);
967 
968 			SetPort(oldPort);
969 		}
970 		break;
971 
972 	case drawItem:
973 			FrameRect(&iRct);
974 			InsetRect(&iRct, 1, 1);
975 
976 			iTmp = iRct.right;
977 			iRct.right = iRct.left + in.Recover;
978 			PaintRect(&iRct);
979 
980 			iRct.left = iRct.right;
981 			iRct.right = iTmp;
982 			EraseRect(&iRct);
983 		break;
984 	}
985 }
986 
987 /* show only <pid-plname>.0 files in get file dialog */
988 pascal Boolean
basenameFileFilter(ParmBlkPtr pPB)989 basenameFileFilter(ParmBlkPtr pPB)
990 {
991 	unsigned char	*pC;
992 
993 	if (! (pC = (unsigned char *) pPB->fileParam.ioNamePtr))
994 		return true;
995 
996 	if ((*pC < 4) || (*pC > 28))						/* save/ 1name .0 */
997 		return true;
998 
999 	if ((pC[*pC - 1] == '.') && (pC[*pC] == '0'))		/* bingo! */
1000 		return false;
1001 
1002 	return true;
1003 }
1004 
1005 static void
beginRecover()1006 beginRecover()
1007 {
1008 	SFTypeList		levlType = {'LEVL'};
1009 	SFReply			sfGetReply;
1010 
1011 	SFGetFile(sfGetWhere, "\p", basenameFileFilterUPP, 1, levlType,
1012 				(DlgHookUPP) 0L, &sfGetReply);
1013 
1014 	memActivity++;
1015 
1016 	if (! sfGetReply.good)
1017 		return;
1018 
1019 	/* get volume (working directory) refnum, basename, and directory i.d. */
1020 	vRefNum = sfGetReply.vRefNum;
1021 	BlockMove(sfGetReply.fName, lock, sfGetReply.fName[0] + 1);
1022 	{
1023 		static CInfoPBRec	catInfo;
1024 
1025 		catInfo.hFileInfo.ioNamePtr = (StringPtr) sfGetReply.fName;
1026 		catInfo.hFileInfo.ioVRefNum = sfGetReply.vRefNum;
1027 		catInfo.hFileInfo.ioDirID = 0L;
1028 
1029 		if (PBGetCatInfoSync(&catInfo))
1030 		{
1031 			note(noErr, alidNote, "\pSorry: Bad File Info");
1032 			return;
1033 		}
1034 
1035 		dirID = catInfo.hFileInfo.ioFlParID;
1036 	}
1037 
1038 	/* open the progress thermometer dialog */
1039 	(void) GetNewDialog(dlogProgress, (Ptr) &dlgThermo, (WindowPtr) -1L);
1040 	if (ResError() || MemError())
1041 		note(noErr, alidNote, "\pOops: Progress thermometer unavailable");
1042 	else
1043 	{
1044 		in.Dialog = 1;
1045 		memActivity++;
1046 
1047 		itemizeThermo(initItem);
1048 
1049 		ShowWindow(WNDTHM);
1050 	}
1051 
1052 	timeCursor = TickCount() + CURS_LATENT;
1053 	saveRefNum = gameRefNum = levRefNum = -1;
1054 	in.Recover = 1;
1055 }
1056 
1057 static void
continueRecover()1058 continueRecover()
1059 {
1060 	restore_savefile();
1061 
1062 	/* update the thermometer */
1063 	if (in.Dialog && ! (in.Recover % 4))
1064 		itemizeThermo(invalItem);
1065 
1066 	if (in.Recover <= MAX_RECOVER_COUNT)
1067 		return;
1068 
1069 	endRecover();
1070 
1071 	if (saveRezStrings())
1072 		return;
1073 
1074 	note(noErr, alidNote, "\pOK: Recovery succeeded");
1075 }
1076 
1077 /* no messages from here (since we might be quitting) */
1078 static void
endRecover()1079 endRecover()
1080 {
1081 	in.Recover = 0;
1082 
1083 	oldCursor = curs_Init;
1084 	SetCursor(&qd.arrow);
1085 
1086 	/* clean up abandoned files */
1087 	if (gameRefNum >= 0)
1088 		(void) FSClose(gameRefNum);
1089 
1090 	if (levRefNum >= 0)
1091 		(void) FSClose(levRefNum);
1092 
1093 	if (saveRefNum >= 0)
1094 	{
1095 		(void) FSClose(saveRefNum);
1096 		(void) FlushVol((StringPtr) 0L, vRefNum);
1097 		/* its corrupted so trash it ... */
1098 		(void) HDelete(vRefNum, dirID, savename);
1099 	}
1100 
1101 	saveRefNum = gameRefNum = levRefNum = -1;
1102 
1103 	/* close the progress thermometer dialog */
1104 	in.Dialog = 0;
1105 	CloseDialog(DLGTHM);
1106 	DisposHandle(dlgThermo.items);
1107 	memActivity++;
1108 }
1109 
1110 /* add friendly, non-essential resource strings to save file */
1111 static short
saveRezStrings()1112 saveRezStrings()
1113 {
1114 	short			sRefNum;
1115 	StringHandle	strHnd;
1116 	short			i, rezID;
1117 	unsigned char	*plName;
1118 
1119 	HCreateResFile(vRefNum, dirID, savename);
1120 
1121 	sRefNum = HOpenResFile(vRefNum, dirID, savename, fsRdWrPerm);
1122 	if (sRefNum <= 0)
1123 	{
1124 		note(noErr, alidNote, "\pOK: Minor resource map error");
1125 		return 1;
1126 	}
1127 
1128 	/* savename and hpid get mutilated here... */
1129 	plName = savename + 5;				/* save/ */
1130 	*savename -= 5;
1131 	do
1132 	{
1133 		plName++;
1134 		(*savename)--;
1135 		hpid /= 10;
1136 	}
1137 	while (hpid);
1138 	*plName = *savename;
1139 
1140 	for (i = 1; i <= 2; i++)
1141 	{
1142 		switch (i)
1143 		{
1144 		case 1:
1145 			rezID = PLAYER_NAME_RES_ID;
1146 			strHnd = NewString(* (Str255 *) plName);
1147 			break;
1148 
1149 		case 2:
1150 			rezID = APP_NAME_RES_ID;
1151 			strHnd = NewString(* (Str255 *) "\pNetHack");
1152 			break;
1153 		}
1154 
1155 		if (! strHnd)
1156 		{
1157 			note(noErr, alidNote, "\pOK: Minor \'STR \' resource error");
1158 			CloseResFile(sRefNum);
1159 			return 1;
1160 		}
1161 
1162 		/* should check for errors... */
1163 		AddResource((Handle) strHnd, 'STR ', rezID, * (Str255 *) "\p");
1164 	}
1165 
1166 	memActivity++;
1167 
1168 	/* should check for errors... */
1169 	CloseResFile(sRefNum);
1170 	return 0;
1171 }
1172 
1173 static void
set_levelfile_name(long lev)1174 set_levelfile_name(long lev)
1175 {
1176 	unsigned char	*tf;
1177 
1178 	/* find the dot.  this is guaranteed to happen. */
1179 	for (tf = (lock + *lock); *tf != '.'; tf--, lock[0]--)
1180 		;
1181 
1182 	/* append the level number string (pascal) */
1183 	if (tf > lock)
1184 	{
1185 		NumToString(lev, * (Str255 *) tf);
1186 		lock[0] += *tf;
1187 		*tf = '.';
1188 	}
1189 	else	/* huh??? */
1190 	{
1191 		endRecover();
1192 		note(noErr, alidNote, "\pSorry: File Name Error");
1193 	}
1194 }
1195 
1196 static short
open_levelfile(long lev)1197 open_levelfile(long lev)
1198 {
1199 	OSErr	openErr;
1200 	short	fRefNum;
1201 
1202 	set_levelfile_name(lev);
1203 	if (! in.Recover)
1204 		return (-1);
1205 
1206 	if ((openErr = HOpen(vRefNum, dirID, lock, fsRdWrPerm, &fRefNum))
1207 			&& (openErr != fnfErr))
1208 	{
1209 		endRecover();
1210 		note(noErr, alidNote, "\pSorry: File Open Error");
1211 		return (-1);
1212 	}
1213 
1214 	return (openErr ? -1 : fRefNum);
1215 }
1216 
1217 static short
create_savefile(unsigned char * savename)1218 create_savefile(unsigned char *savename)
1219 {
1220 	short	fRefNum;
1221 
1222 	/* translate savename to a pascal string (in place) */
1223 	{
1224 		unsigned char	*pC;
1225 		short			nameLen;
1226 
1227 		for (pC = savename; *pC; pC++);
1228 
1229 		nameLen = pC - savename;
1230 
1231 		for ( ; pC > savename; pC--)
1232 			*pC = *(pC - 1);
1233 
1234 		*savename = nameLen;
1235 	}
1236 
1237 	if (HCreate(vRefNum, dirID, savename, MAC_CREATOR, SAVE_TYPE)
1238 		|| HOpen(vRefNum, dirID, savename, fsRdWrPerm, &fRefNum))
1239 	{
1240 		endRecover();
1241 		note(noErr, alidNote, "\pSorry: File Create Error");
1242 		return (-1);
1243 	}
1244 
1245 	return fRefNum;
1246 }
1247 
1248 static void
copy_bytes(short inRefNum,short outRefNum)1249 copy_bytes(short inRefNum, short outRefNum)
1250 {
1251 	char	*buf = (char *) pIOBuf;
1252 	long	bufSiz = pBytes->memIOBuf;
1253 
1254 	long	nfrom, nto;
1255 
1256 	do
1257 	{
1258 		nfrom = read_levelfile(inRefNum, buf, bufSiz);
1259 		if (! in.Recover)
1260 			return;
1261 
1262 		nto = write_savefile(outRefNum, buf, nfrom);
1263 		if (! in.Recover)
1264 			return;
1265 
1266 		if (nto != nfrom)
1267 		{
1268 			endRecover();
1269 			note(noErr, alidNote, "\pSorry: File Copy Error");
1270 			return;
1271 		}
1272 	}
1273 	while (nfrom == bufSiz);
1274 }
1275 
1276 static void
restore_savefile()1277 restore_savefile()
1278 {
1279 	static int	savelev;
1280 	long		saveTemp, lev;
1281 	xchar		levc;
1282 	struct version_info version_data;
1283 
1284 	/* level 0 file contains:
1285 	 *	pid of creating process (ignored here)
1286 	 *	level number for current level of save file
1287 	 *	name of save file nethack would have created
1288 	 *	and game state
1289 	 */
1290 
1291 	lev = in.Recover - 1;
1292 	if (lev == 0L)
1293 	{
1294 		gameRefNum = open_levelfile(0L);
1295 
1296 		if (in.Recover)
1297 			(void) read_levelfile(gameRefNum, (Ptr) &hpid, sizeof(hpid));
1298 
1299 		if (in.Recover)
1300 			saveTemp = read_levelfile(gameRefNum, (Ptr) &savelev, sizeof(savelev));
1301 
1302 		if (in.Recover && (saveTemp != sizeof(savelev)))
1303 		{
1304 			endRecover();
1305 			note(noErr, alidNote, "\pSorry: \"checkpoint\" was not enabled");
1306 			return;
1307 		}
1308 
1309 		if (in.Recover)
1310 			(void) read_levelfile(gameRefNum, (Ptr) savename, sizeof(savename));
1311 		if (in.Recover)
1312 			(void) read_levelfile(gameRefNum,
1313 				    (Ptr) &version_data, sizeof version_data);
1314 
1315 		/* save file should contain:
1316 		 *	current level (including pets)
1317 		 *	(non-level-based) game state
1318 		 *	other levels
1319 		 */
1320 		if (in.Recover)
1321 			saveRefNum = create_savefile(savename);
1322 
1323 		if (in.Recover)
1324 			levRefNum = open_levelfile(savelev);
1325 
1326 		if (in.Recover)
1327 			(void) write_savefile(saveRefNum,
1328 				    (Ptr) &version_data, sizeof version_data);
1329 		if (in.Recover)
1330 			copy_bytes(levRefNum, saveRefNum);
1331 
1332 		if (in.Recover)
1333 			close_file(&levRefNum);
1334 
1335 		if (in.Recover)
1336 			unlink_file(lock);
1337 
1338 		if (in.Recover)
1339 			copy_bytes(gameRefNum, saveRefNum);
1340 
1341 		if (in.Recover)
1342 			close_file(&gameRefNum);
1343 
1344 		if (in.Recover)
1345 			set_levelfile_name(0L);
1346 
1347 		if (in.Recover)
1348 			unlink_file(lock);
1349 	}
1350 	else if (lev != savelev)
1351 	{
1352 		levRefNum = open_levelfile(lev);
1353 		if (levRefNum >= 0)
1354 		{
1355 			/* any or all of these may not exist */
1356 			levc = (xchar) lev;
1357 
1358 			(void) write_savefile(saveRefNum, (Ptr) &levc, sizeof(levc));
1359 
1360 			if (in.Recover)
1361 				copy_bytes(levRefNum, saveRefNum);
1362 
1363 			if (in.Recover)
1364 				close_file(&levRefNum);
1365 
1366 			if (in.Recover)
1367 				unlink_file(lock);
1368 		}
1369 	}
1370 
1371 	if (in.Recover == MAX_RECOVER_COUNT)
1372 		close_file(&saveRefNum);
1373 
1374 	if (in.Recover)
1375 		in.Recover++;
1376 }
1377 
1378 static long
read_levelfile(short rdRefNum,Ptr bufPtr,long count)1379 read_levelfile(short rdRefNum, Ptr bufPtr, long count)
1380 {
1381 	OSErr	rdErr;
1382 	long	rdCount = count;
1383 
1384 	if ((rdErr = FSRead(rdRefNum, &rdCount, bufPtr)) && (rdErr != eofErr))
1385 	{
1386 		endRecover();
1387 		note(noErr, alidNote, "\pSorry: File Read Error");
1388 		return (-1L);
1389 	}
1390 
1391 	return rdCount;
1392 }
1393 
1394 static long
write_savefile(short wrRefNum,Ptr bufPtr,long count)1395 write_savefile(short wrRefNum, Ptr bufPtr, long count)
1396 {
1397 	long	wrCount = count;
1398 
1399 	if (FSWrite(wrRefNum, &wrCount, bufPtr))
1400 	{
1401 		endRecover();
1402 		note(noErr, alidNote, "\pSorry: File Write Error");
1403 		return (-1L);
1404 	}
1405 
1406 	return wrCount;
1407 }
1408 
1409 static void
close_file(short * pFRefNum)1410 close_file(short *pFRefNum)
1411 {
1412 	if (FSClose(*pFRefNum) || FlushVol((StringPtr) 0L, vRefNum))
1413 	{
1414 		endRecover();
1415 		note(noErr, alidNote, "\pSorry: File Close Error");
1416 		return;
1417 	}
1418 
1419 	*pFRefNum = -1;
1420 }
1421 
1422 static void
unlink_file(unsigned char * filename)1423 unlink_file(unsigned char *filename)
1424 {
1425 	if (HDelete(vRefNum, dirID, filename))
1426 	{
1427 		endRecover();
1428 		note(noErr, alidNote, "\pSorry: File Delete Error");
1429 		return;
1430 	}
1431 }
1432 
1433