diff --git a/sys/mac/mrecover.c b/sys/mac/mrecover.c new file mode 100644 index 000000000..6929e77c1 --- /dev/null +++ b/sys/mac/mrecover.c @@ -0,0 +1,1419 @@ +/* SCCS Id: @(#)mrecover.c 3.3 96/07/24 */ +/* Copyright (c) David Hairston, 1993. */ +/* NetHack may be freely redistributed. See license for details. */ + +/* Macintosh Recovery Application */ + +/* based on code in util/recover.c. the significant differences are: + * - GUI vs. CLI. the vast majority of code here supports the GUI. + * - Mac toolbox equivalents are used in place of ANSI functions. + * - void restore_savefile(void) is event driven. + * - integral type substitutions here and there. + */ + +/* + * Think C 5.0.4 project specs: + * signature: 'nhRc' + * SIZE (-1) info: flags: 0x5880, size: 65536L/65536L (64k/64k) + * libraries: MacTraps [yes], MacTraps2 (HFileStuff) [yes], ANSI [no] + * compatibility: system 6 and system 7 + * misc: sizeof(int): 2, "\p": unsigned char, enum size varies, + * prototypes required, type checking enforced, no optimizers, + * FAR CODE [no], FAR DATA [no], SEPARATE STRS [no], single segment, + * short macsbug symbols + */ + +/* + * To do (maybe, just maybe): + * - Merge with the code in util/recover.c. + * - Document launch (e.g. GUI equivalent of 'recover basename'). + * - Drag and drop. + * - Internal memory tweaks (stack and heap usage). + * - Use status file to allow resuming aborted recoveries. + * - Bundle 'LEVL' files with recover (easier document launch). + * - Prohibit recovering games "in progress". + * - Share AppleEvents with NetHack to auto-recover crashed games. + */ + + +#include "config.h" + +/**** Toolbox defines ****/ + +/* MPW C headers (99.44% pure) */ +#include +#include +#include +#include +#ifdef applec +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef THINK /* glue for System 7 Icon Family call (needed by Think C 5.0.4) */ +pascal OSErr GetIconSuite(Handle *theIconSuite, short theResID, long selector) + = {0x303C, 0x0501, 0xABC9}; +#endif + + +/**** Application defines ****/ + +/* Memory */ +typedef struct memBytes /* format of 'memB' resource, preloaded/locked */ +{ + short memReserved; + short memCleanup; /* 4 - memory monitor activity limit */ + long memPreempt; /* 32k - start iff FreeMem() > */ + long memWarning; /* 12k - warn if MaxMem() < */ + long memAbort; /* 4k - abort if MaxMem() < */ + long memIOBuf; /* 16k - read/write buffer size */ +} memBytes, *memBytesPtr, **memBytesHandle; + +#define membID 128 /* 'memB' resource ID */ + + +/* Cursor */ +#define CURS_FRAME 4L /* 1/15 second - spin cursor */ +#define CURS_LATENT 60L /* pause before spin cursor */ +#define curs_Init (-1) /* token for set arrow */ +#define curs_Total 8 /* maybe 'acur' would be better */ +#define cursorOffset 128 /* GetCursor(cursorOffset + i) */ + + +/* Menu */ +enum +{ + mbar_Init = -1, + mbarAppl, /* normal mode */ + mbarRecover, /* in recovery mode */ + mbarDA /* DA in front mode */ +}; +enum +{ + menuApple, + menuFile, + menuEdit, + menu_Total, + + muidApple = 128, + muidFile, + muidEdit +}; +enum +{ + /* Apple menu */ + mitmAbout = 1, + mitmHelp, + ____128_1, + + /* File menu */ + mitmOpen = 1, + ____129_1, + mitmClose_DA, + ____129_2, + mitmQuit + + /* standard minimum required Edit menu */ +}; + + +/* Alerts */ +enum +{ + alrtNote, /* general messages */ + alrtHelp, /* help message */ + alrt_Total, + + alertAppleMenu = 127, /* menuItem to alert ID offset */ + alidNote, + alidHelp +}; + +#define aboutBufSize 80 /* i.e. 2 lines of 320 pixels */ + + +/* Notification */ +#define nmBufSize (32 + aboutBufSize + 32) +typedef struct notifRec +{ + NMRec nmr; + struct notifRec * nmNext; + short nmDispose; + unsigned char nmBuf[nmBufSize]; +} notifRec, *notifPtr; + +#define nmPending nmRefCon /* &in.Notify */ +#define iconNotifyID 128 +#define ics_1_and_4 0x00000300 + +/* Dialogs */ +enum +{ + dlogProgress = 256 +}; +enum +{ + uitmThermo = 1 +}; +enum +{ + initItem, + invalItem, + drawItem +}; + + +/* Miscellaneous */ +typedef struct modeFlags +{ + short Front; /* fg/bg event handling */ + short Notify; /* level of pending NM notifications */ + short Dialog; /* a modeless dialog is open */ + short Recover; /* restoration progress index */ +} modeFlags; + +/* convenient define to allow easier (for me) parsing of 'vers' resource */ +typedef struct versXRec +{ + NumVersion numVers; + short placeCode; + unsigned char versStr[]; /* (small string)(large string) */ +} versXRec, *versXPtr, **versXHandle; + + + +/**** Global variables ****/ +modeFlags in = {1}; /* in Front */ +EventRecord wnEvt; +SysEnvRec sysEnv; +unsigned char aboutBuf[aboutBufSize]; /* vers 1 "Get Info" string */ +memBytesPtr pBytes; /* memory management */ +unsigned short memActivity; /* more memory management */ +MenuHandle mHnd[menu_Total]; +CursPtr cPtr[curs_Total]; /* busy cursors */ +unsigned long timeCursor; /* next cursor frame time */ +short oldCursor = curs_Init; /* see adjustGUI() below */ +notifPtr pNMQ; /* notification queue pointer */ +notifRec nmt; /* notification template */ +DialogTHndl thermoTHnd; +DialogRecord dlgThermo; /* progress thermometer */ +#define DLGTHM ((DialogPtr) &dlgThermo) +#define WNDTHM ((WindowPtr) &dlgThermo) +#define GRFTHM ((GrafPtr) &dlgThermo) + +Point sfGetWhere; /* top left corner of get file dialog */ +Ptr pIOBuf; /* read/write buffer pointer */ +short vRefNum; /* SFGetFile working directory/volume refnum */ +long dirID; /* directory i.d. */ +NMUPP nmCompletionUPP; /* UPP for nmCompletion */ +FileFilterUPP basenameFileFilterUPP; /* UPP for basenameFileFilter */ +UserItemUPP drawThermoUPP; /* UPP for progress callback */ + +#define MAX_RECOVER_COUNT 256 + +#define APP_NAME_RES_ID (-16396) /* macfile.h */ +#define PLAYER_NAME_RES_ID 1001 /* macfile.h */ + +/* variables from util/recover.c */ +#define SAVESIZE FILENAME +unsigned char savename[SAVESIZE]; /* originally a C string */ +unsigned char lock[256]; /* pascal string */ + +int hpid; /* NetHack (unix-style) process i.d. */ +short saveRefNum; /* save file descriptor */ +short gameRefNum; /* level 0 file descriptor */ +short levRefNum; /* level n file descriptor */ + + +/**** Prototypes ****/ +static void warmup(void); +static Handle alignTemplate(ResType, short, short, short, Point *); +pascal void nmCompletion(NMRec *); +static void noteErrorMessage(unsigned char *, unsigned char *); +static void note(short, short, unsigned char *); +static void adjustGUI(void); +static void adjustMemory(void); +static void optionMemStats(void); +static void RecoverMenuEvent(long); +static void eventLoop(void); +static void cooldown(void); + +pascal void drawThermo(WindowPtr, short); +static void itemizeThermo(short); +pascal Boolean basenameFileFilter(ParmBlkPtr); +static void beginRecover(void); +static void continueRecover(void); +static void endRecover(void); +static short saveRezStrings(void); + +/* analogous prototypes from util/recover.c */ +static void set_levelfile_name(long); +static short open_levelfile(long); +static short create_savefile(unsigned char *); +static void copy_bytes(short, short); +static void restore_savefile(void); + +/* auxiliary prototypes */ +static long read_levelfile(short, Ptr, long); +static long write_savefile(short, Ptr, long); +static void close_file(short *); +static void unlink_file(unsigned char *); + + +/**** Routines ****/ + +main() +{ + /* heap adjust */ + MaxApplZone(); + MoreMasters(); + MoreMasters(); + + /* manager initialization */ + InitGraf(&qd.thePort); + InitFonts(); + InitWindows(); + InitMenus(); + TEInit(); + InitDialogs(0L); + InitCursor(); + nmCompletionUPP = NewNMProc(nmCompletion); + basenameFileFilterUPP = NewFileFilterProc(basenameFileFilter); + drawThermoUPP = NewUserItemProc(drawThermo); + + /* get system environment, notification requires 6.0 or better */ + (void) SysEnvirons(curSysEnvVers, &sysEnv); + if (sysEnv.systemVersion < 0x0600) + { + ParamText("\pAbort: System 6.0 is required", "\p", "\p", "\p"); + (void) Alert(alidNote, (ModalFilterUPP) 0L); + ExitToShell(); + } + + warmup(); + eventLoop(); + + /* normally these routines are never reached from here */ + cooldown(); + ExitToShell(); + return 0; +} + +static void +warmup() +{ + short i; + + /* pre-System 7 MultiFinder hack for smooth launch */ + for (i = 0; i < 10; i++) + { + if (WaitNextEvent(osMask, &wnEvt, 2L, (RgnHandle) 0L)) + if (((wnEvt.message & osEvtMessageMask) >> 24) == suspendResumeMessage) + in.Front = (wnEvt.message & resumeFlag); + } + +#if 0 // ??? + /* clear out the Finder info */ + { + short message, count; + + CountAppFiles(&message, &count); + while(count) + ClrAppFiles(count--); + } +#endif + + /* fill out the notification template */ + nmt.nmr.qType = nmType; + nmt.nmr.nmMark = 1; + nmt.nmr.nmSound = (Handle) -1L; /* system beep */ + nmt.nmr.nmStr = nmt.nmBuf; + nmt.nmr.nmResp = nmCompletionUPP; + nmt.nmr.nmPending = (long) &in.Notify; + + +#if 1 + { + /* get the app name */ + ProcessInfoRec info; + ProcessSerialNumber psn; + + info.processInfoLength = sizeof(info); + info.processName = nmt.nmBuf; + info.processAppSpec = NULL; + GetCurrentProcess(&psn); + GetProcessInformation(&psn, &info); + } +#else + /* prepend app name (31 chars or less) to notification buffer */ + { + short apRefNum; + Handle apParams; + + GetAppParms(* (Str255 *) &nmt.nmBuf, &apRefNum, &apParams); + } +#endif + + /* add formatting (two line returns) */ + nmt.nmBuf[++(nmt.nmBuf[0])] = '\r'; + nmt.nmBuf[++(nmt.nmBuf[0])] = '\r'; + + /**** note() is usable now but not aesthetically complete ****/ + + /* get notification icon */ + if (sysEnv.systemVersion < 0x0700) + { + if (! (nmt.nmr.nmIcon = GetResource('SICN', iconNotifyID))) + note(nilHandleErr, 0, "\pNil SICN Handle"); + } + else + { + if (GetIconSuite(&nmt.nmr.nmIcon, iconNotifyID, ics_1_and_4)) + note(nilHandleErr, 0, "\pBad Icon Family"); + } + + /* load and align various dialog/alert templates */ + (void) alignTemplate('ALRT', alidNote, 0, 4, (Point *) 0L); + (void) alignTemplate('ALRT', alidHelp, 0, 4, (Point *) 0L); + + thermoTHnd = (DialogTHndl) alignTemplate('DLOG', dlogProgress, 20, 8, (Point *) 0L); + + (void) alignTemplate('DLOG', getDlgID, 0, 6, (Point *) &sfGetWhere); + + /* get the "busy cursors" (preloaded/locked) */ + for (i = 0; i < curs_Total; i++) + { + CursHandle cHnd; + + if (! (cHnd = GetCursor(i + cursorOffset))) + note(nilHandleErr, 0, "\pNil CURS Handle"); + + cPtr[i] = *cHnd; + } + + /* get the 'vers' 1 long (Get Info) string - About Recover... */ + { + versXHandle vHnd; + + if (! (vHnd = (versXHandle) GetResource('vers', 1))) + note(nilHandleErr, 0, "\pNil vers Handle"); + + i = (**vHnd).versStr[0] + 1; /* offset to Get Info pascal string */ + + if ((aboutBuf[0] = (**vHnd).versStr[i]) > (aboutBufSize - 1)) + aboutBuf[0] = aboutBufSize - 1; + + i++; + + MoveHHi((Handle) vHnd); /* DEE - Fense ... */ + HLock((Handle) vHnd); + BlockMove(&((**vHnd).versStr[i]), &(aboutBuf[1]), aboutBuf[0]); + ReleaseResource((Handle) vHnd); + } + + /* form the menubar */ + for (i = 0; i < menu_Total; i++) + { + if (! (mHnd[i] = GetMenu(i + muidApple))) + note(nilHandleErr, 0, "\pNil MENU Handle"); + + /* expand the apple menu */ + if (i == menuApple) + AddResMenu(mHnd[menuApple], 'DRVR'); + + InsertMenu(mHnd[i], 0); + } + + /* pre-emptive memory check */ + { + memBytesHandle hBytes; + Size grow; + + if (! (hBytes = (memBytesHandle) GetResource('memB', membID))) + note(nilHandleErr, 0, "\pNil Memory Handle"); + + pBytes = *hBytes; + + if (MaxMem(&grow) < pBytes->memPreempt) + note(memFullErr, 0, "\pMore Memory Required\rTry adding 16k"); + + memActivity = pBytes->memCleanup; /* force initial cleanup */ + } + + /* get the I/O buffer */ + if (! (pIOBuf = NewPtr(pBytes->memIOBuf))) + note(memFullErr, 0, "\pNil I/O Pointer"); +} + +/* align a window-related template to the main screen */ +static Handle +alignTemplate(ResType rezType, short rezID, short vOff, short vDenom, Point *pPt) +{ + Handle rtnHnd; + Rect *pRct; + + vOff += GetMBarHeight(); + + if (! (rtnHnd = GetResource(rezType, rezID))) + note(nilHandleErr, 0, "\pNil Template Handle"); + + pRct = (Rect *) *rtnHnd; + + /* don't move memory while aligning rect */ + pRct->right -= pRct->left; /* width */ + pRct->bottom -= pRct->top; /* height */ + pRct->left = (qd.screenBits.bounds.right - pRct->right) / 2; + pRct->top = (qd.screenBits.bounds.bottom - pRct->bottom - vOff) / vDenom; + pRct->top += vOff; + pRct->right += pRct->left; + pRct->bottom += pRct->top; + + if (pPt) + *pPt = * (Point *) pRct; /* top left corner */ + + return rtnHnd; +} + +/* notification completion routine */ +pascal void +nmCompletion(NMRec * pNMR) +{ + (void) NMRemove(pNMR); + + (* (short *) (pNMR->nmPending))--; /* decrement pending note level */ + ((notifPtr) pNMR)->nmDispose = 1; /* allow DisposPtr() */ +} + +/* + * handle errors inside of note(). the error message is appended to the + * given message but on a separate line and must fit within nmBufSize. + */ +static void +noteErrorMessage(unsigned char *msg, unsigned char *errMsg) +{ + short i = nmt.nmBuf[0] + 1; /* insertion point */ + + BlockMove(&msg[1], &nmt.nmBuf[i], msg[0]); + nmt.nmBuf[i + msg[0]] = '\r'; + nmt.nmBuf[0] += (msg[0] + 1); + + note(memFullErr, 0, errMsg); +} + +/* + * display messages using Notification Manager or an alert. + * no run-length checking is done. the messages are created to fit + * in the allocated space (nmBufSize and aboutBufSize). + */ +static void +note(short errorSignal, short alertID, unsigned char *msg) +{ + if (! errorSignal) + { + Size grow; + + if (MaxMem(&grow) < pBytes->memAbort) + noteErrorMessage(msg, "\pOut of Memory"); + } + + if (errorSignal || !in.Front) + { + notifPtr pNMR; + short i = nmt.nmBuf[0] + 1; /* insertion point */ + + if (errorSignal) /* use notification template */ + { + pNMR = &nmt; + + /* we're going to abort so add in this prefix */ + BlockMove("Abort: ", &nmt.nmBuf[i], 7); + i += 7; + nmt.nmBuf[0] += 7; + } + else /* allocate a notification record */ + { + if (! (pNMR = (notifPtr) NewPtr(sizeof(notifRec)))) + noteErrorMessage(msg, "\pNil New Pointer"); + + /* initialize it */ + *pNMR = nmt; + pNMR->nmr.nmStr = (StringPtr) &(pNMR->nmBuf); + + /* update the notification queue */ + if (!pNMQ) + pNMQ = pNMR; + else + { + notifPtr pNMX; + + /* find the end of the queue */ + for (pNMX = pNMQ; pNMX->nmNext; pNMX = pNMX->nmNext) + ; + + pNMX->nmNext = pNMR; + } + } + + /* concatenate the message */ + BlockMove(&msg[1], &((pNMR->nmBuf)[i]), msg[0]); + (pNMR->nmBuf)[0] += msg[0]; + + in.Notify++; /* increase note pending level */ + + NMInstall((NMRec *) pNMR); + + if (errorSignal) + cooldown(); + + return; + } + + /* in front and no error so use an alert */ + ParamText(msg, "\p", "\p", "\p"); + (void) Alert(alertID, (ModalFilterUPP) 0L); + ResetAlrtStage(); + + memActivity++; +} + +static void +adjustGUI() +{ + static short oldMenubar = mbar_Init; /* force initial update */ + short newMenubar; + WindowPeek frontWindow; + + /* oldCursor is external so it can be reset in endRecover() */ + static short newCursor = curs_Init; + unsigned long timeNow; + short useArrow; + + /* adjust menubar 1st */ + newMenubar = in.Recover ? mbarRecover : mbarAppl; + + /* desk accessories take precedence */ + if (frontWindow = (WindowPeek) FrontWindow()) + if (frontWindow->windowKind < 0) + newMenubar = mbarDA; + + if (newMenubar != oldMenubar) + { + /* adjust menus */ + switch (oldMenubar = newMenubar) + { + case mbarAppl: + EnableItem(mHnd[menuFile], mitmOpen); + SetItemMark(mHnd[menuFile], mitmOpen, noMark); + DisableItem(mHnd[menuFile], mitmClose_DA); + DisableItem(mHnd[menuEdit], 0); + break; + + case mbarRecover: + DisableItem(mHnd[menuFile], mitmOpen); + SetItemMark(mHnd[menuFile], mitmOpen, checkMark); + DisableItem(mHnd[menuFile], mitmClose_DA); + DisableItem(mHnd[menuEdit], 0); + break; + + case mbarDA: + DisableItem(mHnd[menuFile], mitmOpen); + EnableItem(mHnd[menuFile], mitmClose_DA); + EnableItem(mHnd[menuEdit], 0); + break; + } + + DrawMenuBar(); + } + + /* now adjust the cursor */ + if (useArrow = (!in.Recover || (newMenubar == mbarDA))) + newCursor = curs_Init; + else if ((timeNow = TickCount()) >= timeCursor) /* spin cursor */ + { + timeCursor = timeNow + CURS_FRAME; + if (++newCursor >= curs_Total) + newCursor = 0; + } + + if (newCursor != oldCursor) + { + oldCursor = newCursor; + + SetCursor(useArrow ? &qd.arrow : cPtr[newCursor]); + } +} + +static void +adjustMemory() +{ + Size grow; + + memActivity = 0; + + if (MaxMem(&grow) < pBytes->memWarning) + note(noErr, alidNote, "\pWarning: Memory is running low"); + + (void) ResrvMem((Size) FreeMem()); /* move all handles high */ +} + +/* show memory stats: FreeMem, MaxBlock, PurgeSpace, and StackSpace */ +static void +optionMemStats() +{ + unsigned char *pFormat = "\pFree:#k Max:#k Purge:#k Stack:#k"; + char *pSub = "#"; /* not a pascal string */ + unsigned char nBuf[16]; + long nStat, contig; + Handle strHnd; + long nOffset; + short i; + + if (wnEvt.modifiers & shiftKey) + adjustMemory(); + + if (! (strHnd = NewHandle((Size) 128))) + { + note(noErr, alidNote, "\pOops: Memory stats unavailable!"); + return; + } + + SetString((StringHandle) strHnd, pFormat); + nOffset = 1L; + + for (i = 1; i <= 4; i++) + { + /* get the replacement number stat */ + switch (i) + { + case 1: nStat = FreeMem(); break; + case 2: nStat = MaxBlock(); break; + case 3: PurgeSpace(&nStat, &contig); break; + case 4: nStat = StackSpace(); break; + } + + NumToString((nStat >> 10), * (Str255 *) &nBuf); + + **strHnd += nBuf[0] - 1; + nOffset = Munger(strHnd, nOffset, (Ptr) pSub, 1L, (Ptr) &nBuf[1], nBuf[0]); + } + + MoveHHi(strHnd); + HLock(strHnd); + note(noErr, alidNote, (unsigned char *) *strHnd); + DisposHandle(strHnd); +} + +static void +RecoverMenuEvent(long menuEntry) +{ + short menuID = HiWord(menuEntry); + short menuItem = LoWord(menuEntry); + + switch (menuID) + { + case muidApple: + switch (menuItem) + { + case mitmAbout: + if (wnEvt.modifiers & optionKey) + optionMemStats(); + /* fall thru */ + case mitmHelp: + note(noErr, (alertAppleMenu + menuItem), aboutBuf); + break; + + default: /* DA's or apple menu items */ + { + unsigned char daName[32]; + + GetItem(mHnd[menuApple], menuItem, * (Str255 *) &daName); + (void) OpenDeskAcc(daName); + + memActivity++; + } + break; + } + break; + + case muidFile: + switch (menuItem) + { + case mitmOpen: + beginRecover(); + break; + + case mitmClose_DA: + { + WindowPeek frontWindow; + short refNum; + + if (frontWindow = (WindowPeek) FrontWindow()) + if ((refNum = frontWindow->windowKind) < 0) + CloseDeskAcc(refNum); + + memActivity++; + } + break; + + case mitmQuit: + cooldown(); + break; + } + break; + + case muidEdit: + (void) SystemEdit(menuItem - 1); + break; + } + + HiliteMenu(0); +} + +static void +eventLoop() +{ + short wneMask = (in.Front ? everyEvent : (osMask + updateMask)); + long wneSleep = (in.Front ? 0L : 3L); + + while (1) + { + if (in.Front) + adjustGUI(); + + if (memActivity >= pBytes->memCleanup) + adjustMemory(); + + (void) WaitNextEvent(wneMask, &wnEvt, wneSleep, (RgnHandle) 0L); + + if (in.Dialog) + (void) IsDialogEvent(&wnEvt); + + switch (wnEvt.what) + { + case osEvt: + if (((wnEvt.message & osEvtMessageMask) >> 24) == suspendResumeMessage) + { + in.Front = (wnEvt.message & resumeFlag); + wneMask = (in.Front ? everyEvent : (osMask + updateMask)); + wneSleep = (in.Front ? 0L : 3L); + } + break; + + case nullEvent: + /* adjust the FIFO notification queue */ + if (pNMQ && pNMQ->nmDispose) + { + notifPtr pNMX = pNMQ->nmNext; + + DisposPtr((Ptr) pNMQ); + pNMQ = pNMX; + + memActivity++; + } + + if (in.Recover) + continueRecover(); + break; + + case mouseDown: + { + WindowPtr whichWindow; + + switch(FindWindow( wnEvt . where , &whichWindow)) + { + case inMenuBar: + RecoverMenuEvent(MenuSelect( wnEvt . where )); + break; + + case inSysWindow: + SystemClick(&wnEvt, whichWindow); + break; + + case inDrag: + { + Rect boundsRect = qd.screenBits.bounds; + Point offsetPt; + + InsetRect(&boundsRect, 4, 4); + boundsRect.top += GetMBarHeight(); + + DragWindow(whichWindow, * ((Point *) &wnEvt.where), &boundsRect); + + boundsRect = whichWindow->portRect; + offsetPt = * (Point *) &(whichWindow->portBits.bounds); + OffsetRect(&boundsRect, -offsetPt.h, -offsetPt.v); + + * (Rect *) *thermoTHnd = boundsRect; + } + break; + } + } + break; + + case keyDown: + { + char key = (wnEvt.message & charCodeMask); + + if (wnEvt.modifiers & cmdKey) + { + if (key == '.') + { + if (in.Recover) + { + endRecover(); + note(noErr, alidNote, "\pSorry: Recovery aborted"); + } + } + else + RecoverMenuEvent(MenuKey(key)); + } + } + break; + + /* without windows these events belong to our thermometer */ + case updateEvt: + case activateEvt: + { + DialogPtr dPtr; + short itemHit; + + (void) DialogSelect(&wnEvt, &dPtr, &itemHit); + } + + case diskEvt: + if (HiWord(wnEvt.message)) + { + Point pt = {60, 60}; + + (void) DIBadMount(pt, wnEvt.message); + DIUnload(); + + memActivity++; + } + break; + } /* switch (wnEvt.what) */ + } /* while (1) */ +} + +static void +cooldown() +{ + if (in.Recover) + endRecover(); + + /* wait for pending notifications to complete */ + while (in.Notify) + (void) WaitNextEvent(0, &wnEvt, 3L, (RgnHandle) 0L); + + ExitToShell(); +} + +/* draw the progress thermometer and frame. 1 level <=> 1 horiz. pixel */ +pascal void +drawThermo(WindowPtr wPtr, short inum) +{ + itemizeThermo(drawItem); +} + +/* manage progress thermometer dialog */ +static void +itemizeThermo(short itemMode) +{ + short iTyp, iTmp; + Handle iHnd; + Rect iRct; + + GetDItem(DLGTHM, uitmThermo, &iTyp, &iHnd, &iRct); + + switch(itemMode) + { + case initItem: + SetDItem(DLGTHM, uitmThermo, iTyp, (Handle) drawThermoUPP, &iRct); + break; + + case invalItem: + { + GrafPtr oldPort; + + GetPort(&oldPort); + SetPort(GRFTHM); + + InsetRect(&iRct, 1, 1); + InvalRect(&iRct); + + SetPort(oldPort); + } + break; + + case drawItem: + FrameRect(&iRct); + InsetRect(&iRct, 1, 1); + + iTmp = iRct.right; + iRct.right = iRct.left + in.Recover; + PaintRect(&iRct); + + iRct.left = iRct.right; + iRct.right = iTmp; + EraseRect(&iRct); + break; + } +} + +/* show only .0 files in get file dialog */ +pascal Boolean +basenameFileFilter(ParmBlkPtr pPB) +{ + unsigned char *pC; + + if (! (pC = (unsigned char *) pPB->fileParam.ioNamePtr)) + return true; + + if ((*pC < 4) || (*pC > 28)) /* save/ 1name .0 */ + return true; + + if ((pC[*pC - 1] == '.') && (pC[*pC] == '0')) /* bingo! */ + return false; + + return true; +} + +static void +beginRecover() +{ + SFTypeList levlType = {'LEVL'}; + SFReply sfGetReply; + + SFGetFile(sfGetWhere, "\p", basenameFileFilterUPP, 1, levlType, + (DlgHookUPP) 0L, &sfGetReply); + + memActivity++; + + if (! sfGetReply.good) + return; + + /* get volume (working directory) refnum, basename, and directory i.d. */ + vRefNum = sfGetReply.vRefNum; + BlockMove(sfGetReply.fName, lock, sfGetReply.fName[0] + 1); + { + static CInfoPBRec catInfo; + + catInfo.hFileInfo.ioNamePtr = (StringPtr) sfGetReply.fName; + catInfo.hFileInfo.ioVRefNum = sfGetReply.vRefNum; + catInfo.hFileInfo.ioDirID = 0L; + + if (PBGetCatInfoSync(&catInfo)) + { + note(noErr, alidNote, "\pSorry: Bad File Info"); + return; + } + + dirID = catInfo.hFileInfo.ioFlParID; + } + + /* open the progress thermometer dialog */ + (void) GetNewDialog(dlogProgress, (Ptr) &dlgThermo, (WindowPtr) -1L); + if (ResError() || MemError()) + note(noErr, alidNote, "\pOops: Progress thermometer unavailable"); + else + { + in.Dialog = 1; + memActivity++; + + itemizeThermo(initItem); + + ShowWindow(WNDTHM); + } + + timeCursor = TickCount() + CURS_LATENT; + saveRefNum = gameRefNum = levRefNum = -1; + in.Recover = 1; +} + +static void +continueRecover() +{ + restore_savefile(); + + /* update the thermometer */ + if (in.Dialog && ! (in.Recover % 4)) + itemizeThermo(invalItem); + + if (in.Recover <= MAX_RECOVER_COUNT) + return; + + endRecover(); + + if (saveRezStrings()) + return; + + note(noErr, alidNote, "\pOK: Recovery succeeded"); +} + +/* no messages from here (since we might be quitting) */ +static void +endRecover() +{ + in.Recover = 0; + + oldCursor = curs_Init; + SetCursor(&qd.arrow); + + /* clean up abandoned files */ + if (gameRefNum >= 0) + (void) FSClose(gameRefNum); + + if (levRefNum >= 0) + (void) FSClose(levRefNum); + + if (saveRefNum >= 0) + { + (void) FSClose(saveRefNum); + (void) FlushVol((StringPtr) 0L, vRefNum); + /* its corrupted so trash it ... */ + (void) HDelete(vRefNum, dirID, savename); + } + + saveRefNum = gameRefNum = levRefNum = -1; + + /* close the progress thermometer dialog */ + in.Dialog = 0; + CloseDialog(DLGTHM); + DisposHandle(dlgThermo.items); + memActivity++; +} + +/* add friendly, non-essential resource strings to save file */ +static short +saveRezStrings() +{ + short sRefNum; + StringHandle strHnd; + short i, rezID; + unsigned char *plName; + + HCreateResFile(vRefNum, dirID, savename); + + sRefNum = HOpenResFile(vRefNum, dirID, savename, fsRdWrPerm); + if (sRefNum <= 0) + { + note(noErr, alidNote, "\pOK: Minor resource map error"); + return 1; + } + + /* savename and hpid get mutilated here... */ + plName = savename + 5; /* save/ */ + *savename -= 5; + do + { + plName++; + (*savename)--; + hpid /= 10; + } + while (hpid); + *plName = *savename; + + for (i = 1; i <= 2; i++) + { + switch (i) + { + case 1: + rezID = PLAYER_NAME_RES_ID; + strHnd = NewString(* (Str255 *) plName); + break; + + case 2: + rezID = APP_NAME_RES_ID; + strHnd = NewString(* (Str255 *) "\pNetHack"); + break; + } + + if (! strHnd) + { + note(noErr, alidNote, "\pOK: Minor \'STR \' resource error"); + CloseResFile(sRefNum); + return 1; + } + + /* should check for errors... */ + AddResource((Handle) strHnd, 'STR ', rezID, * (Str255 *) "\p"); + } + + memActivity++; + + /* should check for errors... */ + CloseResFile(sRefNum); + return 0; +} + +static void +set_levelfile_name(long lev) +{ + unsigned char *tf; + + /* find the dot. this is guaranteed to happen. */ + for (tf = (lock + *lock); *tf != '.'; tf--, lock[0]--) + ; + + /* append the level number string (pascal) */ + if (tf > lock) + { + NumToString(lev, * (Str255 *) tf); + lock[0] += *tf; + *tf = '.'; + } + else /* huh??? */ + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Name Error"); + } +} + +static short +open_levelfile(long lev) +{ + OSErr openErr; + short fRefNum; + + set_levelfile_name(lev); + if (! in.Recover) + return (-1); + + if ((openErr = HOpen(vRefNum, dirID, lock, fsRdWrPerm, &fRefNum)) + && (openErr != fnfErr)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Open Error"); + return (-1); + } + + return (openErr ? -1 : fRefNum); +} + +static short +create_savefile(unsigned char *savename) +{ + short fRefNum; + + /* translate savename to a pascal string (in place) */ + { + unsigned char *pC; + short nameLen; + + for (pC = savename; *pC; pC++); + + nameLen = pC - savename; + + for ( ; pC > savename; pC--) + *pC = *(pC - 1); + + *savename = nameLen; + } + + if (HCreate(vRefNum, dirID, savename, MAC_CREATOR, SAVE_TYPE) + || HOpen(vRefNum, dirID, savename, fsRdWrPerm, &fRefNum)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Create Error"); + return (-1); + } + + return fRefNum; +} + +static void +copy_bytes(short inRefNum, short outRefNum) +{ + char *buf = (char *) pIOBuf; + long bufSiz = pBytes->memIOBuf; + + long nfrom, nto; + + do + { + nfrom = read_levelfile(inRefNum, buf, bufSiz); + if (! in.Recover) + return; + + nto = write_savefile(outRefNum, buf, nfrom); + if (! in.Recover) + return; + + if (nto != nfrom) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Copy Error"); + return; + } + } + while (nfrom == bufSiz); +} + +static void +restore_savefile() +{ + static int savelev; + long saveTemp, lev; + xchar levc; + struct version_info version_data; + + /* level 0 file contains: + * pid of creating process (ignored here) + * level number for current level of save file + * name of save file nethack would have created + * and game state + */ + + lev = in.Recover - 1; + if (lev == 0L) + { + gameRefNum = open_levelfile(0L); + + if (in.Recover) + (void) read_levelfile(gameRefNum, (Ptr) &hpid, sizeof(hpid)); + + if (in.Recover) + saveTemp = read_levelfile(gameRefNum, (Ptr) &savelev, sizeof(savelev)); + + if (in.Recover && (saveTemp != sizeof(savelev))) + { + endRecover(); + note(noErr, alidNote, "\pSorry: \"checkpoint\" was not enabled"); + return; + } + + if (in.Recover) + (void) read_levelfile(gameRefNum, (Ptr) savename, sizeof(savename)); + if (in.Recover) + (void) read_levelfile(gameRefNum, + (Ptr) &version_data, sizeof version_data); + + /* save file should contain: + * current level (including pets) + * (non-level-based) game state + * other levels + */ + if (in.Recover) + saveRefNum = create_savefile(savename); + + if (in.Recover) + levRefNum = open_levelfile(savelev); + + if (in.Recover) + (void) write_savefile(saveRefNum, + (Ptr) &version_data, sizeof version_data); + if (in.Recover) + copy_bytes(levRefNum, saveRefNum); + + if (in.Recover) + close_file(&levRefNum); + + if (in.Recover) + unlink_file(lock); + + if (in.Recover) + copy_bytes(gameRefNum, saveRefNum); + + if (in.Recover) + close_file(&gameRefNum); + + if (in.Recover) + set_levelfile_name(0L); + + if (in.Recover) + unlink_file(lock); + } + else if (lev != savelev) + { + levRefNum = open_levelfile(lev); + if (levRefNum >= 0) + { + /* any or all of these may not exist */ + levc = (xchar) lev; + + (void) write_savefile(saveRefNum, (Ptr) &levc, sizeof(levc)); + + if (in.Recover) + copy_bytes(levRefNum, saveRefNum); + + if (in.Recover) + close_file(&levRefNum); + + if (in.Recover) + unlink_file(lock); + } + } + + if (in.Recover == MAX_RECOVER_COUNT) + close_file(&saveRefNum); + + if (in.Recover) + in.Recover++; +} + +static long +read_levelfile(short rdRefNum, Ptr bufPtr, long count) +{ + OSErr rdErr; + long rdCount = count; + + if ((rdErr = FSRead(rdRefNum, &rdCount, bufPtr)) && (rdErr != eofErr)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Read Error"); + return (-1L); + } + + return rdCount; +} + +static long +write_savefile(short wrRefNum, Ptr bufPtr, long count) +{ + long wrCount = count; + + if (FSWrite(wrRefNum, &wrCount, bufPtr)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Write Error"); + return (-1L); + } + + return wrCount; +} + +static void +close_file(short *pFRefNum) +{ + if (FSClose(*pFRefNum) || FlushVol((StringPtr) 0L, vRefNum)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Close Error"); + return; + } + + *pFRefNum = -1; +} + +static void +unlink_file(unsigned char *filename) +{ + if (HDelete(vRefNum, dirID, filename)) + { + endRecover(); + note(noErr, alidNote, "\pSorry: File Delete Error"); + return; + } +} +