/variants/slashem/sys/mac/mrecover.c

https://bitbucket.org/clivecrous/ruhack · C · 1432 lines · 1024 code · 279 blank · 129 comment · 150 complexity · 44df2bed9679a398b599ded730531f2a MD5 · raw file

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