home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / GRAPHICS / DAKIT.ZIP / LPFILE.C < prev    next >
Encoding:
C/C++ Source or Header  |  1990-10-24  |  58.0 KB  |  1,929 lines

  1. /*--lpfile.c----------------------------------------------------------
  2.  *
  3.  * Large Page File.  This is a format for dividing a file into 64 KB
  4.  * "large pages" ("lp"s) that can be stored out-of-sequence in the file.
  5.  * That is, an lp can be logically inserted into the file, without
  6.  * having to move all the following data out of the way.
  7.  *
  8.  * Each lp holds one or more "records".  A record is of any length from
  9.  * 0 to almost 64 KB.  The records in the file are sequentially numbered
  10.  * starting with 0.  The records in each lp are a sub-sequence; e.g.,
  11.  * "3 records starting with #6" would be #6,#7,#8.
  12.  * 
  13.  * A DeluxePaint Animation "ANM" ("Anim") file is built on this mechanism,
  14.  * so that as frames change in size, they can be maintained with a minimum
  15.  * of extra file i/o, yet without loss of playback performance.
  16.  * In addition, there is an optional special record which is the delta from
  17.  * the last frame to the first frame, for smooth playback of looping anims.
  18.  */
  19.  
  20. #include "main.h"
  21. #include "anim.h"
  22. #include <fcntl.h>
  23. #include <stdio.h>
  24. #include <io.h>    /* chsize */
  25.  
  26. #define local static
  27.  
  28. extern BOOL animDisk, outputLargePageFile, editting, cycling;
  29. extern WORD curFrame;
  30. extern UWORD animFrames, frameDelay, startFrame, endFrame, waitVerts,
  31.       gapLimitAddr;
  32. extern Box screenBox, animBox, animMove;
  33. extern BITMAP prevFrame, hidbm;
  34.  
  35. extern LONG curpal[MAX_COLORS];
  36. extern void AnimVBlank();
  37.  
  38. #if U_COLORCYCLE
  39. extern Range cycles[MAXNCYCS];
  40.  
  41. #endif
  42.  
  43. /* --------------- forward declarations --------------- */
  44. UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP);
  45. void ReadLargePage(UWORD nLp);
  46. WORD WriteCurLp(void);
  47. void ReadCurLp(void);
  48. UWORD FindLargePageForRecord(register UWORD nRecord);
  49. void WriteAnimFrameAndReadNext(void);
  50.  
  51. /* --------------- --------------- */
  52.  
  53. WORD lpFile = NULL;        /* Keep open so can access again. */
  54. char lpfSuffix[]= "ANM";
  55.  
  56. local Range cycles[MAXNCYCS];
  57.  
  58.     /* Disk i/o sets after filling buffer, screen i/o clears after using. */
  59. UWORD nBuffers = 2;
  60. UWORD useBuffers = 2;
  61.  
  62.  
  63. extern char animName[];
  64.  
  65. local UWORD saveBitmapSeg = NULL;
  66. #define prevFrameSeg    BSEG(&prevFrame)
  67. #define hidbmSeg        BSEG(&hidbm)
  68.  
  69. /* --------------------------------------------------------------------------
  70.  * --------------- Description of "current record". ---------------
  71.  */
  72. typedef struct {
  73.     UWORD curRecord;    /* record being accessed w/i lp. NOTE: ABSOLUTE rec
  74.                          * #, not relative to lp baseRecord. */
  75.     UWORD recAddr;        /* Address of first byte of record, w/i lp buffer. */
  76.     UWORD recBytes;        /* # bytes in record. */
  77.     UWORD bodyAddr;        /* Address of Body of current frame. */
  78.     UWORD bodyBytes;    /* # bytes in Bitmap record's Body. */
  79.     UWORD extraBytes;    /* # bytes in Bitmap record prior to Body.
  80.                          * ID+FLAGS & ExtraData, incl. count & pad.
  81.                          * Used to skip over ExtraData. */
  82.     UWORD extraDataCnt;    /* # bytes of contents of ExtraData. */
  83. } BmRecX;    /* Access to a Bitmap record. */
  84.  
  85. local BmRecX curRecX;    /* Description of "current record". */
  86.  
  87. #define CUR_REC        curRecX.curRecord
  88.  
  89. /* --- These should be "CUR_REC_...", but the names get unreasonably long. */
  90. #define REC_ADDR    curRecX.recAddr
  91. #define REC_BYTES    curRecX.recBytes
  92. #define BODY_ADDR    curRecX.bodyAddr
  93. #define BODY_BYTES    curRecX.bodyBytes
  94. #define EXTRA_BYTES    curRecX.extraBytes
  95. #define EXTRA_DATA_CNT curRecX.extraDataCnt
  96.  
  97. #define DEFAULT_BITMAP_ID_FLAGS    ((0<<8) + 'B')
  98.     /* First byte is 'B'.  On 8086 that's low byte. */
  99.  
  100. /* ----- Functions to manipulate "current record" w/i CUR_LP w/i lp buffer. */
  101. /* TBD: we're not actually using these, but doing the logic where needed. */
  102.  
  103. /* Shift data in buffer so there is room for "n" extraBytes, and set
  104.  * EXTRA_BYTES.  NOTE: this is NOT "extraDataCount" -- it includes
  105.  * ID+FLAGS & extraDataCount in its size.
  106.  */
  107. local void CurRec_SetExtraBytes(UWORD n);
  108.  
  109. /* Shift data in buffer so there is room for "n" bodyBytes, and set
  110.  * BODY_BYTES.
  111.  */
  112. local void CurRec_SetBodyBytes(UWORD n);
  113.  
  114. /* Find & set CUR_REC.  NOTE: "n" is absolute, not relative to baseRecord.
  115.  * This will put new lp in lp buffer if necessary.
  116.  */
  117. local void CurRec_Set(UWORD n);
  118. UWORD NRecord2NFrame(WORD nRecord);
  119.  
  120. /* --------------------------------------------------------------------------
  121.  * --------------- buffering large pages ---------------
  122.  */
  123.  
  124. local UWORD preBufferSeg = NULL;
  125. local UWORD preFrameAddr0 = 0;
  126.  
  127. local UWORD nBuf, nNextBuf = 0, nNextPreBuf = 0, nPreLp = 0, nPreRecord;
  128. local BOOL lastRecordInBuf, curLpIsDirty = FALSE;
  129.  
  130.     /* "lastRecordInBuf is not used during editing, so is not part of
  131.      *    curRecX.
  132.      */
  133. #define LAST_RECORD_IN_BUF    (LP_BASE_REC + LP_N_RECS - 1)
  134.  
  135. /* -------------------- LargePage -------------------- */
  136. #define MAX_LARGE_PAGE    256
  137. typedef struct {
  138.     UWORD baseRecord;    /* First record in lp. */
  139.     UWORD nRecords;        /* # records in lp. */
  140.        /* bit 15 of "nRecords" == "has continuation from previous lp".
  141.         * bit 14 of "nRecords" == "final record continues on next lp".
  142.         * File format thus permits 16383 records/lp.
  143.         * Only MAX_RECORDS_PER_LP is currently supported by the code,
  144.         * to minimize table size in DGROUP in DP Animation.
  145.         * TBD: we're not handling non-zero bits 14&15.
  146.         */
  147.     UWORD nBytes;    /* # bytes of contents, excluding header.
  148.                      * Gap of "64KB - nBytesUsed - headerSize" at end of lp.
  149.                      * headerSize == "8 + 2*nRecords".
  150.                      * Header is followed by record #baseRecord. */
  151. } LP_DESCRIPTOR;
  152. local LP_DESCRIPTOR lpfTable[MAX_LARGE_PAGE];
  153.  
  154. #define LARGE_PAGE_SIZE    0x10000L
  155.  
  156. #define MAX_RECORDS_PER_LP  256    /* TBD: why restrict, other than RAM usage? */
  157. #define LP_TABLE_ELEMENT    UWORD    /* # bytes for each record of curLp. */
  158.                     /* NOTE: lp's header must be accounted for separately.*/
  159.  
  160. local LP_TABLE_ELEMENT lpTable[MAX_RECORDS_PER_LP];
  161. local UWORD lpTableOffset = 0;    /* So can pretend lpTable starts at some
  162.                                  * element other than 0, to fake out
  163.                                  * WriteCurLp. */
  164.  
  165. typedef struct {
  166.     LP_DESCRIPTOR x;    /* Duplicate of lpfTable[CUR_LP].  NOT relied on,
  167.                          * just here to help scavenge damaged files.
  168.                          * "baseRecord" NOT kept up-to-date, because
  169.                          * LP.AddRecord or DeleteRecord changes
  170.                          * "baseRecord" of ALL following lps -- would be
  171.                          * very slow to add one record near the start of a
  172.                          * huge file!  This field is accurate whenever the
  173.                          * lp is written, but is not relied on after that.
  174.                          * It still helps scavenging.  Since "Optimize Anim"
  175.                          * re-writes every lp, this field is accurate if
  176.                          * Optimize after AddRecord/DeleteRecord. */
  177.     UWORD nByteContinuation;    /* ==0.  FUTURE: allows record from
  178.                                  * previous lp to extend on to this lp, so
  179.                                  * don't waste file space. */
  180. } LPageHeaderFixed;
  181.    /* These fields precede the array giving "REC_BYTES" per record. */
  182.  
  183.  
  184. #define LP_HEADER_SIZE(nRecords)    ( sizeof(LPageHeaderFixed) + \
  185.     (nRecords) * sizeof(LP_TABLE_ELEMENT) )
  186.     /* NOTE: if this changes, so must WriteLargePage0(). */
  187.  
  188. #define FIRST_RECORD_N    0        /* Records #d from 0. */
  189. #define LAST_RECORD_N    (totalNRecords-1)
  190.             /* INCLUDES last-to-first delta if present! */
  191.  
  192. UWORD totalNRecords;    /* Total # records in lpFile, including lastDelta. */
  193. UWORD nLps;        /* # large pages in lpFile. */
  194.  
  195.  
  196. /* --------------------------------------------------------------------------
  197.  * --------------- Description of "current lp". ---------------
  198.  * One lp at a time is present in a buffer of just under 64 KB.
  199.  * The contents of this buffer are maintained in the same form as on disk --
  200.  * fixed header info, variable-length recordSizes table, data for records.
  201.  */
  202.  
  203. extern UWORD lpSeg;        /* Segment of lp buffer. */
  204.  
  205. typedef struct {
  206.     UWORD nLp;    /* lp's # w/i file */
  207.     UWORD recAddr0;        /* First byte of first record w/i lp. (after
  208.                          * header). */
  209.     LP_DESCRIPTOR x;    /* Describes lp's contents. */
  210. } LpX;        /* Access to an lp. */
  211.  
  212. local LpX curLpX;        /* Description of "current lp". */
  213.  
  214. #define NO_LP   ((UWORD)-1)
  215. #define CUR_LP    curLpX.nLp
  216.  
  217. /* --- These should be "CUR_LP_...", but the names get unreasonably long. */
  218. #define LP_BASE_REC    curLpX.x.baseRecord
  219. #define LP_N_RECS    curLpX.x.nRecords
  220. #define LP_N_BYTES    curLpX.x.nBytes
  221. #define LP_REC_ADDR0    curLpX.recAddr0
  222.     /* Location of first byte of frame data in lpBuffer. */
  223.  
  224. /* ASSUMES LP_REC_ADDR0 set for correct # records. */
  225. #define LP_TOTAL_BYTES        (LP_REC_ADDR0 + LP_N_BYTES)
  226. #define GAP_SIZE            (MAX_LARGE_PAGE_SIZE - LP_TOTAL_BYTES)
  227.  
  228. /* --------------------------------------------------------------------------
  229.  * -------------------- LargePageFile --------------------
  230.  * Lpf's Header size is 256 + 3 * 256 + 6 * maxLps.
  231.  * When lpf created, maxLps must be chosen.  If file needs more lps,
  232.  * will have to move all data in file.  By convention, set maxLps to
  233.  * 256 initially, increase in multiples of 256 as needed.
  234.  * FOR NOW: WON'T HANDLE FILE UNLESS SET TO 256.
  235.  * 256 * 64 KB == 16 MB.
  236.  */
  237.  
  238. local struct {
  239.     ULONG id;    /* ID for LargePageFile == MakeID('L','P','F',' '). */
  240.     UWORD maxLps;        /* 256 FOR NOW.  max # largePages allowed in this
  241.                          * file. */
  242.     UWORD nLps;    /* # largePages currently in this file.  0..maxLps.
  243.                  * lps #d from 0.
  244.                  * NOTE: In RAM, we don't keep this field up to date. */
  245.     ULONG nRecords;        /* # records currently in this file.  65535 is
  246.                          * current limit.  Allowing one for last-to-first
  247.                          * delta, that restricts to 65534 frames of lo-res
  248.                          * animation.
  249.                          * Records #d from 0.  NOTE: In RAM, we
  250.                          * don't keep this field up to date. */
  251.     UWORD maxRecsPerLp;    /* 256 FOR NOW.  # records permitted in an lp.
  252.                          * So reader knows table size to allocate. */
  253.     UWORD lpfTableOffset;        /* 1280 FOR NOW.  Absolute Seek position of
  254.                                  * lpfTable. This allows content-specific
  255.                                  * header to be variable-size in the
  256.                                  * future. */
  257.     ULONG contentType;    /* for ANIM == MakeID('A','N','I','M').  All
  258.                          * following info is specific to ANIM format. */
  259.     UWORD width;        /* in pixels. */
  260.     UWORD height;        /* in pixels. */
  261.     UBYTE variant;        /* 0==ANIM. */
  262.     UBYTE version;        /* 0== cycles stored for 18x/sec timer.
  263.                          * 1== cycles stored for 70x/sec timer. */
  264.     BOOL hasLastDelta;    /* 1==Record(s) representing final frame are a
  265.                          * delta from last-to-first frame.  nFrames ==
  266.                          * (nRecords/recordsPerFrame)-hasLastDelta. */
  267.     BOOL lastDeltaValid;        /* So needn't actually remove delta.
  268.                                  * (When hasLastDelta==1)  0==there is a last
  269.                                  * delta, but it hasn't been updated to
  270.                                  * match the current first&last frames, so
  271.                                  * it should be ignored. This is done so
  272.                                  * don't have to physically remove the
  273.                                  * delta from the file when the first frame
  274.                                  * changes. */
  275.     UBYTE pixelType;    /* ==0 for 256-color. */
  276.     UBYTE highestBBComp;        /* 1 (RunSkipDump) FOR NOW. highest #d
  277.                                  * "Record Bitmap.Body.compressionType".
  278.                                  * So reader can determine whether it can
  279.                                  * handle this Anim. */
  280.     UBYTE otherRecordsPerFrame;    /* 0 FOR NOW. */
  281.     UBYTE bitmapRecordsPerFrame;/* ==1 for 320x200,256-color. */
  282.     UBYTE recordTypes[32];        /* Not yet implemented. */
  283.     ULONG nFrames;        /* In case future version adds other records at end
  284.                          * of file, we still know how many frames. NOTE:
  285.                          * DOES include last-to-first delta when present.
  286.                          * NOTE: In RAM, we don't keep
  287.                          * this field up to date. */
  288.     UWORD framesPerSecond;        /* # frames to play per second. */
  289.     UWORD pad2[29];
  290. #if 0    /* read/write this directly, no need to waste DGROUP */
  291.     Range cycles[MAXNCYCS];
  292. #endif
  293. } lpfHdr;    /* total is 128 words == 256 bytes. */
  294.  
  295. #if CCYCLE70
  296. #define LPF_VERSION        1        /* cycles stored for 70x/sec timer. */
  297. #else
  298. #define LPF_VERSION        0        /* cycles stored for 18x/sec timer. */
  299. #endif
  300.  
  301. #if 0
  302. UWORD checkFrames[32];    /* frame #s to checkpoint.  each ==0 when not used.
  303.                          * # non-zero * recsPerFrame == # records used for
  304.                          * this purpose.  Must subtract from nRecords to
  305.                          * determine how many frames are really in file.
  306.                          * TBD: Allocate fixed record #s for these? TBD:
  307.                          * force to be one record per lp? NOTE: since frame
  308.                          * #s, not record #s, seems okay to restrict to
  309.                          * 16-bit.  If lo-res file gets > 64 K frames,
  310.                          * scale #s so can always span # frames. Ex: if
  311.                          * 64K..127K frames, these #s are "*2". Recommend:
  312.                          * # checkpoints proportional to # lps. Keep 1/8th
  313.                          * anim-size in checkpoints. Every time file
  314.                          * doubles in size, double # checks. */
  315.  
  316. #endif
  317.  
  318. #define LARGE_PAGE_FILE_ID        MakeID('L','P','F',' ')
  319. #define ANIM_CONTENTS_ID        MakeID('A','N','I','M')
  320.  
  321. #define LPF_HEADER_HEAD_SIZE_IN_FILE    256
  322.     /* First few fields at start of a Large Page File. */
  323.  
  324. #define SIZEOF_LPF_TABLE_IN_FILE    (sizeof(LP_DESCRIPTOR) * MAX_LARGE_PAGE)
  325.     /* FUTURE: will be permitted to vary! */
  326.  
  327. #define LPF_HEADER_SIZE_IN_FILE        ( LPF_HEADER_HEAD_SIZE_IN_FILE + \
  328.                          PALETTE_SIZE + SIZEOF_LPF_TABLE_IN_FILE )
  329.  /* Everything in Large Page File before the first lp. */
  330.  
  331. #define LPF_TABLE_OFFSET    (LPF_HEADER_HEAD_SIZE_IN_FILE + PALETTE_SIZE)
  332. #define BBC_RUN_SKIP_DUMP    1
  333.  
  334.  
  335. /* ----------------------------------------------------------------------- */
  336. /* TBD: If you want better error handling, implement these. */
  337. #define DiskFullChangesLostExit()    exit(1)
  338. #define CantCreateAnimFileExit()    exit(1)
  339. #define InvalidAnimFileExit()    exit(1)
  340. #define TooManyPagesExit()    exit(1)
  341. #define BadBitmapRecordExit()    exit(1)
  342. #define UnrecognizedDeltaTypeExit()    exit(1)
  343.  
  344. #define LPageTooLargeMsg()
  345. #define LpfTooLargeMsg()
  346. #define TroubleWritingFileMsg()
  347. #define LpfNotHandledByVersion()
  348. #define TroubleReadingFileMsg()
  349. #define NotAnAnimFileMsg()
  350.  
  351.  
  352. /* ----------------------------------------------------------------------- */
  353.  
  354. /* --------------- GetFramesPerSecond ---------------
  355.  */
  356. UWORD GetFramesPerSecond()
  357. {
  358.     return lpfHdr.framesPerSecond;
  359. }
  360.  
  361.  
  362. /* --------------- DeclareFramesPerSecond ---------------
  363.  * Make sure all parties know what the (new) framesPerSecond play-rate is.
  364.  */
  365. DeclareFramesPerSecond(UWORD newFPS)
  366. {
  367.     if (newFPS == 0)
  368.         newFPS = FASTEST_RATE;
  369.     lpfHdr.framesPerSecond = newFPS;
  370. }
  371.  
  372.  
  373. /* --------------- AppendSuffix ---------------
  374.  */
  375. char sDot[] = ".";
  376.  
  377. void AppendSuffix(char *name, char *baseName, char *suffix)
  378. {
  379.     strcpy(name, baseName);
  380.     strcat(name, sDot);
  381.     strcat(name, suffix);
  382. }
  383.  
  384.  
  385. /* --------------- OpenFile ---------------
  386.  */
  387. WORD OpenFile(char *baseName, char *suffix)
  388. {
  389.     WORD file;
  390.     char name[80];
  391.  
  392.     AppendSuffix(name, baseName, suffix);
  393.  
  394.     file = opendos(name, O_RDWR);
  395.     if (file == -1)
  396.         file = NULL;            /* No file open. */
  397.     return file;
  398. }
  399.  
  400.  
  401. /* --------------- DeleteFile ---------------
  402.  */
  403.  
  404. void DeleteFile(char *baseName, char *suffix)
  405. {
  406.     char name[80];
  407.  
  408.     AppendSuffix(name, baseName, suffix);
  409.     deletedos(name);
  410. }
  411.  
  412.  
  413. /* --------------- AnimFramesX ---------------
  414.  * Set "animFrames" to new value.
  415.  * This is a low-level routine.
  416.  */
  417. void AnimFramesX(WORD nFrames)
  418. {
  419.     animFrames = nFrames;
  420. }
  421.  
  422.  
  423. /* --------------- OpenLpFile ---------------
  424.  */
  425. BOOL OpenLpFile()
  426. {
  427.     register UWORD i;
  428.  
  429.     lpFile = OpenFile(animName, lpfSuffix);
  430.  
  431.     if (!lpFile)
  432.         return FAIL;
  433.  
  434.             /* --- Read lpfHdr. */
  435.     CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET));
  436.     CHECK_DOS(readfull(lpFile, (char *)&lpfHdr, sizeof(lpfHdr)));
  437.  
  438.         /* --- read cycle info */
  439.     CHECK_DOS(readfull(lpFile, (char *)cycles, sizeof(cycles)));
  440.  
  441.     /* --- Read palette, after skipping future-expansion of lpfHdr.
  442.      * ASSUME PALETTE_SIZE == 3*256.
  443.      */
  444.     CHECK_DOS(lseekdos(lpFile, (LONG) LPF_HEADER_HEAD_SIZE_IN_FILE, SEEK_SET));
  445.     CHECK_DOS(readfull(lpFile, (char *)curpal, PALETTE_SIZE));
  446.  
  447.     /* --- Read lpfTable, immediately after palette. */
  448.     CHECK_DOS(readfull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE));
  449.  
  450.         /* --- Check that it is a DPaint-Animate Anim. */
  451.     if (lpfHdr.id != LARGE_PAGE_FILE_ID ||
  452.         lpfHdr.contentType != ANIM_CONTENTS_ID) {
  453.         NotAnAnimFileMsg();
  454.         goto badFile;
  455.     }
  456.     if (lpfHdr.maxLps != MAX_LARGE_PAGE ||
  457.         lpfHdr.nRecords > (ULONG) MAX_RECORDS) {
  458.         LpfTooLargeMsg();
  459.         goto badFile;
  460.     }
  461.     /* Note: maxLps may be too SMALL as well. */
  462.  
  463.     if (lpfHdr.maxRecsPerLp > MAX_RECORDS_PER_LP) {
  464.         LPageTooLargeMsg();
  465.         goto badFile;
  466.     }
  467.         /* --- Check that it is an Anim type we can handle. */
  468.     if (lpfHdr.lpfTableOffset != LPF_TABLE_OFFSET ||
  469.         lpfHdr.width != 320 ||
  470.         lpfHdr.height != 200 || lpfHdr.highestBBComp > BBC_RUN_SKIP_DUMP ||
  471.         lpfHdr.bitmapRecordsPerFrame != 1 || lpfHdr.otherRecordsPerFrame ||
  472.         lpfHdr.pixelType) {
  473.         LpfNotHandledByVersion();
  474.         goto badFile;
  475.     }
  476.     for (i = 0; i < lpfHdr.nLps; i++) {
  477.         if (lpfTable[i].nRecords > MAX_RECORDS_PER_LP ||
  478.             lpfTable[i].nBytes > MAX_LARGE_PAGE_SIZE) {
  479.             LPageTooLargeMsg();
  480.             goto badFile;
  481.         }
  482.         /* FUTURE: hi 2 bits of nRecords indicate continuation. */
  483.     }
  484.  
  485.     nLps = lpfHdr.nLps;            /* TBD: why not use fields in place? */
  486.     totalNRecords = (UWORD) lpfHdr.nRecords;
  487.  
  488.     AnimFramesX(lpfHdr.nFrames - lpfHdr.hasLastDelta);
  489.     DeclareFramesPerSecond(lpfHdr.framesPerSecond);
  490.  
  491.     CUR_LP = NO_LP;                /* None loaded. */
  492. /*    ReadLpfFrame(FIRST_FRAME_N);    --- So have valid, for InsureBuffer...*/
  493.     return SUCCESS;
  494.  
  495. doserr:
  496.     TroubleReadingFileMsg();
  497. badFile:
  498.     closedos(lpFile);
  499.     lpFile = NULL;
  500.     InvalidAnimFileExit();
  501.     return FAIL;
  502. }
  503.  
  504.  
  505. /* --------------- SeekLargePage ---------------
  506.  * Seek lp file to the specified large page.
  507.  * Return FALSE on error.
  508.  */
  509. #define LP_FILE_OFFSET(nLp)    \
  510.             (LONG)((LONG)(nLp) * LARGE_PAGE_SIZE + LPF_HEADER_SIZE_IN_FILE)
  511.  
  512. local LONG SeekLargePage(UWORD nLp)
  513. {
  514.     if (nLp > MAX_LARGE_PAGE)
  515.         BugExit("LP1");    /* "Invalid lp #"); */
  516.  
  517.     /* Seek to lp.  File has header, then lps #d from 0. */
  518.     return (lseekdos(lpFile, LP_FILE_OFFSET(nLp), SEEK_SET));
  519. }
  520.  
  521.  
  522. /* --------------- ReadLargePage00 ---------------
  523.  * Also reads HEADER into lpBuf, for read-ahead.
  524.  * When editing, don't want this.
  525.  * IMPLICIT PARAMETERS: preFrameAddr0, lpfTable.
  526.  */
  527. local ReadLargePage00(UWORD lpFile, WORD nLp, UWORD seg)
  528. {
  529.     UWORD nBytes;
  530.  
  531.     preFrameAddr0 = LP_HEADER_SIZE(lpfTable[nLp].nRecords);
  532.  
  533.     nBytes = preFrameAddr0 + lpfTable[nLp].nBytes;
  534.     if (readdos(lpFile, seg, 0, nBytes) != nBytes)
  535.         BugExit("LP2");            /* "ReadLargePage00: nBytes"); */
  536. }
  537.  
  538.  
  539. /* --------------- MaybeWriteCurLp ---------------
  540.  */
  541. local void MaybeWriteCurLp(void)
  542. {
  543.     if (curLpIsDirty) {
  544.         WriteCurLp();
  545.     }
  546. }
  547.  
  548.  
  549. /* --------------- ReadLargePage ---------------
  550.  */
  551. void ReadLargePage(UWORD nLp)
  552. {
  553.     MaybeWriteCurLp();
  554.     SeekLargePage(nLp);
  555.     ReadLargePage00(lpFile, nLp, lpSeg);
  556.  
  557.     curLpX.x = lpfTable[nLp];    /* LP_BASE_REC, LP_N_RECS, LP_N_BYTES */
  558.  
  559.     LP_REC_ADDR0 = LP_HEADER_SIZE(LP_N_RECS);
  560.  
  561. /*    far_movmem( lpSeg, 0,  dataseg(), (UWORD)&LP_N_BYTES,    2 ); */
  562. /*  "baseRecord" field ignored. */
  563. /*    far_movmem( lpSeg, 4,  dataseg(), (UWORD)&LP_N_RECS,  2 ); */
  564. /*  "nByteContinuation" field not used. */
  565.  
  566.     far_movmem(lpSeg, sizeof(LPageHeaderFixed),
  567.                dataseg(), (UWORD) lpTable,
  568.                LP_N_RECS * sizeof(LP_TABLE_ELEMENT));
  569.  
  570.     CUR_LP = nLp;
  571. }
  572.  
  573.  
  574. /* --------------- NFrame2NRecord ---------------
  575.  * Given a frame #, compute the record # in the lpFile.
  576.  * ASSUME has a last-delta, which is the last record.
  577.  */
  578. local UWORD NFrame2NRecord(WORD nFrame)
  579. {
  580.     if (nFrame == LAST_DELTA_N)
  581.         return FIRST_RECORD_N;
  582.     return (nFrame - FIRST_FRAME_N + FIRST_RECORD_N);
  583. }
  584.  
  585.  
  586. /* --------------- NRecord2NFrame ---------------
  587.  * Given an lpfile record #, compute the frame #.
  588.  * ASSUME records are #d from 0.
  589.  */
  590. UWORD NRecord2NFrame(WORD nRecord)
  591. {
  592.     return nRecord + FIRST_FRAME_N;
  593. }
  594.  
  595.  
  596. /* --------------- FindLargePageForRecord ---------------
  597.  * Step through lp descriptions, looking for one that contains
  598.  * requested record.
  599.  * RETURN lp #.
  600.  * NOTE: it is possible for lpfTable[nLp].baseRecord to be non-zero
  601.  * even on an empty page.  Fortunately, the "baseRecord <= nRecord < ..."
  602.  * tests will always reject pages with nRecords==0.
  603.  */
  604. local UWORD FindLargePageForRecord(register UWORD nRecord)
  605. {
  606.     register UWORD nLp, baseRecord;
  607.  
  608.     for (nLp = 0; nLp < nLps; nLp++) {
  609.         baseRecord = lpfTable[nLp].baseRecord;
  610.         if (baseRecord <= nRecord &&
  611.             nRecord < baseRecord + lpfTable[nLp].nRecords) {
  612. #if DEBUG_BUFS
  613.             if (!editting)
  614.                 printf("Find lp:%d for rec:%d\n", nLp, nRecord);        /* TESTING */
  615. #endif
  616.             return nLp;
  617.         }
  618.     }
  619.     BugExit("LP6");                /* "Didn't find record in lpfTable"); */
  620. }
  621.  
  622.  
  623. /* --------------- ReadLargePageForRecord ---------------
  624.  * Find lp containing record, then read it.
  625.  */
  626. local void ReadLargePageForRecord(register UWORD nRecord)
  627. {
  628.     ReadLargePage(FindLargePageForRecord(nRecord));
  629. #if 0
  630.     if (FALSE) {
  631.         PrepExit();
  632.         printf("First ReadLargePageForRecord (%d) on playback.", nRecord);
  633.  
  634.         PrintLPFState();
  635.         exit(1);
  636.     }
  637. #endif    /* 0 */
  638. }
  639.  
  640.  
  641. /* --------------- ReadLpfFrame ---------------
  642.  * Transfer to temp buffer the specified delta-frame.
  643.  */
  644. ReadLpfFrame(WORD nFrame)
  645. {
  646.     register UWORD nRecord;
  647.  
  648.     nRecord = NFrame2NRecord(nFrame);
  649.  
  650.     if ((CUR_LP >= nLps) ||        /* No lp yet. */
  651.         (nRecord < LP_BASE_REC) ||        /* record not in CUR_LP. */
  652.         (nRecord >= LP_BASE_REC + LP_N_RECS)) {
  653.         if (nRecord >= totalNRecords)
  654.             BugExit("LP7");        /* "Impossible lp record."); */
  655.         ReadLargePageForRecord(nRecord);
  656.     }
  657.  
  658.     /* --- Step through records in CUR_LP. */
  659.     REC_ADDR = LP_REC_ADDR0;
  660.     CUR_REC = LP_BASE_REC;
  661.     while (nRecord > CUR_REC) {
  662.         REC_ADDR += lpTable[CUR_REC - LP_BASE_REC];
  663.         CUR_REC++;
  664.     }
  665.     REC_BYTES = lpTable[CUR_REC - LP_BASE_REC];
  666.  
  667.     if (REC_BYTES) {
  668.         UWORD far *ptr;
  669.         UBYTE bitmapId, bitmapFlags;
  670.  
  671.         ptr = WORD_FAR_PTR(lpSeg, REC_ADDR);
  672.         bitmapId = ((UBYTE far *) ptr)[0];
  673.         if (bitmapId != 'B')
  674. #if 0    /* TESTING */
  675.         {
  676.             PrepExit();
  677.             printf("BadB frame:%d id:%x exN:%d recN:%d bodyN:%d, addrs:%x %x, rec data:%x %x %x\n",
  678.                    nFrame, bitmapId, EXTRA_BYTES,
  679.                    REC_BYTES, BODY_BYTES, REC_ADDR, BODY_ADDR, ptr[0], ptr[1], ptr[2]);
  680.             exit(1);
  681.         }
  682. #else
  683.             BadBitmapRecordExit();
  684. #endif
  685.         bitmapFlags = ((UBYTE far *) ptr)[1];
  686.         EXTRA_DATA_CNT = ptr[1];
  687.         EXTRA_BYTES = (bitmapFlags ? 4 + EVEN_UP(EXTRA_DATA_CNT) : 2);
  688.     } else {
  689.         EXTRA_BYTES = 0;        /* No recData, therefore no extra data. */
  690.     }
  691.  
  692.     BODY_ADDR = REC_ADDR + EXTRA_BYTES;
  693.     BODY_BYTES = REC_BYTES - EXTRA_BYTES;
  694.  
  695.     lastRecordInBuf = (CUR_REC == LAST_RECORD_IN_BUF);
  696. }
  697.  
  698.  
  699. /* --------------- PlayDeltaFrame0 ---------------
  700.  * Play the current delta info onto the specified frame bitmap.
  701.  * IMPLICIT INPUT: curRecX, lpSeg.
  702.  * NOTE: The 0 BODY_BYTES case isn't handled at this level.
  703.  */
  704. #define PRSD_ASM    1    /* Play RunSkipDump in assembly. */
  705. local void PlayDeltaFrame0(UWORD bmSeg)
  706. {
  707. #if 1
  708.     UWORD bodyType, nBytes;
  709.  
  710. #else
  711.     register UWORD deltaAddr;
  712.     UWORD picSeg;
  713.     LONG frameType;
  714.  
  715. #endif
  716.  
  717.     if (!BODY_BYTES)
  718.         return;                    /* empty body == no change */
  719.  
  720.     if (BODY_BYTES < 2)
  721.         BugExit("LP8");
  722.             /* "Short body of bitmap record");
  723.              * TBD: return error code
  724.              */
  725.  
  726.                 /* --- runSkipDump or uncompressed */
  727. #define BODY_HEADER_LEN        sizeof(UWORD)
  728.                 /* UWORD holding body type precedes body data. */
  729.     bodyType = *WORD_FAR_PTR(lpSeg, BODY_ADDR);
  730.     nBytes = BODY_BYTES - 2;
  731.     if (bodyType == BMBODY_UNCOMPRESSED) {
  732.         /* --- Direct dump of screen. */
  733.         if (nBytes != N_BYTES_IN_BITMAP)
  734.             BugExit("LP9");        /* "Uncompressed body has wrong # bytes"); */
  735.         far_movmem(lpSeg, BODY_ADDR + BODY_HEADER_LEN,
  736.                    bmSeg, 0, nBytes);
  737.     } else if (bodyType == BMBODY_RUNSKIPDUMP) {        /* --- RunSkipDump
  738.                                                          * encoding of screen. */
  739. #if PRSD_ASM    /* RunSkipDump in assembly. */
  740.         UWORD dstAddr;
  741.         UBYTE far *srcP;
  742.  
  743. #else    /* RunSkipDump in C. */
  744.         register signed char cnt;
  745.         UBYTE pixel;
  746.         UWORD wordCnt;
  747.         UBYTE far *srcP, far * dstP;
  748.  
  749. #endif
  750.  
  751.         if (!nBytes)
  752.             return;                /* empty delta == no change */
  753. #define BMBODY_STOP_CODE_LEN    3
  754. #define BBSTOP0    LONG_OP
  755. #define BBSTOP1    0
  756. #define BBSTOP2    0
  757.         srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_BYTES - BMBODY_STOP_CODE_LEN);
  758.         if (nBytes < BMBODY_STOP_CODE_LEN || srcP[0] != BBSTOP0 ||
  759.             srcP[1] != BBSTOP1 || srcP[2] != BBSTOP2)
  760.             BugExit("LPA");        /* "RunSkipDump body missing stop code"); */
  761.  
  762. #if PRSD_ASM    /* RunSkipDump in assembly. */
  763.         dstAddr = PlayRunSkipDump(nBytes, lpSeg, BODY_ADDR + BODY_HEADER_LEN,
  764.                                   bmSeg, 0);
  765.         if (dstAddr > N_BYTES_IN_BITMAP)
  766. #else    /* RunSkipDump in C. */
  767.         /* --- Here is an algorithm to playback a 256-color Body,
  768.          * onto a same-size screen with rows in sequence,
  769.          * so don't have to test each sequence to see
  770.          * if it is on a new row.
  771.          * NOTE: Once it is working, you may remove all the references to
  772.          * "nBytes", as the stop code is sufficient to end processing.
  773.          */
  774.         srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_HEADER_LEN);
  775.         dstP = BYTE_FAR_PTR(bmSeg, 0);
  776.           /* srcP points at first sequence in Body,
  777.            * dstP points at pixel #0 on screen.
  778.            */
  779. nextOp:
  780.         if (nBytes < 3)
  781.             BugExit("LPB");        /* "RunSkipDump body elided stop code"); */
  782.         cnt = (signed char) *srcP++;
  783.         if (cnt > 0)
  784.             goto dump;
  785.         if (cnt == 0)
  786.             goto run;
  787.         cnt -= 0x80;
  788.         if (cnt == 0)
  789.             goto longOp;
  790. /* shortSkip */
  791.         nBytes -= 1;            /* length of shortSkip code */
  792.         dstP += cnt;            /* adding 7-bit count to 32-bit pointer */
  793.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  794.             goto stop;
  795.         goto nextOp;
  796. dump:
  797.         nBytes -= 1 + cnt;        /* length of dump code & data */
  798.         /* ASM:  ds:si=srcP, es:di=dstP, cx=cnt, then do "rep movsb". */
  799.         /* Equivalent:  "do {  *dstP++ = *srcP++;  } while (--cnt);" */
  800.         far_movmem_ptrs(srcP, dstP, cnt);
  801.         dstP += cnt;
  802.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  803.             goto stop;
  804.         srcP += cnt;
  805.         goto nextOp;
  806. run:
  807.         wordCnt = (UBYTE) * srcP++;        /* 8-bit unsigned count */
  808.         pixel = *srcP++;
  809.         nBytes -= 3;            /* length of run code & data */
  810.         /* ASM:  es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */
  811.         /* Equivalent:  "do {  *dstP++ = pixel; } while (--wordCnt);" */
  812.         far_setmem_ptr(dstP, wordCnt, pixel);
  813.         dstP += wordCnt;
  814.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  815.             goto stop;
  816.         goto nextOp;
  817. longOp:
  818.         wordCnt = *((UWORD far *)srcP)++;
  819.         if ((WORD)wordCnt <= 0)
  820.             goto notLongSkip;    /* Do SIGNED test. */
  821.  
  822. /* longSkip. */
  823.         nBytes -= 3;
  824.         dstP += wordCnt;
  825.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  826.             goto stop;
  827.         goto nextOp;
  828.  
  829. notLongSkip:
  830.         if (wordCnt == 0)
  831.             goto stop;
  832.         wordCnt -= 0x8000;        /* Remove sign bit. */
  833.         if (wordCnt >= 0x4000)
  834.             goto longRun;
  835.  
  836. /* longDump. */
  837.         nBytes -= 3 + wordCnt;
  838.         /* ASM:  ds:si=srcP, es:di=dstP, cx=wordCnt, then do "rep movsb". */
  839.         /* Equivalent:  "do {  *dstP++ = *srcP++;  } while (--wordCnt);" */
  840.         far_movmem_ptrs(srcP, dstP, wordCnt);
  841.         dstP += wordCnt;
  842.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  843.             goto stop;
  844.         srcP += wordCnt;
  845.         goto nextOp;
  846.  
  847. longRun:
  848.         wordCnt -= 0x4000;        /* Clear "longRun" bit. */
  849.         pixel = *srcP++;
  850.         nBytes -= 4;
  851.         /* ASM:  es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */
  852.         /* Equivalent:  "do {  *dstP++ = pixel; } while (--wordCnt);" */
  853.         far_setmem_ptr(dstP, wordCnt, pixel);
  854.         dstP += wordCnt;
  855.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  856.             goto stop;
  857.         goto nextOp;
  858.  
  859. stop:
  860. /* all done */
  861.         if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
  862. #endif    /* RunSkipDump in C. */
  863.             BugExit("LPC");
  864.               /* "Wrong amount of screen data generated from RunSkipDump
  865.                *    body");
  866.                */
  867.     }
  868.     else {                        /* TBD: Should be Info Msg, not Exit. */
  869.         UnrecognizedDeltaTypeExit();
  870.     }
  871. }
  872.  
  873.  
  874. /* --------------- PlayDeltaFrame ---------------
  875.  * Play the current delta info onto the specified frame bitmap.
  876.  */
  877. PlayDeltaFrame(BITMAP * bm)
  878. {
  879.     if (BODY_BYTES == 0)
  880.         return;                    /* "0 bytes" means "no change". */
  881.     PlayDeltaFrame0(BSEG(bm));
  882. }
  883.  
  884.  
  885. /* ----------------------------------------------------------------------- */
  886. BOOL permitScreenSwap = TRUE;
  887.  
  888. /* --------------- CreateFile ---------------
  889.  */
  890. WORD CreateFile(char *baseName, char *suffix)
  891. {
  892.     WORD file;
  893.     char name[80];
  894.  
  895.     AppendSuffix(name, baseName, suffix);
  896.     file = creatdos(name, O_RDWR);
  897.     if (file == -1)
  898.         file = NULL;            /* No file open. */
  899.     return file;
  900. }
  901.  
  902.  
  903. /* --------------- CloseFile ---------------
  904.  * Once i/o is buffered, this will flush & release buffer.
  905.  */
  906. void CloseFile(WORD file)
  907. {
  908.     if (file && file != -1)
  909.         closedos(file);
  910.             /* NOTE: we use "NULL" to indicate non-open file. */
  911. }
  912.  
  913.  
  914. /* --------------- WriteN ---------------
  915.  * Write near data, and return FALSE on error.
  916.  */
  917. BOOL WriteN(UWORD file, BYTE *data, UWORD cnt)
  918. {
  919.     return (cnt == writedos(file, dataseg(), data, cnt));
  920. }
  921.  
  922.  
  923. /* --------------- WriteFar ---------------
  924.  * Write far data, and return FALSE on error.
  925.  */
  926. BOOL WriteFar(UWORD file, WORD far * data, UWORD cnt)
  927. {
  928.     return (cnt == writedos(file, FP_SEG(data), FP_OFF(data), cnt));
  929. }
  930.  
  931.  
  932. /* --------------- ReadN ---------------
  933.  * Read near data, and return FALSE on error.
  934.  */
  935. BOOL ReadN(UWORD file, BYTE * data, UWORD cnt)
  936. {
  937.     return (cnt == read(file, data, cnt));
  938. }
  939.  
  940.  
  941. /* --------------- ReadFar ---------------
  942.  * Read far data, and return FALSE on error.
  943.  */
  944. BOOL ReadFar(UWORD file, WORD far * data, UWORD cnt)
  945. {
  946.     return (cnt == readdos(file, FP_SEG(data), FP_OFF(data), cnt));
  947. }
  948.  
  949.  
  950. /* --------------- CreateLpFile0 ---------------
  951.  * Create file with one frame.
  952.  * If "blank", the first frame is blank, otherwise it is the
  953.  * contents of "hidbm".
  954.  */
  955. BOOL animOptimized;        /* TRUE when anim is packed for optimal playback. */
  956. BOOL cleanGap = FALSE;    /* TRUE to put nulls in gap when write lp. */
  957.  
  958. void CreateLpFile0(BOOL blank)
  959. {
  960.     lpFile = CreateFile(animName, lpfSuffix);
  961.     if (!lpFile)
  962.         CantCreateAnimFileExit();
  963.  
  964.     setmem(&lpfHdr, sizeof(lpfHdr), 0);    /* BEFORE set any fields. */
  965. #if 0    /* ...done by setmem... */
  966.     lpfHdr.variant = lpfHdr.hasLastDelta =
  967.         lpfHdr.lastDeltaValid = lpfHdr.pixelType =
  968.         lpfHdr.otherRecordsPerFrame = 0;
  969. #endif
  970.     lpfHdr.version = LPF_VERSION;
  971.  
  972.         /* TBD: Whenever create new anim, starts at a specified rate.
  973.          * Should this instead be done when program boots, then use whatever
  974.          * gets loaded?
  975.          * Hard to say, since can either be set by user,
  976.          * or by loading an anim (in which case user may not realize it
  977.          *    changed).
  978.          */
  979.        /* TBD: Boot, or NewAnim, now creates at 10 fps.  Good choice? */
  980.     DeclareFramesPerSecond(10 /* FASTEST_RATE */ );
  981.  
  982.     lpfHdr.id = LARGE_PAGE_FILE_ID;
  983.     lpfHdr.maxLps = MAX_LARGE_PAGE;
  984.     lpfHdr.maxRecsPerLp = MAX_RECORDS_PER_LP;
  985. /*    lpfHdr.nLps & .nRecords are over-ridden by independent variables. */
  986.     lpfHdr.lpfTableOffset = LPF_TABLE_OFFSET;
  987.     lpfHdr.contentType = ANIM_CONTENTS_ID;
  988.     lpfHdr.width = 320;
  989.     lpfHdr.height = 200;
  990.     lpfHdr.bitmapRecordsPerFrame = 1;
  991.     lpfHdr.highestBBComp = BBC_RUN_SKIP_DUMP;
  992.  
  993.     AnimFramesX(totalNRecords = 1);        /* # records in whole lpFile */
  994.     lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta;
  995.     nLps = 1;
  996.     CUR_LP = 0;
  997.  
  998.     CUR_REC = LP_BASE_REC = 0;
  999.     LP_N_RECS = 1;                /* # records in current large page */
  1000.     LP_N_BYTES = 0;
  1001.     lpTable[0] = 0;                /* A zero-byte page description. */
  1002.         /* (Since it is first frame, zero-bytes means all-white). */
  1003.  
  1004.     LP_REC_ADDR0 = 0;            /* No header info before frame data in
  1005.                                  * buffer seg. */
  1006. #if 1
  1007.     /* --- Hack: Force zero-size first frame to be re-computed as
  1008.      * a series of all-white runs, since the player no longer sets bitmap
  1009.      * to white before first frame.
  1010.      * THEN, say the anim is not dirty, so won't save it unless some
  1011.      * change is made.  This avoids asking user to save the empty
  1012.      * untitled anim, when he boots DP-A, then does LoadAnim.
  1013.      * !!! Is ugly to be calling these routines from inside here. !!!
  1014.      */
  1015.     ReadLpfFrame(FIRST_FRAME_N);
  1016.     if (blank) {
  1017.         SetBitMap(&hidbm, white_color);        /* Clear what will be frame 0. */
  1018.         FirstAnimFrame0();
  1019.         WriteAnimFrameAndReadNext();
  1020.     } else {                    /* Use "hidbm" as first frame's contents. */
  1021.         FirstAnimFrame00();
  1022.         WriteAnimFrameAndReadNext();
  1023.     }
  1024.     animOptimized = TRUE;
  1025. #endif
  1026.  
  1027.     WriteCurLp();                /* Make sure disk is up-to-date. */
  1028.     ReadCurLp();                /* Overkill.  Makes sure buffer & tables
  1029.                                  * consistent. */
  1030.     ReadLpfFrame(FIRST_FRAME_N);
  1031.         /* Adjust BODY_ADDR, etc., for correct header size. */
  1032. }
  1033.  
  1034.  
  1035. /* --------------- CreateLpFile ---------------
  1036.  * Create file, with first frame blank.
  1037.  */
  1038. void CreateLpFile()
  1039. {
  1040.     CreateLpFile0(TRUE);
  1041. }
  1042.  
  1043.  
  1044. /* --------------- WriteHdrAndCloseLpFile ---------------
  1045.  * IMPLICIT PARAMETERS: lpFile, lpfHdr, nLps, lpfTable, totalNRecords.
  1046.  */
  1047. WriteHdrAndCloseLpFile()
  1048. {
  1049.     register UWORD i;
  1050.     BOOL tryAgain = TRUE;
  1051.  
  1052. again:
  1053.     if (!lpFile)
  1054.         return;
  1055.  
  1056.     MaybeWriteCurLp();
  1057.  
  1058. /*    lpfHdr.version = LPF_VERSION; --redundant */
  1059.     lpfHdr.nLps = nLps;
  1060.     lpfHdr.nRecords = totalNRecords;
  1061.     lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta;
  1062.  
  1063.         /* --- Clear unused elements of lpfTable. */
  1064.     setmem(&lpfTable[nLps],
  1065.            sizeof(LP_DESCRIPTOR) * (MAX_LARGE_PAGE - nLps),
  1066.            0);
  1067.  
  1068.     /* --- Write lpfHdr. */
  1069.     CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET));
  1070.     CHECK_DOS(writefull(lpFile, (char *) &lpfHdr, sizeof(lpfHdr)));
  1071.  
  1072.         /* --- Write cycle info as part of header */
  1073.         /* TBD: byte flipping for 68000 */
  1074.     CHECK_DOS(writefull(lpFile, (char *) cycles, sizeof(cycles)));
  1075.  
  1076.     /* --- Write palette, immediately after future-expansion of lpfHdr.
  1077.      * ASSUME PALETTE_SIZE == 3*256.
  1078.      * NOTE: Palette byte-order not changed between 8086 & 68000.
  1079.      */
  1080.     CHECK_DOS(writefull(lpFile, (char *)curpal, PALETTE_SIZE));
  1081.  
  1082.         /* --- Write lpfTable, immediately after palette. */
  1083.     CHECK_DOS(writefull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE));
  1084.     goto ok;
  1085.  
  1086. doserr:
  1087.     if (tryAgain) {                /* Attempt to overcome spurious disk error. */
  1088.         tryAgain = FALSE;
  1089.         goto again;
  1090.     }
  1091.     TroubleWritingFileMsg();
  1092. ok:
  1093.     closedos(lpFile);
  1094.     lpFile = NULL;
  1095.  
  1096.     if ((animFrames + lpfHdr.hasLastDelta) != totalNRecords)
  1097.         BugExit("LPH");            /* "inconsistent #frames vs #records in
  1098.                                  * file"); */
  1099.                                 /* NOTE: when hasLastDelta is TRUE,
  1100.                                  *    nRecords includes that delta.
  1101.                                  * In that case, number of frames in anim is
  1102.                                  *    "nRecords-1".
  1103.                                  * ASSUME TRUE == 1.
  1104.                                  * WARNING: Calling "BugExit" would be
  1105.                                  *    recursive, if done before
  1106.                                  * set lpFile = NULL.
  1107.                                  */
  1108. }
  1109.  
  1110.  
  1111. /* --------------- WriteLargePage0 ---------------
  1112.  * IMPLICIT PARAMETERS: "lpfTable" describes output lpFile.
  1113.  * TBD: should be descriptor containing both lpFile & lpfTable.
  1114.  * ASSUME lpFile is open for writing.
  1115.  * Q: Can this change to do in one disk i/o operation?
  1116.  * That requires lpTable to be moved to head of lpSeg, yet lpTable is
  1117.  * variable-size.  So will have to MOVE the 64KB of data.
  1118.  * ANS: CANNOT have "lpTable" part of lpSeg (requiring this routine to
  1119.  * shift data in lpSeg), as this routine is used to
  1120.  * WriteNewLp, while lpSeg still holding other useful data!
  1121.  */
  1122. #define LP_SIZE_IN_FILE(nLp) \
  1123.     (lpfTable[nLp].nBytes + LP_HEADER_SIZE(lpfTable[nLp].nRecords))
  1124.  /* Size of an lp in file -- contents PLUS header. */
  1125.  
  1126. WORD WriteLargePage0(
  1127.                      UWORD lpFile, UWORD seg, LP_TABLE_ELEMENT lpTable[],
  1128.                      register LpX * lpX)
  1129. {
  1130.     LPageHeaderFixed lphf;
  1131.     UWORD gapSize, writeSize;
  1132.     UBYTE zeroes[512];    /* 512 bytes on stack.  TBD: can be larger? */
  1133.     UWORD zeroSeg, zeroAddr;
  1134.     BOOL tryAgain = TRUE;
  1135.  
  1136. again:
  1137.     lpfTable[lpX->nLp] = lpX->x;
  1138.  
  1139.     CHECK_DOS(SeekLargePage(lpX->nLp));
  1140.  
  1141.         /* --- Write header for lp.
  1142.          * Note: if header changes, so must LP_HEADER_SIZE.
  1143.          */
  1144.     lphf.x = lpX->x;
  1145.     lphf.nByteContinuation = 0;
  1146.  
  1147.     CHECK_DOS(writefull(lpFile, (char *) &lphf, sizeof(lphf)));
  1148.  
  1149.     if (lpX->x.nRecords)
  1150.         CHECK_DOS(writefull(lpFile,
  1151.            (char *) lpTable, lpX->x.nRecords * sizeof(LP_TABLE_ELEMENT)));
  1152.        /* --- Table describing the records. */
  1153.  
  1154.     if (lpX->x.nBytes)
  1155.         CHECK_DOS(writedosfull(lpFile, seg, lpX->recAddr0, lpX->x.nBytes));
  1156.  
  1157.     if (cleanGap) {                /* Fill gap with nulls. */
  1158.         zeroSeg = DAllocBiggest(32768, &writeSize);
  1159.         if (writeSize <= sizeof(zeroes))
  1160.             zeroSeg = SFree(zeroSeg);
  1161.         if (zeroSeg)
  1162.             zeroAddr = 0;        /* Allocated a large far buffer */
  1163.         else {
  1164.             zeroSeg = dataseg();/* Use stack buffer */
  1165.             zeroAddr = (UWORD) zeroes;
  1166.             writeSize = sizeof(zeroes);
  1167.         }
  1168.         far_setmem(zeroSeg, zeroAddr, writeSize, 0);
  1169.         gapSize = (65535 - LP_SIZE_IN_FILE(lpX->nLp)) + 1;
  1170.         while (gapSize) {
  1171.             if (writeSize > gapSize)
  1172.                 writeSize = gapSize;
  1173.             gapSize -= writeSize;
  1174.             CHECK_DOS(writedosfull(
  1175.                                    lpFile, zeroSeg, zeroAddr, writeSize));
  1176.         }
  1177.         if (zeroSeg != dataseg())
  1178.             zeroSeg = SFree(zeroSeg);
  1179.     }
  1180.     return (SUCCESS);
  1181. doserr:
  1182.     if (tryAgain) {                /* Attempt to overcome spurious disk error. */
  1183.         tryAgain = FALSE;
  1184.         goto again;
  1185.     }
  1186.     TroubleWritingFileMsg();
  1187.     return (FAIL);
  1188. }
  1189.  
  1190.  
  1191. /* --------------- WriteCurLp ---------------
  1192.  * Write current large page back into buffer.
  1193.  */
  1194. local WORD WriteCurLp(void)
  1195. {
  1196.     WORD result;
  1197.  
  1198.     result = WriteLargePage0(lpFile, lpSeg, &lpTable[lpTableOffset], &curLpX);
  1199.     curLpIsDirty = FALSE;
  1200.     return result;
  1201. }
  1202.  
  1203.  
  1204. /* --------------- ReadCurLp ---------------
  1205.  * Read current large page back into buffer.
  1206.  * TBD: If no lp specified yet, does nothing.  Should it load first frame
  1207.  * instead?
  1208.  */
  1209. local void ReadCurLp(void)
  1210. {
  1211.     if (CUR_LP < nLps)
  1212.         ReadLargePage(CUR_LP);
  1213. }
  1214.  
  1215.  
  1216. /* --------------- RecordFits ---------------
  1217.  */
  1218. BOOL RecordFits(UWORD srcBytes)
  1219. {
  1220.     if (LP_N_RECS == MAX_RECORDS_PER_LP)
  1221.         return FALSE;
  1222.  
  1223.             /* --- If added this record, check whether header + body still
  1224.              *    fit.
  1225.              */
  1226.     if (LP_HEADER_SIZE(LP_N_RECS + 1) + (ULONG)LP_N_BYTES + (ULONG)srcBytes >
  1227.         (ULONG)MAX_LARGE_PAGE_SIZE)
  1228.         return FALSE;
  1229.     return TRUE;
  1230. }
  1231.  
  1232.  
  1233. /* --------------- LpfFrameLimitAddr ---------------
  1234.  * For current lpf frame, RETURN limitAddr.
  1235.  * == current size of frame.
  1236.  */
  1237. UWORD LpfFrameLimitAddr()
  1238. {
  1239.     return BODY_ADDR + BODY_BYTES;
  1240. }
  1241.  
  1242.  
  1243. /* ------------------------------------------------------------ */
  1244. /* --------------- Other routines --------------- */
  1245.  
  1246. /* --------------- IsLpfId ---------------
  1247.  * RETURN TRUE if id matches Large-Page-File's id.
  1248.  */
  1249. BOOL IsLpfId(ULONG id)
  1250. {
  1251.     return (id == LARGE_PAGE_FILE_ID);
  1252. }
  1253.  
  1254.  
  1255. /* ------------------------------------------------------------------------
  1256.  *                            Delta management.
  1257.  * ------------------------------------------------------------------------
  1258.  */
  1259.  
  1260.  
  1261. /* --------------- delta buffer ---------------
  1262.  * Buffer to hold one delta (difference between two bitmaps).
  1263.  * (seg:addr) is start of buffer, (seg:limit) is just past end of buffer,
  1264.  * "nBytes" is length of delta.
  1265.  */
  1266. typedef struct {
  1267.     UWORD seg, addr, limit, nBytes;
  1268. }      DeltaX;
  1269. DeltaX deltaX = {0};
  1270.  
  1271. extern UWORD allocSeg;
  1272.  
  1273. /* --------------- ComputeDeltaX ---------------
  1274.  * Given prevFrame & current frame in hidbm, compute delta in save_bitmap.
  1275.  * IMPLICIT INPUT: deltaX, hidbmSeg, prevFrameSeg.
  1276.  * RETURN TRUE if succeeded in creating delta, FALSE if delta didn't fit.
  1277.  * SIDE_EFFECT: set deltaX.nBytes, store that many bytes at deltaX.(seg:addr).
  1278.  * Note: if algorithm is changed, must be careful that can't exceed
  1279.  * MAX_LARGE_PAGE_SIZE.
  1280.  */
  1281. #define MRSD_ASM    1    /* Make RunSkipDump in assembly. */
  1282.  
  1283. #define CLIP_X(stopLabel)     {    \
  1284.     if (x == w) {                \
  1285.         x = 0;                    \
  1286.         if (inFinalRow) {        \
  1287.             mustStop = TRUE;    \
  1288.             goto stopLabel; }    \
  1289.         if (src2 == finalRow2) {\
  1290.             w = finalRowWidth;    \
  1291.             inFinalRow = TRUE; }\
  1292.         }                        \
  1293.     }
  1294.  
  1295. #define CROSSED_W(n)    (xWas + (n) > width)
  1296.     /* Test avoids using SHORT ops when line-wrap.
  1297.      * This is specified, so can write algorithm to play onto wider screen,
  1298.      * without having to test shortDump & shortRun for line-wrap on any pixel.
  1299.      * "width" rather than "w", since is from previous line, not current line.
  1300.      * This only matters on last line.
  1301.      */
  1302.  
  1303. #define PUT_DUMP(body, cnt)   if (cnt) {            \
  1304.             if ((cnt) <= MAX_SHORT_DUMP  &&  !CROSSED_W(cnt)) \
  1305.                 *dst++ = OP_DUMP | (cnt);        \
  1306.             else{                                \
  1307.                 *dst++ = LONG_OP;                \
  1308.                 *((UWORD far *)dst)++ =            \
  1309.                    (LONG_DUMP<<8) | (cnt); }    \
  1310.             if (dst - dst0 > finalDst-(cnt))    \
  1311.                 goto incompressible;            \
  1312.             far_movmem_ptrs( body, dst, cnt );    \
  1313.             dst += cnt;  }
  1314.  /* NOTE the check for zero cnt.
  1315.   * NOTE that cnt must already have been limited to MAX_DUMP.
  1316.   */
  1317.  
  1318. #define PUT_RUN(wordCnt,runPix)    if (wordCnt) {    \
  1319.             if ((wordCnt) <= MAX_SHORT_RUN  &&  !CROSSED_W(wordCnt)) { \
  1320.                 *dst++ = OP_RUN;                \
  1321.                 *dst++ = wordCnt; }                \
  1322.             else{                                \
  1323.                 *dst++ = LONG_OP;                \
  1324.                 *((UWORD far *)dst)++ =            \
  1325.                    (LONG_RUN<<8) | (wordCnt); }    \
  1326.             *dst++ = runPix;                    \
  1327.             }
  1328.  
  1329.  
  1330. local BOOL ComputeDeltaX(BOOL skipPermitted)
  1331. {
  1332.     /* Inputs: */
  1333.     UBYTE far *src1, far * src2, far * final2, far * dst;
  1334.     UWORD width, finalDst;
  1335.  
  1336.     /* src1 points to previous frame, src2 to screen, limit2 to final
  1337.      *    screen
  1338.      * byte, dst points to "finalDst+1" byte buffer for Body,
  1339.      * width is # pixels in a row.  For 64 KB buffer, finalDst==0xFFFF.
  1340.      */
  1341.     /* Local variables: */
  1342.     UBYTE far *finalRow2, far * dst0;
  1343.     UBYTE pixel, runPix, skipInDump, runInDump;
  1344.     UWORD x, w, finalRowWidth, wordCnt, xWas;
  1345.     BOOL inFinalRow, mustStop;
  1346.  
  1347. #if 0
  1348.     UWORD deltaSize;
  1349.  
  1350. #endif
  1351.     if (!deltaX.limit)
  1352.         BugExit("LPJ");            /* "ComputeDelta -- deltaX.limit"); */
  1353.  
  1354.     src1 = BYTE_FAR_PTR(prevFrameSeg, 0);
  1355.     src2 = BYTE_FAR_PTR(hidbmSeg, 0);
  1356.     final2 = BYTE_FAR_PTR(hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1);
  1357.     dst = BYTE_FAR_PTR(deltaX.seg, deltaX.addr);
  1358.     width = hidbm.box.w;
  1359.     finalDst = deltaX.limit;
  1360.  
  1361.     w = width;
  1362.     finalRowWidth = (UWORD)(final2 - src2 + 1) % width;
  1363.     if (!finalRowWidth)
  1364.         finalRowWidth = width;    /* # pixels in final row. */
  1365.     finalRow2 = final2 - (finalRowWidth - 1);    /* Ptr to start of final
  1366.                                                  * row. */
  1367.     inFinalRow = (finalRow2 == src2);
  1368.       /* FALSE unless src2 is a single row. */
  1369.     if (inFinalRow)
  1370.         w = finalRowWidth;        /* In case single-row src is shorter than
  1371.                                  * width. */
  1372.  
  1373.  
  1374. #if MRSD_ASM    /* RunSkipDump in assembly. */
  1375.     return MakeRunSkipDump(prevFrameSeg, hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1,
  1376.                     deltaX.seg, deltaX.addr, deltaX.limit, &deltaX.nBytes,
  1377.           w, FP_OFF(finalRow2), finalRowWidth, inFinalRow, skipPermitted);
  1378.  
  1379. #else    /* ---- RunSkipDump in C. --- most likely out of date ------ */
  1380.     dst0 = dst;                    /* Remember, so can calculate Body size. */
  1381.     *(UWORD far *) dst = BMBODY_RUNSKIPDUMP;    /* 2-byte header with type. */
  1382.     dst += 2;
  1383.  
  1384.     x = 0;                        /* Keep track of pixel-position, so break
  1385.                                  * at row end. */
  1386.     mustStop = FALSE;
  1387.  
  1388. notInASequence:
  1389.     wordCnt = 0;
  1390.  
  1391. /*--- "pixel" must be set when jump to beDump with "wordCnt != 0".*/
  1392. beDump:
  1393.     xWas = x + width - wordCnt;    /* Where in line dump started. */
  1394.     if (xWas >= width)
  1395.         xWas -= width;
  1396.               /* "width" logic in case x < wordCnt, because already
  1397.                *    line-wrapped.
  1398.                */
  1399.     if (FP_OFF(dst) > finalDst - 3)
  1400.         goto incompressible;
  1401.             /* "3" is longDump overhead.  Dump DATA is checked inside
  1402.              *    PUT_DUMP.
  1403.              */
  1404.     skipInDump = 0;
  1405.     runInDump = (wordCnt ? 1 : 0);        /* Any pixel is run==1. */
  1406. inDump:
  1407.     CLIP_X(stopDump);            /* If mustStop, goto to "stopDump" to get
  1408.                                  * final dump. */
  1409.     runPix = pixel;
  1410.     pixel = *src2++;
  1411.     x++;
  1412.     wordCnt++;                    /* Get&count byte. */
  1413.     if (pixel == *src1++)
  1414.         skipInDump++;            /* # skippable bytes. */
  1415.     else
  1416.         skipInDump = 0;
  1417.     if (pixel == runPix)
  1418.         runInDump++;            /* # runnable bytes. */
  1419.     else
  1420.         runInDump = 1;            /* Any pixel is run==1. */
  1421.  
  1422.     if (skipInDump == MIN_SKIP && skipPermitted) {        /* Minimum worthwhile
  1423.                                                          * skip. */
  1424.         PUT_DUMP(src2 - wordCnt, wordCnt - MIN_SKIP);
  1425.         wordCnt = MIN_SKIP;
  1426.         goto beSkip;            /* "Skip 2" so far. */
  1427.     }
  1428.     if (runInDump == MIN_RUN) {    /* Minimum worthwhile run. */
  1429.         PUT_DUMP(src2 - wordCnt, wordCnt - MIN_RUN);
  1430.         wordCnt = MIN_RUN;
  1431.         goto beRun;                /* "Run 4" so far. */
  1432.     }
  1433.     /* --- Byte still in Dump. */
  1434.     if (wordCnt == MAX_LONG_DUMP) {
  1435. stopDump:
  1436.         PUT_DUMP(src2 - wordCnt, wordCnt);
  1437.         if (mustStop)
  1438.             goto stop;
  1439.         goto notInASequence;
  1440.     }
  1441.     goto inDump;
  1442.  
  1443. beRun:
  1444.     xWas = x + width - wordCnt;    /* Where in line run started. */
  1445.     if (xWas >= width)
  1446.         xWas -= width;
  1447.     if (FP_OFF(dst) > finalDst - 4)
  1448.         goto incompressible;    /* "4" is length of longRun. */
  1449.     skipInDump = 0;                /* ACTUALLY skip is in run, not in dump... */
  1450.     runPix = pixel;                /* Only need do once per run. */
  1451. inRun:
  1452.     CLIP_X(stopRun);            /* If mustStop, goto to "stopRun" to get
  1453.                                  * final run. */
  1454.     pixel = *src2++;
  1455.     x++;
  1456.     wordCnt++;                    /* Get byte & count it. */
  1457.     if (pixel == *src1++)
  1458.         skipInDump++;            /* # skippable bytes. */
  1459.     else
  1460.         skipInDump = 0;
  1461.         /* Note: check for byte runnable comes later. */
  1462.  
  1463.     if (skipInDump == MIN_RUN_SKIP && skipPermitted) {
  1464.         wordCnt -= MIN_RUN_SKIP;
  1465.         PUT_RUN(wordCnt, runPix);
  1466.         wordCnt = MIN_RUN_SKIP;
  1467.         goto beSkip;            /* "Skip 4" so far. */
  1468.     }
  1469.     if (pixel != runPix) {        /* Byte not runnable -- requires Dump. */
  1470.         wordCnt--;                /* Retract pixel from Run. */
  1471.         PUT_RUN(wordCnt, runPix);
  1472.         wordCnt = 1;
  1473.         goto beDump;            /* "Dump 1" so far. */
  1474.     }
  1475.     /* --- Byte runnable. */
  1476.     if (wordCnt == MAX_LONG_RUN) {
  1477. stopRun:
  1478.         PUT_RUN(wordCnt, runPix);
  1479.         if (mustStop)
  1480.             goto stop;
  1481.         goto notInASequence;
  1482.     }
  1483.     goto inRun;
  1484.  
  1485. beSkip:
  1486.     xWas = x + width - wordCnt;    /* Where in line skip started. */
  1487.     if (xWas >= width)
  1488.         xWas -= width;
  1489.     if (FP_OFF(dst) > finalDst - 3)
  1490.         goto incompressible;    /* "3" is length of LongSkip. */
  1491. inSkip:
  1492.     runPix = pixel;
  1493.     pixel = *src2++;
  1494.     x++;
  1495.     wordCnt++;                    /* Get byte & count it. */
  1496.     if (pixel != *src1++) {        /* Byte not skippable. */
  1497.         wordCnt--;                /* Retract pixel from Skip. */
  1498.         if (wordCnt <= MAX_SHORT_SKIP) {
  1499.             *dst++ = OP_SKIP | wordCnt;
  1500.         } else if (wordCnt <= 2 * MAX_SHORT_SKIP) {
  1501.             *dst++ = OP_SKIP | MAX_SHORT_SKIP;
  1502.             wordCnt -= MAX_SHORT_SKIP;
  1503.             *dst++ = OP_SKIP | wordCnt;
  1504.         } else {
  1505.             UWORD wordCnt0 = wordCnt;
  1506.  
  1507.             while (wordCnt0) {
  1508.                 wordCnt = MIN(wordCnt0, MAX_LONG_SKIP);
  1509.                 wordCnt0 -= wordCnt;
  1510.                 *dst++ = LONG_OP;
  1511.                 *dst++ = (UBYTE)wordCnt;        /* Low byte of UWORD. */
  1512.                 *dst++ = wordCnt >> 8;    /* High byte of UWORD. */
  1513.             }
  1514.         }
  1515.         wordCnt = 1;
  1516.         goto beDump;            /* "Dump 1" so far. */
  1517.     }
  1518.     /* --- Byte skippable. */
  1519.     /* --- Final Skip is NOT output prior to Stop. */
  1520.     CLIP_X(stop);
  1521.     goto inSkip;                /* Skip may be any length up to 64KB-1. */
  1522.  
  1523. stop:
  1524.     if (FP_OFF(dst) > finalDst - 3)
  1525.         goto incompressible;    /* "3" is length of Stop. */
  1526.     *dst++ = LONG_OP;            /* Stop represented as "LongOp #0". */
  1527.     /* 68000 note: Don't use word op to put cnt, as may not be
  1528.      *    word-aligned.
  1529.      */
  1530.     *dst++ = 0;                    /* High byte of cnt==0. */
  1531.     *dst++ = 0;                    /* Low byte of cnt==0. */
  1532.     deltaX.nBytes = dst - dst0;
  1533.       /* "body size": # bytes in Body, excluding any header.
  1534.        * NOTE: incompressible tests guarantee that deltaX.nBytes won't quite
  1535.        * reach 64 KB, which would overflow UWORD.
  1536.        */
  1537.     return TRUE;                /* deltaX.nBytes is set as a side-effect. */
  1538.       /* NOTE: if deltaX.nBytes == 3, all there is is a Stop.
  1539.        * Client may wish to set deltaX.nBytes == 0 & have no data in this
  1540.        *    case.
  1541.        */
  1542.  
  1543. incompressible:
  1544.     return FALSE;
  1545. #endif    /* RunSkipDump in C. */
  1546. }
  1547.  
  1548.  
  1549. /* --------------- AbandonChangesMaybeExit ---------------
  1550.  */
  1551. local void AbandonChangesMaybeExit(BOOL closeAndExit)
  1552. {
  1553.     CUR_LP = (UWORD)-1;        /* None loaded -- abandon lpBuffer. */
  1554.     curLpIsDirty = FALSE;
  1555.     /* --- Drastic solution -- don't try to continue. */
  1556.     if (closeAndExit)
  1557.         DiskFullChangesLostExit();        /* closes lp. */
  1558. }
  1559.  
  1560.  
  1561. /* --------------- FindFreeLp ---------------
  1562.  */
  1563. local UWORD FindFreeLp(void)
  1564. {
  1565.     register UWORD nLp;
  1566.  
  1567.     for (nLp = 0;  nLp < nLps;  nLp++) {
  1568.         if (lpfTable[nLp].nRecords == 0)
  1569.             return nLp;            /* Found empty lp in middle of file. */
  1570.     }
  1571.     if (nLps == MAX_LARGE_PAGE) {
  1572.         BugExit("LPK");            /* "Too many pages in anim"); */
  1573.     }
  1574.     return nLps++;            /* Page # of next lp to use (lps #d from 0). */
  1575. }
  1576.  
  1577.  
  1578. /* --------------- LpTableToLpBufferHeader ---------------
  1579.  * Assuming space made available, put correct contents in header,
  1580.  * based on lpTable & variables.
  1581.  * ASSUMES LP_N_RECS & LP_N_BYTES has already been changed to reflect # recs
  1582.  * & new contents in lp.
  1583.  * Copy these changes to lpfTable, so FindLargePageForRecord will see truth.
  1584.  */
  1585. local void LpTableToLpBufferHeader(void)
  1586. {
  1587.     far_movmem(dataseg(), (UWORD)&curLpX.x, lpSeg, 0, sizeof(LP_DESCRIPTOR));
  1588.     *WORD_FAR_PTR(lpSeg, sizeof(LPageHeaderFixed) - 2) = 0;       /* no cont'. */
  1589.         /* ASSUMES nByteContinuation is last field. */
  1590.     far_movmem(dataseg(), (UWORD)lpTable, lpSeg, sizeof(LPageHeaderFixed),
  1591.                sizeof(LP_TABLE_ELEMENT) * LP_N_RECS);
  1592.  
  1593.     lpfTable[CUR_LP] = curLpX.x;    /* IMPORTANT! */
  1594. }
  1595.  
  1596.  
  1597. /* --------------- UpdateLpBufferHeader ---------------
  1598.  * Shift the data in the lp buffer, and re-write the header, so that it
  1599.  * matches lpTable.
  1600.  */
  1601. local void UpdateLpBufferHeader(void)
  1602. {
  1603.     UWORD newRecAddr0 = LP_HEADER_SIZE(LP_N_RECS);
  1604.  
  1605.     far_movmem_same_seg(lpSeg, LP_REC_ADDR0, newRecAddr0, LP_N_BYTES);
  1606.  
  1607.         /* --- "current record" info has changed location. */
  1608.     REC_ADDR = REC_ADDR - LP_REC_ADDR0 + newRecAddr0;
  1609.     BODY_ADDR = BODY_ADDR - LP_REC_ADDR0 + newRecAddr0;
  1610.  
  1611.     LP_REC_ADDR0 = newRecAddr0;
  1612.     LpTableToLpBufferHeader();
  1613.     curLpIsDirty = TRUE;
  1614. }
  1615.  
  1616.  
  1617. /* --------------- WriteNewLp ---------------
  1618.  * Extract a portion of lp buffer as a new lp.
  1619.  * Return FAIL if trouble writing (presumably, disk is full).
  1620.  */
  1621. #define SV_BASE_REC    svLpX.x.baseRecord
  1622. local WORD WriteNewLp(UWORD startRecord, UWORD endRecord)
  1623. {
  1624.     register UWORD nRecord;
  1625.     WORD result;
  1626.     LpX svLpX;
  1627.  
  1628.     svLpX = curLpX;
  1629.     CUR_LP = FindFreeLp();
  1630.  
  1631.     lpTableOffset = startRecord - SV_BASE_REC;
  1632.         /* Fake out WriteCurLp. */
  1633.  
  1634.             /* NOTE: need unmodified startRecord & endRecord for this. */
  1635.     LP_BASE_REC = startRecord;
  1636.     LP_N_RECS = endRecord - startRecord + 1;
  1637.  
  1638.     startRecord -= SV_BASE_REC;
  1639.     endRecord -= SV_BASE_REC;
  1640.  
  1641.         /* NOTE: use startRecord/endRecord RELATIVE to SV_BASE_REC. */
  1642.         /* --- Get from real LP_REC_ADDR0 to fake LP_REC_ADDR0 (at
  1643.          *    startRecord).
  1644.          */
  1645.     for (nRecord = 0; nRecord < startRecord; nRecord++) {
  1646.         LP_REC_ADDR0 += lpTable[nRecord];
  1647.     }
  1648.  
  1649.         /* --- Sum of bytes for desired records. */
  1650.     LP_N_BYTES = 0;                /* So far */
  1651.     for (nRecord = startRecord; nRecord <= endRecord; nRecord++) {
  1652.         LP_N_BYTES += lpTable[nRecord];
  1653.     }
  1654.     result = WriteCurLp();        /* New lp. */
  1655.  
  1656.     lpTableOffset = 0;            /* Done faking out WriteCurLp. */
  1657.     curLpX = svLpX;                /* Restore access to "current lp". */
  1658.                             /* Including LP_REC_ADDR0! */
  1659.     return result;
  1660. }
  1661.  
  1662.  
  1663. /* --------------- CurRec_NewBody ---------------
  1664.  * Place a new body into CurRec.  If necessary, records before and/or after
  1665.  * current are moved into new lps.
  1666.  * IMPLICIT INPUT: curRecX, deltaX, lpSeg, curLpX.
  1667.  */
  1668. local void CurRec_NewBody(void)
  1669. {
  1670.     WORD result = SUCCESS;
  1671.  
  1672.     /* 1. Determine how much room for new body. */
  1673. #define AVAIL_BODY_SIZE        (GAP_SIZE + BODY_BYTES)
  1674.     UWORD availBodySize = AVAIL_BODY_SIZE;
  1675.     BOOL newExtraBytes = (EXTRA_BYTES == 0);
  1676.  
  1677.         /* If there was no record at all, create an ID+FLAGS UWORD. */
  1678.     if (newExtraBytes)
  1679.         availBodySize -= 2;        /* For ID+FLAGS. */
  1680.  
  1681.             /* 2A. If won't fit, move other records to new lps. */
  1682.     if (deltaX.nBytes > availBodySize) {
  1683.         /* --- Dumbest possible approach: split into 3 buffers. */
  1684.         UWORD newRecAddr0 = LP_HEADER_SIZE(1);
  1685.  
  1686.                 /* --- Following records. */
  1687.         if (CUR_REC != LAST_RECORD_IN_BUF && result == SUCCESS) {
  1688.             result = WriteNewLp(CUR_REC + 1, LAST_RECORD_IN_BUF);
  1689. /*            if (result != SUCCESS)   goto fail;   TBD... */
  1690.             /* Cur lp now has just base-rec thru cur-rec. */
  1691.             LP_N_RECS = (CUR_REC + 1) - LP_BASE_REC;
  1692.             LP_N_BYTES = (REC_ADDR + REC_BYTES) - LP_REC_ADDR0;
  1693.             UpdateLpBufferHeader();
  1694.             availBodySize = AVAIL_BODY_SIZE;
  1695.             if (newExtraBytes)
  1696.                 availBodySize -= 2;        /* For ID+FLAGS. */
  1697.             if (deltaX.nBytes <= availBodySize)
  1698.                 goto nowFits;
  1699.         }
  1700.         if (CUR_REC > LP_BASE_REC && result == SUCCESS)
  1701.             result = WriteNewLp(LP_BASE_REC, CUR_REC - 1);        /* Previous records */
  1702.  
  1703.         if (result != SUCCESS) {
  1704.             /* TBD: Don't use new lps. */
  1705.             BugExit("NewLp");
  1706.         }
  1707.                 /* --- Prepare lp with just CUR_REC. */
  1708.         LP_BASE_REC = CUR_REC;
  1709.         LP_N_RECS = 1;
  1710.         if (newExtraBytes)
  1711.             EXTRA_BYTES = 2;
  1712.         else
  1713.             far_movmem(lpSeg, REC_ADDR, lpSeg, newRecAddr0, EXTRA_BYTES);
  1714.         BODY_BYTES = deltaX.nBytes;
  1715.         LP_REC_ADDR0 = REC_ADDR = newRecAddr0;
  1716.         BODY_ADDR = REC_ADDR + EXTRA_BYTES;
  1717.         REC_BYTES = EXTRA_BYTES + BODY_BYTES;
  1718.         LP_N_BYTES = lpTable[0] = REC_BYTES;
  1719.         LpTableToLpBufferHeader();        /* Buffer now has single frame in
  1720.                                          * it! */
  1721.                                             /* NOTE: should NOT do i/o,
  1722.                                              *    just preparing variables.
  1723.                                              */
  1724.     } else {                    /* 2B. Make room for new body, & set
  1725.                                  * variables. */
  1726.         UWORD curRecLimit;
  1727.         UWORD sizeChange;
  1728.  
  1729. nowFits:
  1730.         curRecLimit = REC_ADDR + REC_BYTES;
  1731.         sizeChange = deltaX.nBytes - BODY_BYTES;
  1732.             /* NOTE: May be NEGATIVE number.  Relying on 16-bit wrapping! */
  1733. #define BYTES_AFTER_BODY    (LP_TOTAL_BYTES - curRecLimit)
  1734.  
  1735.         if (newExtraBytes) {
  1736.             EXTRA_BYTES = 2;
  1737.             sizeChange += EXTRA_BYTES;
  1738.             BODY_ADDR = REC_ADDR + EXTRA_BYTES;
  1739.                 /* No longer access old body. */
  1740.         }
  1741. #define NEW_REC_LIM            (curRecLimit + sizeChange)
  1742.             /* ASSUMES body is last field in record. */
  1743.             /* ASSUMES lp starts at addr 0 w/i lpSeg. */
  1744.  
  1745.         far_movmem_same_seg(lpSeg, curRecLimit, NEW_REC_LIM, BYTES_AFTER_BODY);
  1746.         BODY_BYTES = deltaX.nBytes;        /* When newExtra, not same as
  1747.                                          * sizeChange */
  1748.         REC_BYTES += sizeChange;
  1749.         LP_N_BYTES += sizeChange;
  1750.         lpTable[CUR_REC - LP_BASE_REC] = REC_BYTES;
  1751.     }
  1752.  
  1753.         /* 3. Get new body into lp buffer. */
  1754.     if (newExtraBytes)
  1755.         *WORD_FAR_PTR(lpSeg, REC_ADDR) = DEFAULT_BITMAP_ID_FLAGS;
  1756.     far_movmem(deltaX.seg, deltaX.addr, lpSeg, BODY_ADDR, BODY_BYTES);
  1757.     curLpIsDirty = TRUE;
  1758. }
  1759.  
  1760. /* --------------- DAllocBiggest ---------------
  1761.  * Allocate the requested block.  If can't, find and allocate
  1762.  * the biggest possible.
  1763.  * TBD: Is there a DOS or BIOS call to find biggest available block?
  1764.  * RETURNS seg # of allocation, NULL if failed.
  1765.  * SIDE_EFFECT: sets "*allocSizeP" to # bytes actually allocated.
  1766.  */
  1767. local UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP)
  1768. {
  1769.     UWORD seg, allocParas;
  1770.  
  1771.         /* --- First, try to allocate full request, kicking out other stuff
  1772.          * if necessary.
  1773.          */
  1774.     seg = DAlloc(nBytes);
  1775.     if (seg) {
  1776.         *allocSizeP = nBytes;
  1777.         return seg;
  1778.     }
  1779.  
  1780.     /* --- Couldn't get all, get what we can. */
  1781.  
  1782. #define N_PARAGRAPHS(nBytes)    ( (nBytes >> 4) + (nBytes & 0x0f ? 1 : 0) )
  1783.         /* NOTE: Don't do as "(nBytes+15) >> 4", since may overflow UWORD. */
  1784.  
  1785.     seg = allocsome(N_PARAGRAPHS(nBytes), &allocParas);
  1786.     *allocSizeP = allocParas << 4;
  1787.     if (!seg)                    /* Only happens if memory completely full. */
  1788.         *allocSizeP = 0;
  1789.     return seg;
  1790. }
  1791.  
  1792.  
  1793. /* --------------- ComputeDelta ---------------
  1794.  * Compute delta into a large temporary buffer.
  1795.  * IMPLICIT INPUT: curRecX, lpSeg.
  1796.  * SIDE_EFFECT: sets deltaX.
  1797.  */
  1798. local void ComputeDelta(void)
  1799. {
  1800.     BOOL skipPermitted = (curFrame != FIRST_FRAME_N);
  1801.  
  1802.     /* Disallow skip code on first frame, so don't need to blank
  1803.      * frame before drawing delta -- will overwrite every pixel
  1804.      * via Dumps and Runs.
  1805.      */
  1806.  
  1807.     deltaX.seg = allocSeg;
  1808.     deltaX.addr = 0;
  1809.     deltaX.limit = MAX_LARGE_PAGE_SIZE;
  1810.  
  1811. bigEnough:
  1812.     if (!ComputeDeltaX(skipPermitted)) {
  1813.         /* Handle incompressible frame. */
  1814.         UWORD rawSize = N_BYTES_IN_BITMAP;
  1815.  
  1816.         deltaX.nBytes = rawSize + sizeof(UWORD);
  1817.             /* UWORD for header, then raw dump of screen data. */
  1818.         if (deltaX.limit - deltaX.addr < deltaX.nBytes)
  1819.             BugExit("LPM");
  1820.                 /* "Buffer too small for incompressible frame"); */
  1821.         *WORD_FAR_PTR(deltaX.seg, deltaX.addr) = BMBODY_UNCOMPRESSED;
  1822.             /* header contains type "uncompressed". */
  1823.         far_movmem(hidbmSeg, 0, deltaX.seg, deltaX.addr + 2, rawSize);
  1824.     }
  1825. }
  1826.  
  1827.  
  1828. /* --------------- WriteFinalAnimFrame ---------------
  1829.  * Compute and transfer to file the current delta-frame,
  1830.  * which must be the last frame in the file.
  1831.  * (If not last frame, must use WriteAnimFrameAndReadNext).
  1832.  */
  1833. void WriteFinalAnimFrame(void)
  1834. {
  1835.     ComputeDelta();
  1836.     CurRec_NewBody();            /* Implicit: deltaX. */
  1837. }
  1838.  
  1839. /* --------------- WriteAnimFrameAndReadNext ---------------
  1840.  * Transfer to file the specified delta-frame.
  1841.  * Also compute & transfer the next delta-frame, as it is affected by the
  1842.  * changes in the current anim-frame.
  1843.  * TBD: Some caller's may wish to MaybeWriteCurLp, so that lp buffer is
  1844.  * free for immediate use by menus/pop-ups.
  1845.  */
  1846. void WriteAnimFrameAndReadNext(void)
  1847. {
  1848.     ComputeDelta();
  1849.  
  1850.     /* ---2. Exchange prevFrame with new-frame in hidbm.
  1851.      *         Because new-frame is next-frame's prevFrame.
  1852.      */
  1853.     far_swapmem(BSEG(&hidbm), 0, prevFrameSeg, 0, N_BYTES_IN_BITMAP);
  1854.  
  1855.     /* ---3. Apply old-delta to hidbm.  (Convert prev- to old- frame.) */
  1856.     PlayDeltaFrame(&hidbm);        /* Implicit: curRecX, lpSeg. */
  1857.     CurRec_NewBody();            /* Implicit: deltaX. */
  1858.  
  1859.     /* ---4. If at anim end, simply set up first frame. */
  1860.     /* FUTURE: may compute delta between last & first frame.
  1861.      * Could store in special frame before the normal first frame.
  1862.      */
  1863.     curFrame++;
  1864.     if (curFrame > LAST_FRAME_N) {
  1865.         FirstAnimFrame0();
  1866.         goto done;
  1867.     }
  1868.  
  1869.     /* ---5. Read & apply next delta. */
  1870.     ReadLpfFrame(curFrame);
  1871.     PlayDeltaFrame(&hidbm);        /* Convert old-frame to next-frame. */
  1872.  
  1873.     /* ---6. Recompute next-frame's delta (since prevFrame is new.) */
  1874.     ComputeDelta();
  1875.     CurRec_NewBody();
  1876.  
  1877. done: ;
  1878. }
  1879.  
  1880. /* --------------- AddFrame ---------------
  1881.  * ASSUMES current frame is last frame in lpfile.
  1882.  * Add an identical frame after it, and make it current.
  1883.  * NOTE: Be careful to leave valid cur rec & cur lp variables.
  1884.  */
  1885. void AddFrame()
  1886. {
  1887.     UWORD afterRecord = CUR_REC; /* Hold, in case lp changes affect CUR_REC.*/
  1888.     WORD afterFrame = curFrame;
  1889.  
  1890.     if ( MAX_RECORDS_PER_LP - LP_N_RECS < 1  ||        /* lpTable overflow*/
  1891.          MAX_LARGE_PAGE_SIZE - LP_N_BYTES <
  1892.             LP_HEADER_SIZE(LP_N_RECS+1)                /* lp overflow */
  1893.        ) {
  1894.         /* --- Create new lp, with only an empty frame. */
  1895.         MaybeWriteCurLp();
  1896.  
  1897.         LP_BASE_REC = afterRecord + 1;
  1898.         LP_N_BYTES = 0;
  1899.         setmem( &lpTable[0], sizeof(LP_TABLE_ELEMENT), 0 );
  1900.             /* Zero the new frame. */
  1901.  
  1902.         /* --- SWITCH ACCESS from current lp to new lp. */
  1903.         CUR_LP = FindFreeLp();
  1904.         LP_N_RECS = 1;
  1905.  
  1906.         WriteCurLp();
  1907.     } else {
  1908.         /* Add frame to current lp.
  1909.          * Zero the new frame.  record # relative to CUR_LP's base rec.
  1910.          */
  1911.         lpTable[afterRecord+1 - LP_BASE_REC] = 0;
  1912.         LP_N_RECS += 1;
  1913.     }
  1914.     totalNRecords += 1;
  1915.     AnimFramesX(animFrames + 1);
  1916.  
  1917.     UpdateLpBufferHeader();
  1918.     ReadLpfFrame(curFrame = afterFrame+1);    /* Make this frame current. */
  1919. }
  1920.  
  1921. /* --------------- ConvertFinalFrameToLastDelta ---------------
  1922.  */
  1923. ConvertFinalFrameToLastDelta()
  1924. {
  1925.     animFrames--;
  1926.     lpfHdr.hasLastDelta = lpfHdr.lastDeltaValid = TRUE;
  1927.     curFrame--;
  1928. }
  1929.