Logo Search packages:      
Sourcecode: jnethack version File versions  Download package

mrecover.c

/*    SCCS Id: @(#)mrecover.c 3.2         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 <Errors.h>
#include <OSUtils.h>
#include <Resources.h>
#include <Files.h>
#ifdef applec
#include <SysEqu.h>
#endif
#include <Menus.h>

#include <Desk.h>
#include <DiskInit.h>
#include <Notification.h>
#include <Packages.h>
#include <Script.h>
#include <StandardFile.h>
#include <ToolUtils.h>
#include <Processes.h>

#ifndef __MWERKS__      /* 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 */

#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 MenuEvent(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);

      /* 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();
}

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
MenuEvent(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:
                              MenuEvent(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
                                    MenuEvent(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) drawThermo, &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 <pid-plname>.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;
      }
}


Generated by  Doxygen 1.6.0   Back to index