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