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