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