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