NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / sys / mac / mrecover.c
blobbb3584d9daed59e4afbd37f18ad6fa9a088f37e3
1 /* aNetHack 0.0.1 mrecover.c $ANH-Date: 1432512798 2015/05/25 00:13:18 $ $ANH-Branch: master $:$ANH-Revision: 1.8 $ */
2 /* Copyright (c) David Hairston, 1993. */
3 /* aNetHack may be freely redistributed. See license for details. */
5 /* Macintosh Recovery Application */
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.
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
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 aNetHack to auto-recover crashed games.
38 #include "config.h"
40 /**** Toolbox defines ****/
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>
63 #ifdef THINK /* glue for System 7 Icon Family call (needed by Think C 5.0.4) \
65 pascal OSErr GetIconSuite(Handle *theIconSuite, short theResID,
66 long selector) = { 0x303C, 0x0501, 0xABC9 };
67 #endif
69 /**** Application defines ****/
71 /* Memory */
72 typedef struct memBytes /* format of 'memB' resource, preloaded/locked */
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;
82 #define membID 128 /* 'memB' resource ID */
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) */
91 /* Menu */
92 enum {
93 mbar_Init = -1,
94 mbarAppl, /* normal mode */
95 mbarRecover, /* in recovery mode */
96 mbarDA /* DA in front mode */
98 enum {
99 menuApple,
100 menuFile,
101 menuEdit,
102 menu_Total,
104 muidApple = 128,
105 muidFile,
106 muidEdit
108 enum {
109 /* Apple menu */
110 mitmAbout = 1,
111 mitmHelp,
112 ____128_1,
114 /* File menu */
115 mitmOpen = 1,
116 ____129_1,
117 mitmClose_DA,
118 ____129_2,
119 mitmQuit
121 /* standard minimum required Edit menu */
124 /* Alerts */
125 enum {
126 alrtNote, /* general messages */
127 alrtHelp, /* help message */
128 alrt_Total,
130 alertAppleMenu = 127, /* menuItem to alert ID offset */
131 alidNote,
132 alidHelp
135 #define aboutBufSize 80 /* i.e. 2 lines of 320 pixels */
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;
146 #define nmPending nmRefCon /* &in.Notify */
147 #define iconNotifyID 128
148 #define ics_1_and_4 0x00000300
150 /* Dialogs */
151 enum { dlogProgress = 256 };
152 enum { uitmThermo = 1 };
153 enum { initItem, invalItem, drawItem };
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;
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;
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)
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 */
197 #define MAX_RECOVER_COUNT 256
199 #define APP_NAME_RES_ID (-16396) /* macfile.h */
200 #define PLAYER_NAME_RES_ID 1001 /* macfile.h */
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 */
207 int hpid; /* aNetHack (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 */
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);
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);
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);
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 *);
246 /**** Routines ****/
248 main()
250 /* heap adjust */
251 MaxApplZone();
252 MoreMasters();
253 MoreMasters();
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);
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();
275 warmup();
276 eventLoop();
278 /* normally these routines are never reached from here */
279 cooldown();
280 ExitToShell();
281 return 0;
284 static void
285 warmup()
287 short i;
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);
297 #if 0 // ???
298 /* clear out the Finder info */
300 short message, count;
302 CountAppFiles(&message, &count);
303 while(count)
304 ClrAppFiles(count--);
306 #endif
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;
316 #if 1
318 /* get the app name */
319 ProcessInfoRec info;
320 ProcessSerialNumber psn;
322 info.processInfoLength = sizeof(info);
323 info.processName = nmt.nmBuf;
324 info.processAppSpec = NULL;
325 GetCurrentProcess(&psn);
326 GetProcessInformation(&psn, &info);
328 #else
329 /* prepend app name (31 chars or less) to notification buffer */
331 short apRefNum;
332 Handle apParams;
334 GetAppParms(*(Str255 *) &nmt.nmBuf, &apRefNum, &apParams);
336 #endif
338 /* add formatting (two line returns) */
339 nmt.nmBuf[++(nmt.nmBuf[0])] = '\r';
340 nmt.nmBuf[++(nmt.nmBuf[0])] = '\r';
342 /**** note() is usable now but not aesthetically complete ****/
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");
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);
357 thermoTHnd = (DialogTHndl) alignTemplate('DLOG', dlogProgress, 20, 8,
358 (Point *) 0L);
360 (void) alignTemplate('DLOG', getDlgID, 0, 6, (Point *) &sfGetWhere);
362 /* get the "busy cursors" (preloaded/locked) */
363 for (i = 0; i < curs_Total; i++) {
364 CursHandle cHnd;
366 if (!(cHnd = GetCursor(i + cursorOffset)))
367 note(nilHandleErr, 0, "\pNil CURS Handle");
369 cPtr[i] = *cHnd;
372 /* get the 'vers' 1 long (Get Info) string - About Recover... */
374 versXHandle vHnd;
376 if (!(vHnd = (versXHandle) GetResource('vers', 1)))
377 note(nilHandleErr, 0, "\pNil vers Handle");
379 i = (**vHnd).versStr[0] + 1; /* offset to Get Info pascal string */
381 if ((aboutBuf[0] = (**vHnd).versStr[i]) > (aboutBufSize - 1))
382 aboutBuf[0] = aboutBufSize - 1;
384 i++;
386 MoveHHi((Handle) vHnd); /* DEE - Fense ... */
387 HLock((Handle) vHnd);
388 BlockMove(&((**vHnd).versStr[i]), &(aboutBuf[1]), aboutBuf[0]);
389 ReleaseResource((Handle) vHnd);
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");
397 /* expand the apple menu */
398 if (i == menuApple)
399 AddResMenu(mHnd[menuApple], 'DRVR');
401 InsertMenu(mHnd[i], 0);
404 /* pre-emptive memory check */
406 memBytesHandle hBytes;
407 Size grow;
409 if (!(hBytes = (memBytesHandle) GetResource('memB', membID)))
410 note(nilHandleErr, 0, "\pNil Memory Handle");
412 pBytes = *hBytes;
414 if (MaxMem(&grow) < pBytes->memPreempt)
415 note(memFullErr, 0, "\pMore Memory Required\rTry adding 16k");
417 memActivity = pBytes->memCleanup; /* force initial cleanup */
420 /* get the I/O buffer */
421 if (!(pIOBuf = NewPtr(pBytes->memIOBuf)))
422 note(memFullErr, 0, "\pNil I/O Pointer");
425 /* align a window-related template to the main screen */
426 static Handle
427 alignTemplate(ResType rezType, short rezID, short vOff, short vDenom,
428 Point *pPt)
430 Handle rtnHnd;
431 Rect *pRct;
433 vOff += GetMBarHeight();
435 if (!(rtnHnd = GetResource(rezType, rezID)))
436 note(nilHandleErr, 0, "\pNil Template Handle");
438 pRct = (Rect *) *rtnHnd;
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;
449 if (pPt)
450 *pPt = *(Point *) pRct; /* top left corner */
452 return rtnHnd;
455 /* notification completion routine */
456 pascal void
457 nmCompletion(NMRec *pNMR)
459 (void) NMRemove(pNMR);
461 (*(short *) (pNMR->nmPending))--; /* decrement pending note level */
462 ((notifPtr) pNMR)->nmDispose = 1; /* allow DisposPtr() */
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.
469 static void
470 noteErrorMessage(unsigned char *msg, unsigned char *errMsg)
472 short i = nmt.nmBuf[0] + 1; /* insertion point */
474 BlockMove(&msg[1], &nmt.nmBuf[i], msg[0]);
475 nmt.nmBuf[i + msg[0]] = '\r';
476 nmt.nmBuf[0] += (msg[0] + 1);
478 note(memFullErr, 0, errMsg);
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).
486 static void
487 note(short errorSignal, short alertID, unsigned char *msg)
489 if (!errorSignal) {
490 Size grow;
492 if (MaxMem(&grow) < pBytes->memAbort)
493 noteErrorMessage(msg, "\pOut of Memory");
496 if (errorSignal || !in.Front) {
497 notifPtr pNMR;
498 short i = nmt.nmBuf[0] + 1; /* insertion point */
500 if (errorSignal) /* use notification template */
502 pNMR = &nmt;
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 */
510 if (!(pNMR = (notifPtr) NewPtr(sizeof(notifRec))))
511 noteErrorMessage(msg, "\pNil New Pointer");
513 /* initialize it */
514 *pNMR = nmt;
515 pNMR->nmr.nmStr = (StringPtr) & (pNMR->nmBuf);
517 /* update the notification queue */
518 if (!pNMQ)
519 pNMQ = pNMR;
520 else {
521 notifPtr pNMX;
523 /* find the end of the queue */
524 for (pNMX = pNMQ; pNMX->nmNext; pNMX = pNMX->nmNext)
527 pNMX->nmNext = pNMR;
531 /* concatenate the message */
532 BlockMove(&msg[1], &((pNMR->nmBuf)[i]), msg[0]);
533 (pNMR->nmBuf)[0] += msg[0];
535 in.Notify++; /* increase note pending level */
537 NMInstall((NMRec *) pNMR);
539 if (errorSignal)
540 cooldown();
542 return;
545 /* in front and no error so use an alert */
546 ParamText(msg, "\p", "\p", "\p");
547 (void) Alert(alertID, (ModalFilterUPP) 0L);
548 ResetAlrtStage();
550 memActivity++;
553 static void
554 adjustGUI()
556 static short oldMenubar = mbar_Init; /* force initial update */
557 short newMenubar;
558 WindowPeek frontWindow;
560 /* oldCursor is external so it can be reset in endRecover() */
561 static short newCursor = curs_Init;
562 unsigned long timeNow;
563 short useArrow;
565 /* adjust menubar 1st */
566 newMenubar = in.Recover ? mbarRecover : mbarAppl;
568 /* desk accessories take precedence */
569 if (frontWindow = (WindowPeek) FrontWindow())
570 if (frontWindow->windowKind < 0)
571 newMenubar = mbarDA;
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;
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;
590 case mbarDA:
591 DisableItem(mHnd[menuFile], mitmOpen);
592 EnableItem(mHnd[menuFile], mitmClose_DA);
593 EnableItem(mHnd[menuEdit], 0);
594 break;
597 DrawMenuBar();
600 /* now adjust the cursor */
601 if (useArrow = (!in.Recover || (newMenubar == mbarDA)))
602 newCursor = curs_Init;
603 else if ((timeNow = TickCount()) >= timeCursor) /* spin cursor */
605 timeCursor = timeNow + CURS_FRAME;
606 if (++newCursor >= curs_Total)
607 newCursor = 0;
610 if (newCursor != oldCursor) {
611 oldCursor = newCursor;
613 SetCursor(useArrow ? &qd.arrow : cPtr[newCursor]);
617 static void
618 adjustMemory()
620 Size grow;
622 memActivity = 0;
624 if (MaxMem(&grow) < pBytes->memWarning)
625 note(noErr, alidNote, "\pWarning: Memory is running low");
627 (void) ResrvMem((Size) FreeMem()); /* move all handles high */
630 /* show memory stats: FreeMem, MaxBlock, PurgeSpace, and StackSpace */
631 static void
632 optionMemStats()
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;
642 if (wnEvt.modifiers & shiftKey)
643 adjustMemory();
645 if (!(strHnd = NewHandle((Size) 128))) {
646 note(noErr, alidNote, "\pOops: Memory stats unavailable!");
647 return;
650 SetString((StringHandle) strHnd, pFormat);
651 nOffset = 1L;
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;
670 NumToString((nStat >> 10), *(Str255 *) &nBuf);
672 **strHnd += nBuf[0] - 1;
673 nOffset =
674 Munger(strHnd, nOffset, (Ptr) pSub, 1L, (Ptr) &nBuf[1], nBuf[0]);
677 MoveHHi(strHnd);
678 HLock(strHnd);
679 note(noErr, alidNote, (unsigned char *) *strHnd);
680 DisposHandle(strHnd);
683 static void
684 RecoverMenuEvent(long menuEntry)
686 short menuID = HiWord(menuEntry);
687 short menuItem = LoWord(menuEntry);
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;
700 default: /* DA's or apple menu items */
702 unsigned char daName[32];
704 GetItem(mHnd[menuApple], menuItem, *(Str255 *) &daName);
705 (void) OpenDeskAcc(daName);
707 memActivity++;
708 } break;
710 break;
712 case muidFile:
713 switch (menuItem) {
714 case mitmOpen:
715 beginRecover();
716 break;
718 case mitmClose_DA: {
719 WindowPeek frontWindow;
720 short refNum;
722 if (frontWindow = (WindowPeek) FrontWindow())
723 if ((refNum = frontWindow->windowKind) < 0)
724 CloseDeskAcc(refNum);
726 memActivity++;
727 } break;
729 case mitmQuit:
730 cooldown();
731 break;
733 break;
735 case muidEdit:
736 (void) SystemEdit(menuItem - 1);
737 break;
740 HiliteMenu(0);
743 static void
744 eventLoop()
746 short wneMask = (in.Front ? everyEvent : (osMask + updateMask));
747 long wneSleep = (in.Front ? 0L : 3L);
749 while (1) {
750 if (in.Front)
751 adjustGUI();
753 if (memActivity >= pBytes->memCleanup)
754 adjustMemory();
756 (void) WaitNextEvent(wneMask, &wnEvt, wneSleep, (RgnHandle) 0L);
758 if (in.Dialog)
759 (void) IsDialogEvent(&wnEvt);
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);
769 break;
771 case nullEvent:
772 /* adjust the FIFO notification queue */
773 if (pNMQ && pNMQ->nmDispose) {
774 notifPtr pNMX = pNMQ->nmNext;
776 DisposPtr((Ptr) pNMQ);
777 pNMQ = pNMX;
779 memActivity++;
782 if (in.Recover)
783 continueRecover();
784 break;
786 case mouseDown: {
787 WindowPtr whichWindow;
789 switch (FindWindow(wnEvt.where, &whichWindow)) {
790 case inMenuBar:
791 RecoverMenuEvent(MenuSelect(wnEvt.where));
792 break;
794 case inSysWindow:
795 SystemClick(&wnEvt, whichWindow);
796 break;
798 case inDrag: {
799 Rect boundsRect = qd.screenBits.bounds;
800 Point offsetPt;
802 InsetRect(&boundsRect, 4, 4);
803 boundsRect.top += GetMBarHeight();
805 DragWindow(whichWindow, *((Point *) &wnEvt.where),
806 &boundsRect);
808 boundsRect = whichWindow->portRect;
809 offsetPt = *(Point *) &(whichWindow->portBits.bounds);
810 OffsetRect(&boundsRect, -offsetPt.h, -offsetPt.v);
812 *(Rect *) *thermoTHnd = boundsRect;
813 } break;
815 } break;
817 case keyDown: {
818 char key = (wnEvt.message & charCodeMask);
820 if (wnEvt.modifiers & cmdKey) {
821 if (key == '.') {
822 if (in.Recover) {
823 endRecover();
824 note(noErr, alidNote, "\pSorry: Recovery aborted");
826 } else
827 RecoverMenuEvent(MenuKey(key));
829 } break;
831 /* without windows these events belong to our thermometer */
832 case updateEvt:
833 case activateEvt: {
834 DialogPtr dPtr;
835 short itemHit;
837 (void) DialogSelect(&wnEvt, &dPtr, &itemHit);
840 case diskEvt:
841 if (HiWord(wnEvt.message)) {
842 Point pt = { 60, 60 };
844 (void) DIBadMount(pt, wnEvt.message);
845 DIUnload();
847 memActivity++;
849 break;
850 } /* switch (wnEvt.what) */
851 } /* while (1) */
854 static void
855 cooldown()
857 if (in.Recover)
858 endRecover();
860 /* wait for pending notifications to complete */
861 while (in.Notify)
862 (void) WaitNextEvent(0, &wnEvt, 3L, (RgnHandle) 0L);
864 ExitToShell();
867 /* draw the progress thermometer and frame. 1 level <=> 1 horiz. pixel */
868 pascal void
869 drawThermo(WindowPtr wPtr, short inum)
871 itemizeThermo(drawItem);
874 /* manage progress thermometer dialog */
875 static void
876 itemizeThermo(short itemMode)
878 short iTyp, iTmp;
879 Handle iHnd;
880 Rect iRct;
882 GetDItem(DLGTHM, uitmThermo, &iTyp, &iHnd, &iRct);
884 switch (itemMode) {
885 case initItem:
886 SetDItem(DLGTHM, uitmThermo, iTyp, (Handle) drawThermoUPP, &iRct);
887 break;
889 case invalItem: {
890 GrafPtr oldPort;
892 GetPort(&oldPort);
893 SetPort(GRFTHM);
895 InsetRect(&iRct, 1, 1);
896 InvalRect(&iRct);
898 SetPort(oldPort);
899 } break;
901 case drawItem:
902 FrameRect(&iRct);
903 InsetRect(&iRct, 1, 1);
905 iTmp = iRct.right;
906 iRct.right = iRct.left + in.Recover;
907 PaintRect(&iRct);
909 iRct.left = iRct.right;
910 iRct.right = iTmp;
911 EraseRect(&iRct);
912 break;
916 /* show only <pid-plname>.0 files in get file dialog */
917 pascal Boolean
918 basenameFileFilter(ParmBlkPtr pPB)
920 unsigned char *pC;
922 if (!(pC = (unsigned char *) pPB->fileParam.ioNamePtr))
923 return true;
925 if ((*pC < 4) || (*pC > 28)) /* save/ 1name .0 */
926 return true;
928 if ((pC[*pC - 1] == '.') && (pC[*pC] == '0')) /* bingo! */
929 return false;
931 return true;
934 static void
935 beginRecover()
937 SFTypeList levlType = { 'LEVL' };
938 SFReply sfGetReply;
940 SFGetFile(sfGetWhere, "\p", basenameFileFilterUPP, 1, levlType,
941 (DlgHookUPP) 0L, &sfGetReply);
943 memActivity++;
945 if (!sfGetReply.good)
946 return;
948 /* get volume (working directory) refnum, basename, and directory i.d. */
949 vRefNum = sfGetReply.vRefNum;
950 BlockMove(sfGetReply.fName, lock, sfGetReply.fName[0] + 1);
952 static CInfoPBRec catInfo;
954 catInfo.hFileInfo.ioNamePtr = (StringPtr) sfGetReply.fName;
955 catInfo.hFileInfo.ioVRefNum = sfGetReply.vRefNum;
956 catInfo.hFileInfo.ioDirID = 0L;
958 if (PBGetCatInfoSync(&catInfo)) {
959 note(noErr, alidNote, "\pSorry: Bad File Info");
960 return;
963 dirID = catInfo.hFileInfo.ioFlParID;
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++;
974 itemizeThermo(initItem);
976 ShowWindow(WNDTHM);
979 timeCursor = TickCount() + CURS_LATENT;
980 saveRefNum = gameRefNum = levRefNum = -1;
981 in.Recover = 1;
984 static void
985 continueRecover()
987 restore_savefile();
989 /* update the thermometer */
990 if (in.Dialog && !(in.Recover % 4))
991 itemizeThermo(invalItem);
993 if (in.Recover <= MAX_RECOVER_COUNT)
994 return;
996 endRecover();
998 if (saveRezStrings())
999 return;
1001 note(noErr, alidNote, "\pOK: Recovery succeeded");
1004 /* no messages from here (since we might be quitting) */
1005 static void
1006 endRecover()
1008 in.Recover = 0;
1010 oldCursor = curs_Init;
1011 SetCursor(&qd.arrow);
1013 /* clean up abandoned files */
1014 if (gameRefNum >= 0)
1015 (void) FSClose(gameRefNum);
1017 if (levRefNum >= 0)
1018 (void) FSClose(levRefNum);
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);
1027 saveRefNum = gameRefNum = levRefNum = -1;
1029 /* close the progress thermometer dialog */
1030 in.Dialog = 0;
1031 CloseDialog(DLGTHM);
1032 DisposHandle(dlgThermo.items);
1033 memActivity++;
1036 /* add friendly, non-essential resource strings to save file */
1037 static short
1038 saveRezStrings()
1040 short sRefNum;
1041 StringHandle strHnd;
1042 short i, rezID;
1043 unsigned char *plName;
1045 HCreateResFile(vRefNum, dirID, savename);
1047 sRefNum = HOpenResFile(vRefNum, dirID, savename, fsRdWrPerm);
1048 if (sRefNum <= 0) {
1049 note(noErr, alidNote, "\pOK: Minor resource map error");
1050 return 1;
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;
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;
1070 case 2:
1071 rezID = APP_NAME_RES_ID;
1072 strHnd = NewString(*(Str255 *) "\paNetHack");
1073 break;
1076 if (!strHnd) {
1077 note(noErr, alidNote, "\pOK: Minor \'STR \' resource error");
1078 CloseResFile(sRefNum);
1079 return 1;
1082 /* should check for errors... */
1083 AddResource((Handle) strHnd, 'STR ', rezID, *(Str255 *) "\p");
1086 memActivity++;
1088 /* should check for errors... */
1089 CloseResFile(sRefNum);
1090 return 0;
1093 static void
1094 set_levelfile_name(long lev)
1096 unsigned char *tf;
1098 /* find the dot. this is guaranteed to happen. */
1099 for (tf = (lock + *lock); *tf != '.'; tf--, lock[0]--)
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??? */
1109 endRecover();
1110 note(noErr, alidNote, "\pSorry: File Name Error");
1114 static short
1115 open_levelfile(long lev)
1117 OSErr openErr;
1118 short fRefNum;
1120 set_levelfile_name(lev);
1121 if (!in.Recover)
1122 return (-1);
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);
1131 return (openErr ? -1 : fRefNum);
1134 static short
1135 create_savefile(unsigned char *savename)
1137 short fRefNum;
1139 /* translate savename to a pascal string (in place) */
1141 unsigned char *pC;
1142 short nameLen;
1144 for (pC = savename; *pC; pC++)
1147 nameLen = pC - savename;
1149 for (; pC > savename; pC--)
1150 *pC = *(pC - 1);
1152 *savename = nameLen;
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);
1162 return fRefNum;
1165 static void
1166 copy_bytes(short inRefNum, short outRefNum)
1168 char *buf = (char *) pIOBuf;
1169 long bufSiz = pBytes->memIOBuf;
1171 long nfrom, nto;
1173 do {
1174 nfrom = read_levelfile(inRefNum, buf, bufSiz);
1175 if (!in.Recover)
1176 return;
1178 nto = write_savefile(outRefNum, buf, nfrom);
1179 if (!in.Recover)
1180 return;
1182 if (nto != nfrom) {
1183 endRecover();
1184 note(noErr, alidNote, "\pSorry: File Copy Error");
1185 return;
1187 } while (nfrom == bufSiz);
1190 static void
1191 restore_savefile()
1193 static int savelev;
1194 long saveTemp, lev;
1195 xchar levc;
1196 struct version_info version_data;
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 anethack would have created
1202 * and game state
1205 lev = in.Recover - 1;
1206 if (lev == 0L) {
1207 gameRefNum = open_levelfile(0L);
1209 if (in.Recover)
1210 (void) read_levelfile(gameRefNum, (Ptr) &hpid, sizeof(hpid));
1212 if (in.Recover)
1213 saveTemp =
1214 read_levelfile(gameRefNum, (Ptr) &savelev, sizeof(savelev));
1216 if (in.Recover && (saveTemp != sizeof(savelev))) {
1217 endRecover();
1218 note(noErr, alidNote, "\pSorry: \"checkpoint\" was not enabled");
1219 return;
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);
1229 /* save file should contain:
1230 * current level (including pets)
1231 * (non-level-based) game state
1232 * other levels
1234 if (in.Recover)
1235 saveRefNum = create_savefile(savename);
1237 if (in.Recover)
1238 levRefNum = open_levelfile(savelev);
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);
1246 if (in.Recover)
1247 close_file(&levRefNum);
1249 if (in.Recover)
1250 unlink_file(lock);
1252 if (in.Recover)
1253 copy_bytes(gameRefNum, saveRefNum);
1255 if (in.Recover)
1256 close_file(&gameRefNum);
1258 if (in.Recover)
1259 set_levelfile_name(0L);
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;
1269 (void) write_savefile(saveRefNum, (Ptr) &levc, sizeof(levc));
1271 if (in.Recover)
1272 copy_bytes(levRefNum, saveRefNum);
1274 if (in.Recover)
1275 close_file(&levRefNum);
1277 if (in.Recover)
1278 unlink_file(lock);
1282 if (in.Recover == MAX_RECOVER_COUNT)
1283 close_file(&saveRefNum);
1285 if (in.Recover)
1286 in.Recover++;
1289 static long
1290 read_levelfile(short rdRefNum, Ptr bufPtr, long count)
1292 OSErr rdErr;
1293 long rdCount = count;
1295 if ((rdErr = FSRead(rdRefNum, &rdCount, bufPtr)) && (rdErr != eofErr)) {
1296 endRecover();
1297 note(noErr, alidNote, "\pSorry: File Read Error");
1298 return (-1L);
1301 return rdCount;
1304 static long
1305 write_savefile(short wrRefNum, Ptr bufPtr, long count)
1307 long wrCount = count;
1309 if (FSWrite(wrRefNum, &wrCount, bufPtr)) {
1310 endRecover();
1311 note(noErr, alidNote, "\pSorry: File Write Error");
1312 return (-1L);
1315 return wrCount;
1318 static void
1319 close_file(short *pFRefNum)
1321 if (FSClose(*pFRefNum) || FlushVol((StringPtr) 0L, vRefNum)) {
1322 endRecover();
1323 note(noErr, alidNote, "\pSorry: File Close Error");
1324 return;
1327 *pFRefNum = -1;
1330 static void
1331 unlink_file(unsigned char *filename)
1333 if (HDelete(vRefNum, dirID, filename)) {
1334 endRecover();
1335 note(noErr, alidNote, "\pSorry: File Delete Error");
1336 return;