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