OSDN Git Service

Initial Import
[nethackexpress/trunk.git] / sys / mac / mrecover.c
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
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
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
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
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
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
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
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
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
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
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
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
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
924 drawThermo(WindowPtr wPtr, short inum)
925 {
926         itemizeThermo(drawItem);
927 }
928
929 /* manage progress thermometer dialog */
930 static void
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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