Files
meep-eep e7eabd46b9 Old tools that got removed a long time ago.
git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@1545 8092fc87-c524-0410-9efc-e669fe64eaf9
2005-02-19 00:38:46 +00:00

1929 lines
56 KiB
C++

/*--lpfile.c----------------------------------------------------------
*
* Large Page File. This is a format for dividing a file into 64 KB
* "large pages" ("lp"s) that can be stored out-of-sequence in the file.
* That is, an lp can be logically inserted into the file, without
* having to move all the following data out of the way.
*
* Each lp holds one or more "records". A record is of any length from
* 0 to almost 64 KB. The records in the file are sequentially numbered
* starting with 0. The records in each lp are a sub-sequence; e.g.,
* "3 records starting with #6" would be #6,#7,#8.
*
* A DeluxePaint Animation "ANM" ("Anim") file is built on this mechanism,
* so that as frames change in size, they can be maintained with a minimum
* of extra file i/o, yet without loss of playback performance.
* In addition, there is an optional special record which is the delta from
* the last frame to the first frame, for smooth playback of looping anims.
*/
#include "main.h"
#include "anim.h"
#include <fcntl.h>
#include <stdio.h>
#include <io.h> /* chsize */
#define local static
extern BOOL animDisk, outputLargePageFile, editting, cycling;
extern WORD curFrame;
extern UWORD animFrames, frameDelay, startFrame, endFrame, waitVerts,
gapLimitAddr;
extern Box screenBox, animBox, animMove;
extern BITMAP prevFrame, hidbm;
extern LONG curpal[MAX_COLORS];
extern void AnimVBlank();
#if U_COLORCYCLE
extern Range cycles[MAXNCYCS];
#endif
/* --------------- forward declarations --------------- */
UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP);
void ReadLargePage(UWORD nLp);
WORD WriteCurLp(void);
void ReadCurLp(void);
UWORD FindLargePageForRecord(register UWORD nRecord);
void WriteAnimFrameAndReadNext(void);
/* --------------- --------------- */
WORD lpFile = NULL; /* Keep open so can access again. */
char lpfSuffix[]= "ANM";
local Range cycles[MAXNCYCS];
/* Disk i/o sets after filling buffer, screen i/o clears after using. */
UWORD nBuffers = 2;
UWORD useBuffers = 2;
extern char animName[];
local UWORD saveBitmapSeg = NULL;
#define prevFrameSeg BSEG(&prevFrame)
#define hidbmSeg BSEG(&hidbm)
/* --------------------------------------------------------------------------
* --------------- Description of "current record". ---------------
*/
typedef struct {
UWORD curRecord; /* record being accessed w/i lp. NOTE: ABSOLUTE rec
* #, not relative to lp baseRecord. */
UWORD recAddr; /* Address of first byte of record, w/i lp buffer. */
UWORD recBytes; /* # bytes in record. */
UWORD bodyAddr; /* Address of Body of current frame. */
UWORD bodyBytes; /* # bytes in Bitmap record's Body. */
UWORD extraBytes; /* # bytes in Bitmap record prior to Body.
* ID+FLAGS & ExtraData, incl. count & pad.
* Used to skip over ExtraData. */
UWORD extraDataCnt; /* # bytes of contents of ExtraData. */
} BmRecX; /* Access to a Bitmap record. */
local BmRecX curRecX; /* Description of "current record". */
#define CUR_REC curRecX.curRecord
/* --- These should be "CUR_REC_...", but the names get unreasonably long. */
#define REC_ADDR curRecX.recAddr
#define REC_BYTES curRecX.recBytes
#define BODY_ADDR curRecX.bodyAddr
#define BODY_BYTES curRecX.bodyBytes
#define EXTRA_BYTES curRecX.extraBytes
#define EXTRA_DATA_CNT curRecX.extraDataCnt
#define DEFAULT_BITMAP_ID_FLAGS ((0<<8) + 'B')
/* First byte is 'B'. On 8086 that's low byte. */
/* ----- Functions to manipulate "current record" w/i CUR_LP w/i lp buffer. */
/* TBD: we're not actually using these, but doing the logic where needed. */
/* Shift data in buffer so there is room for "n" extraBytes, and set
* EXTRA_BYTES. NOTE: this is NOT "extraDataCount" -- it includes
* ID+FLAGS & extraDataCount in its size.
*/
local void CurRec_SetExtraBytes(UWORD n);
/* Shift data in buffer so there is room for "n" bodyBytes, and set
* BODY_BYTES.
*/
local void CurRec_SetBodyBytes(UWORD n);
/* Find & set CUR_REC. NOTE: "n" is absolute, not relative to baseRecord.
* This will put new lp in lp buffer if necessary.
*/
local void CurRec_Set(UWORD n);
UWORD NRecord2NFrame(WORD nRecord);
/* --------------------------------------------------------------------------
* --------------- buffering large pages ---------------
*/
local UWORD preBufferSeg = NULL;
local UWORD preFrameAddr0 = 0;
local UWORD nBuf, nNextBuf = 0, nNextPreBuf = 0, nPreLp = 0, nPreRecord;
local BOOL lastRecordInBuf, curLpIsDirty = FALSE;
/* "lastRecordInBuf is not used during editing, so is not part of
* curRecX.
*/
#define LAST_RECORD_IN_BUF (LP_BASE_REC + LP_N_RECS - 1)
/* -------------------- LargePage -------------------- */
#define MAX_LARGE_PAGE 256
typedef struct {
UWORD baseRecord; /* First record in lp. */
UWORD nRecords; /* # records in lp. */
/* bit 15 of "nRecords" == "has continuation from previous lp".
* bit 14 of "nRecords" == "final record continues on next lp".
* File format thus permits 16383 records/lp.
* Only MAX_RECORDS_PER_LP is currently supported by the code,
* to minimize table size in DGROUP in DP Animation.
* TBD: we're not handling non-zero bits 14&15.
*/
UWORD nBytes; /* # bytes of contents, excluding header.
* Gap of "64KB - nBytesUsed - headerSize" at end of lp.
* headerSize == "8 + 2*nRecords".
* Header is followed by record #baseRecord. */
} LP_DESCRIPTOR;
local LP_DESCRIPTOR lpfTable[MAX_LARGE_PAGE];
#define LARGE_PAGE_SIZE 0x10000L
#define MAX_RECORDS_PER_LP 256 /* TBD: why restrict, other than RAM usage? */
#define LP_TABLE_ELEMENT UWORD /* # bytes for each record of curLp. */
/* NOTE: lp's header must be accounted for separately.*/
local LP_TABLE_ELEMENT lpTable[MAX_RECORDS_PER_LP];
local UWORD lpTableOffset = 0; /* So can pretend lpTable starts at some
* element other than 0, to fake out
* WriteCurLp. */
typedef struct {
LP_DESCRIPTOR x; /* Duplicate of lpfTable[CUR_LP]. NOT relied on,
* just here to help scavenge damaged files.
* "baseRecord" NOT kept up-to-date, because
* LP.AddRecord or DeleteRecord changes
* "baseRecord" of ALL following lps -- would be
* very slow to add one record near the start of a
* huge file! This field is accurate whenever the
* lp is written, but is not relied on after that.
* It still helps scavenging. Since "Optimize Anim"
* re-writes every lp, this field is accurate if
* Optimize after AddRecord/DeleteRecord. */
UWORD nByteContinuation; /* ==0. FUTURE: allows record from
* previous lp to extend on to this lp, so
* don't waste file space. */
} LPageHeaderFixed;
/* These fields precede the array giving "REC_BYTES" per record. */
#define LP_HEADER_SIZE(nRecords) ( sizeof(LPageHeaderFixed) + \
(nRecords) * sizeof(LP_TABLE_ELEMENT) )
/* NOTE: if this changes, so must WriteLargePage0(). */
#define FIRST_RECORD_N 0 /* Records #d from 0. */
#define LAST_RECORD_N (totalNRecords-1)
/* INCLUDES last-to-first delta if present! */
UWORD totalNRecords; /* Total # records in lpFile, including lastDelta. */
UWORD nLps; /* # large pages in lpFile. */
/* --------------------------------------------------------------------------
* --------------- Description of "current lp". ---------------
* One lp at a time is present in a buffer of just under 64 KB.
* The contents of this buffer are maintained in the same form as on disk --
* fixed header info, variable-length recordSizes table, data for records.
*/
extern UWORD lpSeg; /* Segment of lp buffer. */
typedef struct {
UWORD nLp; /* lp's # w/i file */
UWORD recAddr0; /* First byte of first record w/i lp. (after
* header). */
LP_DESCRIPTOR x; /* Describes lp's contents. */
} LpX; /* Access to an lp. */
local LpX curLpX; /* Description of "current lp". */
#define NO_LP ((UWORD)-1)
#define CUR_LP curLpX.nLp
/* --- These should be "CUR_LP_...", but the names get unreasonably long. */
#define LP_BASE_REC curLpX.x.baseRecord
#define LP_N_RECS curLpX.x.nRecords
#define LP_N_BYTES curLpX.x.nBytes
#define LP_REC_ADDR0 curLpX.recAddr0
/* Location of first byte of frame data in lpBuffer. */
/* ASSUMES LP_REC_ADDR0 set for correct # records. */
#define LP_TOTAL_BYTES (LP_REC_ADDR0 + LP_N_BYTES)
#define GAP_SIZE (MAX_LARGE_PAGE_SIZE - LP_TOTAL_BYTES)
/* --------------------------------------------------------------------------
* -------------------- LargePageFile --------------------
* Lpf's Header size is 256 + 3 * 256 + 6 * maxLps.
* When lpf created, maxLps must be chosen. If file needs more lps,
* will have to move all data in file. By convention, set maxLps to
* 256 initially, increase in multiples of 256 as needed.
* FOR NOW: WON'T HANDLE FILE UNLESS SET TO 256.
* 256 * 64 KB == 16 MB.
*/
local struct {
ULONG id; /* ID for LargePageFile == MakeID('L','P','F',' '). */
UWORD maxLps; /* 256 FOR NOW. max # largePages allowed in this
* file. */
UWORD nLps; /* # largePages currently in this file. 0..maxLps.
* lps #d from 0.
* NOTE: In RAM, we don't keep this field up to date. */
ULONG nRecords; /* # records currently in this file. 65535 is
* current limit. Allowing one for last-to-first
* delta, that restricts to 65534 frames of lo-res
* animation.
* Records #d from 0. NOTE: In RAM, we
* don't keep this field up to date. */
UWORD maxRecsPerLp; /* 256 FOR NOW. # records permitted in an lp.
* So reader knows table size to allocate. */
UWORD lpfTableOffset; /* 1280 FOR NOW. Absolute Seek position of
* lpfTable. This allows content-specific
* header to be variable-size in the
* future. */
ULONG contentType; /* for ANIM == MakeID('A','N','I','M'). All
* following info is specific to ANIM format. */
UWORD width; /* in pixels. */
UWORD height; /* in pixels. */
UBYTE variant; /* 0==ANIM. */
UBYTE version; /* 0== cycles stored for 18x/sec timer.
* 1== cycles stored for 70x/sec timer. */
BOOL hasLastDelta; /* 1==Record(s) representing final frame are a
* delta from last-to-first frame. nFrames ==
* (nRecords/recordsPerFrame)-hasLastDelta. */
BOOL lastDeltaValid; /* So needn't actually remove delta.
* (When hasLastDelta==1) 0==there is a last
* delta, but it hasn't been updated to
* match the current first&last frames, so
* it should be ignored. This is done so
* don't have to physically remove the
* delta from the file when the first frame
* changes. */
UBYTE pixelType; /* ==0 for 256-color. */
UBYTE highestBBComp; /* 1 (RunSkipDump) FOR NOW. highest #d
* "Record Bitmap.Body.compressionType".
* So reader can determine whether it can
* handle this Anim. */
UBYTE otherRecordsPerFrame; /* 0 FOR NOW. */
UBYTE bitmapRecordsPerFrame;/* ==1 for 320x200,256-color. */
UBYTE recordTypes[32]; /* Not yet implemented. */
ULONG nFrames; /* In case future version adds other records at end
* of file, we still know how many frames. NOTE:
* DOES include last-to-first delta when present.
* NOTE: In RAM, we don't keep
* this field up to date. */
UWORD framesPerSecond; /* # frames to play per second. */
UWORD pad2[29];
#if 0 /* read/write this directly, no need to waste DGROUP */
Range cycles[MAXNCYCS];
#endif
} lpfHdr; /* total is 128 words == 256 bytes. */
#if CCYCLE70
#define LPF_VERSION 1 /* cycles stored for 70x/sec timer. */
#else
#define LPF_VERSION 0 /* cycles stored for 18x/sec timer. */
#endif
#if 0
UWORD checkFrames[32]; /* frame #s to checkpoint. each ==0 when not used.
* # non-zero * recsPerFrame == # records used for
* this purpose. Must subtract from nRecords to
* determine how many frames are really in file.
* TBD: Allocate fixed record #s for these? TBD:
* force to be one record per lp? NOTE: since frame
* #s, not record #s, seems okay to restrict to
* 16-bit. If lo-res file gets > 64 K frames,
* scale #s so can always span # frames. Ex: if
* 64K..127K frames, these #s are "*2". Recommend:
* # checkpoints proportional to # lps. Keep 1/8th
* anim-size in checkpoints. Every time file
* doubles in size, double # checks. */
#endif
#define LARGE_PAGE_FILE_ID MakeID('L','P','F',' ')
#define ANIM_CONTENTS_ID MakeID('A','N','I','M')
#define LPF_HEADER_HEAD_SIZE_IN_FILE 256
/* First few fields at start of a Large Page File. */
#define SIZEOF_LPF_TABLE_IN_FILE (sizeof(LP_DESCRIPTOR) * MAX_LARGE_PAGE)
/* FUTURE: will be permitted to vary! */
#define LPF_HEADER_SIZE_IN_FILE ( LPF_HEADER_HEAD_SIZE_IN_FILE + \
PALETTE_SIZE + SIZEOF_LPF_TABLE_IN_FILE )
/* Everything in Large Page File before the first lp. */
#define LPF_TABLE_OFFSET (LPF_HEADER_HEAD_SIZE_IN_FILE + PALETTE_SIZE)
#define BBC_RUN_SKIP_DUMP 1
/* ----------------------------------------------------------------------- */
/* TBD: If you want better error handling, implement these. */
#define DiskFullChangesLostExit() exit(1)
#define CantCreateAnimFileExit() exit(1)
#define InvalidAnimFileExit() exit(1)
#define TooManyPagesExit() exit(1)
#define BadBitmapRecordExit() exit(1)
#define UnrecognizedDeltaTypeExit() exit(1)
#define LPageTooLargeMsg()
#define LpfTooLargeMsg()
#define TroubleWritingFileMsg()
#define LpfNotHandledByVersion()
#define TroubleReadingFileMsg()
#define NotAnAnimFileMsg()
/* ----------------------------------------------------------------------- */
/* --------------- GetFramesPerSecond ---------------
*/
UWORD GetFramesPerSecond()
{
return lpfHdr.framesPerSecond;
}
/* --------------- DeclareFramesPerSecond ---------------
* Make sure all parties know what the (new) framesPerSecond play-rate is.
*/
DeclareFramesPerSecond(UWORD newFPS)
{
if (newFPS == 0)
newFPS = FASTEST_RATE;
lpfHdr.framesPerSecond = newFPS;
}
/* --------------- AppendSuffix ---------------
*/
char sDot[] = ".";
void AppendSuffix(char *name, char *baseName, char *suffix)
{
strcpy(name, baseName);
strcat(name, sDot);
strcat(name, suffix);
}
/* --------------- OpenFile ---------------
*/
WORD OpenFile(char *baseName, char *suffix)
{
WORD file;
char name[80];
AppendSuffix(name, baseName, suffix);
file = opendos(name, O_RDWR);
if (file == -1)
file = NULL; /* No file open. */
return file;
}
/* --------------- DeleteFile ---------------
*/
void DeleteFile(char *baseName, char *suffix)
{
char name[80];
AppendSuffix(name, baseName, suffix);
deletedos(name);
}
/* --------------- AnimFramesX ---------------
* Set "animFrames" to new value.
* This is a low-level routine.
*/
void AnimFramesX(WORD nFrames)
{
animFrames = nFrames;
}
/* --------------- OpenLpFile ---------------
*/
BOOL OpenLpFile()
{
register UWORD i;
lpFile = OpenFile(animName, lpfSuffix);
if (!lpFile)
return FAIL;
/* --- Read lpfHdr. */
CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET));
CHECK_DOS(readfull(lpFile, (char *)&lpfHdr, sizeof(lpfHdr)));
/* --- read cycle info */
CHECK_DOS(readfull(lpFile, (char *)cycles, sizeof(cycles)));
/* --- Read palette, after skipping future-expansion of lpfHdr.
* ASSUME PALETTE_SIZE == 3*256.
*/
CHECK_DOS(lseekdos(lpFile, (LONG) LPF_HEADER_HEAD_SIZE_IN_FILE, SEEK_SET));
CHECK_DOS(readfull(lpFile, (char *)curpal, PALETTE_SIZE));
/* --- Read lpfTable, immediately after palette. */
CHECK_DOS(readfull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE));
/* --- Check that it is a DPaint-Animate Anim. */
if (lpfHdr.id != LARGE_PAGE_FILE_ID ||
lpfHdr.contentType != ANIM_CONTENTS_ID) {
NotAnAnimFileMsg();
goto badFile;
}
if (lpfHdr.maxLps != MAX_LARGE_PAGE ||
lpfHdr.nRecords > (ULONG) MAX_RECORDS) {
LpfTooLargeMsg();
goto badFile;
}
/* Note: maxLps may be too SMALL as well. */
if (lpfHdr.maxRecsPerLp > MAX_RECORDS_PER_LP) {
LPageTooLargeMsg();
goto badFile;
}
/* --- Check that it is an Anim type we can handle. */
if (lpfHdr.lpfTableOffset != LPF_TABLE_OFFSET ||
lpfHdr.width != 320 ||
lpfHdr.height != 200 || lpfHdr.highestBBComp > BBC_RUN_SKIP_DUMP ||
lpfHdr.bitmapRecordsPerFrame != 1 || lpfHdr.otherRecordsPerFrame ||
lpfHdr.pixelType) {
LpfNotHandledByVersion();
goto badFile;
}
for (i = 0; i < lpfHdr.nLps; i++) {
if (lpfTable[i].nRecords > MAX_RECORDS_PER_LP ||
lpfTable[i].nBytes > MAX_LARGE_PAGE_SIZE) {
LPageTooLargeMsg();
goto badFile;
}
/* FUTURE: hi 2 bits of nRecords indicate continuation. */
}
nLps = lpfHdr.nLps; /* TBD: why not use fields in place? */
totalNRecords = (UWORD) lpfHdr.nRecords;
AnimFramesX(lpfHdr.nFrames - lpfHdr.hasLastDelta);
DeclareFramesPerSecond(lpfHdr.framesPerSecond);
CUR_LP = NO_LP; /* None loaded. */
/* ReadLpfFrame(FIRST_FRAME_N); --- So have valid, for InsureBuffer...*/
return SUCCESS;
doserr:
TroubleReadingFileMsg();
badFile:
closedos(lpFile);
lpFile = NULL;
InvalidAnimFileExit();
return FAIL;
}
/* --------------- SeekLargePage ---------------
* Seek lp file to the specified large page.
* Return FALSE on error.
*/
#define LP_FILE_OFFSET(nLp) \
(LONG)((LONG)(nLp) * LARGE_PAGE_SIZE + LPF_HEADER_SIZE_IN_FILE)
local LONG SeekLargePage(UWORD nLp)
{
if (nLp > MAX_LARGE_PAGE)
BugExit("LP1"); /* "Invalid lp #"); */
/* Seek to lp. File has header, then lps #d from 0. */
return (lseekdos(lpFile, LP_FILE_OFFSET(nLp), SEEK_SET));
}
/* --------------- ReadLargePage00 ---------------
* Also reads HEADER into lpBuf, for read-ahead.
* When editing, don't want this.
* IMPLICIT PARAMETERS: preFrameAddr0, lpfTable.
*/
local ReadLargePage00(UWORD lpFile, WORD nLp, UWORD seg)
{
UWORD nBytes;
preFrameAddr0 = LP_HEADER_SIZE(lpfTable[nLp].nRecords);
nBytes = preFrameAddr0 + lpfTable[nLp].nBytes;
if (readdos(lpFile, seg, 0, nBytes) != nBytes)
BugExit("LP2"); /* "ReadLargePage00: nBytes"); */
}
/* --------------- MaybeWriteCurLp ---------------
*/
local void MaybeWriteCurLp(void)
{
if (curLpIsDirty) {
WriteCurLp();
}
}
/* --------------- ReadLargePage ---------------
*/
void ReadLargePage(UWORD nLp)
{
MaybeWriteCurLp();
SeekLargePage(nLp);
ReadLargePage00(lpFile, nLp, lpSeg);
curLpX.x = lpfTable[nLp]; /* LP_BASE_REC, LP_N_RECS, LP_N_BYTES */
LP_REC_ADDR0 = LP_HEADER_SIZE(LP_N_RECS);
/* far_movmem( lpSeg, 0, dataseg(), (UWORD)&LP_N_BYTES, 2 ); */
/* "baseRecord" field ignored. */
/* far_movmem( lpSeg, 4, dataseg(), (UWORD)&LP_N_RECS, 2 ); */
/* "nByteContinuation" field not used. */
far_movmem(lpSeg, sizeof(LPageHeaderFixed),
dataseg(), (UWORD) lpTable,
LP_N_RECS * sizeof(LP_TABLE_ELEMENT));
CUR_LP = nLp;
}
/* --------------- NFrame2NRecord ---------------
* Given a frame #, compute the record # in the lpFile.
* ASSUME has a last-delta, which is the last record.
*/
local UWORD NFrame2NRecord(WORD nFrame)
{
if (nFrame == LAST_DELTA_N)
return FIRST_RECORD_N;
return (nFrame - FIRST_FRAME_N + FIRST_RECORD_N);
}
/* --------------- NRecord2NFrame ---------------
* Given an lpfile record #, compute the frame #.
* ASSUME records are #d from 0.
*/
UWORD NRecord2NFrame(WORD nRecord)
{
return nRecord + FIRST_FRAME_N;
}
/* --------------- FindLargePageForRecord ---------------
* Step through lp descriptions, looking for one that contains
* requested record.
* RETURN lp #.
* NOTE: it is possible for lpfTable[nLp].baseRecord to be non-zero
* even on an empty page. Fortunately, the "baseRecord <= nRecord < ..."
* tests will always reject pages with nRecords==0.
*/
local UWORD FindLargePageForRecord(register UWORD nRecord)
{
register UWORD nLp, baseRecord;
for (nLp = 0; nLp < nLps; nLp++) {
baseRecord = lpfTable[nLp].baseRecord;
if (baseRecord <= nRecord &&
nRecord < baseRecord + lpfTable[nLp].nRecords) {
#if DEBUG_BUFS
if (!editting)
printf("Find lp:%d for rec:%d\n", nLp, nRecord); /* TESTING */
#endif
return nLp;
}
}
BugExit("LP6"); /* "Didn't find record in lpfTable"); */
}
/* --------------- ReadLargePageForRecord ---------------
* Find lp containing record, then read it.
*/
local void ReadLargePageForRecord(register UWORD nRecord)
{
ReadLargePage(FindLargePageForRecord(nRecord));
#if 0
if (FALSE) {
PrepExit();
printf("First ReadLargePageForRecord (%d) on playback.", nRecord);
PrintLPFState();
exit(1);
}
#endif /* 0 */
}
/* --------------- ReadLpfFrame ---------------
* Transfer to temp buffer the specified delta-frame.
*/
ReadLpfFrame(WORD nFrame)
{
register UWORD nRecord;
nRecord = NFrame2NRecord(nFrame);
if ((CUR_LP >= nLps) || /* No lp yet. */
(nRecord < LP_BASE_REC) || /* record not in CUR_LP. */
(nRecord >= LP_BASE_REC + LP_N_RECS)) {
if (nRecord >= totalNRecords)
BugExit("LP7"); /* "Impossible lp record."); */
ReadLargePageForRecord(nRecord);
}
/* --- Step through records in CUR_LP. */
REC_ADDR = LP_REC_ADDR0;
CUR_REC = LP_BASE_REC;
while (nRecord > CUR_REC) {
REC_ADDR += lpTable[CUR_REC - LP_BASE_REC];
CUR_REC++;
}
REC_BYTES = lpTable[CUR_REC - LP_BASE_REC];
if (REC_BYTES) {
UWORD far *ptr;
UBYTE bitmapId, bitmapFlags;
ptr = WORD_FAR_PTR(lpSeg, REC_ADDR);
bitmapId = ((UBYTE far *) ptr)[0];
if (bitmapId != 'B')
#if 0 /* TESTING */
{
PrepExit();
printf("BadB frame:%d id:%x exN:%d recN:%d bodyN:%d, addrs:%x %x, rec data:%x %x %x\n",
nFrame, bitmapId, EXTRA_BYTES,
REC_BYTES, BODY_BYTES, REC_ADDR, BODY_ADDR, ptr[0], ptr[1], ptr[2]);
exit(1);
}
#else
BadBitmapRecordExit();
#endif
bitmapFlags = ((UBYTE far *) ptr)[1];
EXTRA_DATA_CNT = ptr[1];
EXTRA_BYTES = (bitmapFlags ? 4 + EVEN_UP(EXTRA_DATA_CNT) : 2);
} else {
EXTRA_BYTES = 0; /* No recData, therefore no extra data. */
}
BODY_ADDR = REC_ADDR + EXTRA_BYTES;
BODY_BYTES = REC_BYTES - EXTRA_BYTES;
lastRecordInBuf = (CUR_REC == LAST_RECORD_IN_BUF);
}
/* --------------- PlayDeltaFrame0 ---------------
* Play the current delta info onto the specified frame bitmap.
* IMPLICIT INPUT: curRecX, lpSeg.
* NOTE: The 0 BODY_BYTES case isn't handled at this level.
*/
#define PRSD_ASM 1 /* Play RunSkipDump in assembly. */
local void PlayDeltaFrame0(UWORD bmSeg)
{
#if 1
UWORD bodyType, nBytes;
#else
register UWORD deltaAddr;
UWORD picSeg;
LONG frameType;
#endif
if (!BODY_BYTES)
return; /* empty body == no change */
if (BODY_BYTES < 2)
BugExit("LP8");
/* "Short body of bitmap record");
* TBD: return error code
*/
/* --- runSkipDump or uncompressed */
#define BODY_HEADER_LEN sizeof(UWORD)
/* UWORD holding body type precedes body data. */
bodyType = *WORD_FAR_PTR(lpSeg, BODY_ADDR);
nBytes = BODY_BYTES - 2;
if (bodyType == BMBODY_UNCOMPRESSED) {
/* --- Direct dump of screen. */
if (nBytes != N_BYTES_IN_BITMAP)
BugExit("LP9"); /* "Uncompressed body has wrong # bytes"); */
far_movmem(lpSeg, BODY_ADDR + BODY_HEADER_LEN,
bmSeg, 0, nBytes);
} else if (bodyType == BMBODY_RUNSKIPDUMP) { /* --- RunSkipDump
* encoding of screen. */
#if PRSD_ASM /* RunSkipDump in assembly. */
UWORD dstAddr;
UBYTE far *srcP;
#else /* RunSkipDump in C. */
register signed char cnt;
UBYTE pixel;
UWORD wordCnt;
UBYTE far *srcP, far * dstP;
#endif
if (!nBytes)
return; /* empty delta == no change */
#define BMBODY_STOP_CODE_LEN 3
#define BBSTOP0 LONG_OP
#define BBSTOP1 0
#define BBSTOP2 0
srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_BYTES - BMBODY_STOP_CODE_LEN);
if (nBytes < BMBODY_STOP_CODE_LEN || srcP[0] != BBSTOP0 ||
srcP[1] != BBSTOP1 || srcP[2] != BBSTOP2)
BugExit("LPA"); /* "RunSkipDump body missing stop code"); */
#if PRSD_ASM /* RunSkipDump in assembly. */
dstAddr = PlayRunSkipDump(nBytes, lpSeg, BODY_ADDR + BODY_HEADER_LEN,
bmSeg, 0);
if (dstAddr > N_BYTES_IN_BITMAP)
#else /* RunSkipDump in C. */
/* --- Here is an algorithm to playback a 256-color Body,
* onto a same-size screen with rows in sequence,
* so don't have to test each sequence to see
* if it is on a new row.
* NOTE: Once it is working, you may remove all the references to
* "nBytes", as the stop code is sufficient to end processing.
*/
srcP = BYTE_FAR_PTR(lpSeg, BODY_ADDR + BODY_HEADER_LEN);
dstP = BYTE_FAR_PTR(bmSeg, 0);
/* srcP points at first sequence in Body,
* dstP points at pixel #0 on screen.
*/
nextOp:
if (nBytes < 3)
BugExit("LPB"); /* "RunSkipDump body elided stop code"); */
cnt = (signed char) *srcP++;
if (cnt > 0)
goto dump;
if (cnt == 0)
goto run;
cnt -= 0x80;
if (cnt == 0)
goto longOp;
/* shortSkip */
nBytes -= 1; /* length of shortSkip code */
dstP += cnt; /* adding 7-bit count to 32-bit pointer */
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
goto nextOp;
dump:
nBytes -= 1 + cnt; /* length of dump code & data */
/* ASM: ds:si=srcP, es:di=dstP, cx=cnt, then do "rep movsb". */
/* Equivalent: "do { *dstP++ = *srcP++; } while (--cnt);" */
far_movmem_ptrs(srcP, dstP, cnt);
dstP += cnt;
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
srcP += cnt;
goto nextOp;
run:
wordCnt = (UBYTE) * srcP++; /* 8-bit unsigned count */
pixel = *srcP++;
nBytes -= 3; /* length of run code & data */
/* ASM: es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */
/* Equivalent: "do { *dstP++ = pixel; } while (--wordCnt);" */
far_setmem_ptr(dstP, wordCnt, pixel);
dstP += wordCnt;
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
goto nextOp;
longOp:
wordCnt = *((UWORD far *)srcP)++;
if ((WORD)wordCnt <= 0)
goto notLongSkip; /* Do SIGNED test. */
/* longSkip. */
nBytes -= 3;
dstP += wordCnt;
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
goto nextOp;
notLongSkip:
if (wordCnt == 0)
goto stop;
wordCnt -= 0x8000; /* Remove sign bit. */
if (wordCnt >= 0x4000)
goto longRun;
/* longDump. */
nBytes -= 3 + wordCnt;
/* ASM: ds:si=srcP, es:di=dstP, cx=wordCnt, then do "rep movsb". */
/* Equivalent: "do { *dstP++ = *srcP++; } while (--wordCnt);" */
far_movmem_ptrs(srcP, dstP, wordCnt);
dstP += wordCnt;
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
srcP += wordCnt;
goto nextOp;
longRun:
wordCnt -= 0x4000; /* Clear "longRun" bit. */
pixel = *srcP++;
nBytes -= 4;
/* ASM: es:di=dstP, al=pixel, cx=wordCnt, then do "rep stosb". */
/* Equivalent: "do { *dstP++ = pixel; } while (--wordCnt);" */
far_setmem_ptr(dstP, wordCnt, pixel);
dstP += wordCnt;
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
goto stop;
goto nextOp;
stop:
/* all done */
if (FP_OFF(dstP) > N_BYTES_IN_BITMAP)
#endif /* RunSkipDump in C. */
BugExit("LPC");
/* "Wrong amount of screen data generated from RunSkipDump
* body");
*/
}
else { /* TBD: Should be Info Msg, not Exit. */
UnrecognizedDeltaTypeExit();
}
}
/* --------------- PlayDeltaFrame ---------------
* Play the current delta info onto the specified frame bitmap.
*/
PlayDeltaFrame(BITMAP * bm)
{
if (BODY_BYTES == 0)
return; /* "0 bytes" means "no change". */
PlayDeltaFrame0(BSEG(bm));
}
/* ----------------------------------------------------------------------- */
BOOL permitScreenSwap = TRUE;
/* --------------- CreateFile ---------------
*/
WORD CreateFile(char *baseName, char *suffix)
{
WORD file;
char name[80];
AppendSuffix(name, baseName, suffix);
file = creatdos(name, O_RDWR);
if (file == -1)
file = NULL; /* No file open. */
return file;
}
/* --------------- CloseFile ---------------
* Once i/o is buffered, this will flush & release buffer.
*/
void CloseFile(WORD file)
{
if (file && file != -1)
closedos(file);
/* NOTE: we use "NULL" to indicate non-open file. */
}
/* --------------- WriteN ---------------
* Write near data, and return FALSE on error.
*/
BOOL WriteN(UWORD file, BYTE *data, UWORD cnt)
{
return (cnt == writedos(file, dataseg(), data, cnt));
}
/* --------------- WriteFar ---------------
* Write far data, and return FALSE on error.
*/
BOOL WriteFar(UWORD file, WORD far * data, UWORD cnt)
{
return (cnt == writedos(file, FP_SEG(data), FP_OFF(data), cnt));
}
/* --------------- ReadN ---------------
* Read near data, and return FALSE on error.
*/
BOOL ReadN(UWORD file, BYTE * data, UWORD cnt)
{
return (cnt == read(file, data, cnt));
}
/* --------------- ReadFar ---------------
* Read far data, and return FALSE on error.
*/
BOOL ReadFar(UWORD file, WORD far * data, UWORD cnt)
{
return (cnt == readdos(file, FP_SEG(data), FP_OFF(data), cnt));
}
/* --------------- CreateLpFile0 ---------------
* Create file with one frame.
* If "blank", the first frame is blank, otherwise it is the
* contents of "hidbm".
*/
BOOL animOptimized; /* TRUE when anim is packed for optimal playback. */
BOOL cleanGap = FALSE; /* TRUE to put nulls in gap when write lp. */
void CreateLpFile0(BOOL blank)
{
lpFile = CreateFile(animName, lpfSuffix);
if (!lpFile)
CantCreateAnimFileExit();
setmem(&lpfHdr, sizeof(lpfHdr), 0); /* BEFORE set any fields. */
#if 0 /* ...done by setmem... */
lpfHdr.variant = lpfHdr.hasLastDelta =
lpfHdr.lastDeltaValid = lpfHdr.pixelType =
lpfHdr.otherRecordsPerFrame = 0;
#endif
lpfHdr.version = LPF_VERSION;
/* TBD: Whenever create new anim, starts at a specified rate.
* Should this instead be done when program boots, then use whatever
* gets loaded?
* Hard to say, since can either be set by user,
* or by loading an anim (in which case user may not realize it
* changed).
*/
/* TBD: Boot, or NewAnim, now creates at 10 fps. Good choice? */
DeclareFramesPerSecond(10 /* FASTEST_RATE */ );
lpfHdr.id = LARGE_PAGE_FILE_ID;
lpfHdr.maxLps = MAX_LARGE_PAGE;
lpfHdr.maxRecsPerLp = MAX_RECORDS_PER_LP;
/* lpfHdr.nLps & .nRecords are over-ridden by independent variables. */
lpfHdr.lpfTableOffset = LPF_TABLE_OFFSET;
lpfHdr.contentType = ANIM_CONTENTS_ID;
lpfHdr.width = 320;
lpfHdr.height = 200;
lpfHdr.bitmapRecordsPerFrame = 1;
lpfHdr.highestBBComp = BBC_RUN_SKIP_DUMP;
AnimFramesX(totalNRecords = 1); /* # records in whole lpFile */
lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta;
nLps = 1;
CUR_LP = 0;
CUR_REC = LP_BASE_REC = 0;
LP_N_RECS = 1; /* # records in current large page */
LP_N_BYTES = 0;
lpTable[0] = 0; /* A zero-byte page description. */
/* (Since it is first frame, zero-bytes means all-white). */
LP_REC_ADDR0 = 0; /* No header info before frame data in
* buffer seg. */
#if 1
/* --- Hack: Force zero-size first frame to be re-computed as
* a series of all-white runs, since the player no longer sets bitmap
* to white before first frame.
* THEN, say the anim is not dirty, so won't save it unless some
* change is made. This avoids asking user to save the empty
* untitled anim, when he boots DP-A, then does LoadAnim.
* !!! Is ugly to be calling these routines from inside here. !!!
*/
ReadLpfFrame(FIRST_FRAME_N);
if (blank) {
SetBitMap(&hidbm, white_color); /* Clear what will be frame 0. */
FirstAnimFrame0();
WriteAnimFrameAndReadNext();
} else { /* Use "hidbm" as first frame's contents. */
FirstAnimFrame00();
WriteAnimFrameAndReadNext();
}
animOptimized = TRUE;
#endif
WriteCurLp(); /* Make sure disk is up-to-date. */
ReadCurLp(); /* Overkill. Makes sure buffer & tables
* consistent. */
ReadLpfFrame(FIRST_FRAME_N);
/* Adjust BODY_ADDR, etc., for correct header size. */
}
/* --------------- CreateLpFile ---------------
* Create file, with first frame blank.
*/
void CreateLpFile()
{
CreateLpFile0(TRUE);
}
/* --------------- WriteHdrAndCloseLpFile ---------------
* IMPLICIT PARAMETERS: lpFile, lpfHdr, nLps, lpfTable, totalNRecords.
*/
WriteHdrAndCloseLpFile()
{
register UWORD i;
BOOL tryAgain = TRUE;
again:
if (!lpFile)
return;
MaybeWriteCurLp();
/* lpfHdr.version = LPF_VERSION; --redundant */
lpfHdr.nLps = nLps;
lpfHdr.nRecords = totalNRecords;
lpfHdr.nFrames = animFrames + lpfHdr.hasLastDelta;
/* --- Clear unused elements of lpfTable. */
setmem(&lpfTable[nLps],
sizeof(LP_DESCRIPTOR) * (MAX_LARGE_PAGE - nLps),
0);
/* --- Write lpfHdr. */
CHECK_DOS(lseekdos(lpFile, (LONG) 0, SEEK_SET));
CHECK_DOS(writefull(lpFile, (char *) &lpfHdr, sizeof(lpfHdr)));
/* --- Write cycle info as part of header */
/* TBD: byte flipping for 68000 */
CHECK_DOS(writefull(lpFile, (char *) cycles, sizeof(cycles)));
/* --- Write palette, immediately after future-expansion of lpfHdr.
* ASSUME PALETTE_SIZE == 3*256.
* NOTE: Palette byte-order not changed between 8086 & 68000.
*/
CHECK_DOS(writefull(lpFile, (char *)curpal, PALETTE_SIZE));
/* --- Write lpfTable, immediately after palette. */
CHECK_DOS(writefull(lpFile, (char *)lpfTable, SIZEOF_LPF_TABLE_IN_FILE));
goto ok;
doserr:
if (tryAgain) { /* Attempt to overcome spurious disk error. */
tryAgain = FALSE;
goto again;
}
TroubleWritingFileMsg();
ok:
closedos(lpFile);
lpFile = NULL;
if ((animFrames + lpfHdr.hasLastDelta) != totalNRecords)
BugExit("LPH"); /* "inconsistent #frames vs #records in
* file"); */
/* NOTE: when hasLastDelta is TRUE,
* nRecords includes that delta.
* In that case, number of frames in anim is
* "nRecords-1".
* ASSUME TRUE == 1.
* WARNING: Calling "BugExit" would be
* recursive, if done before
* set lpFile = NULL.
*/
}
/* --------------- WriteLargePage0 ---------------
* IMPLICIT PARAMETERS: "lpfTable" describes output lpFile.
* TBD: should be descriptor containing both lpFile & lpfTable.
* ASSUME lpFile is open for writing.
* Q: Can this change to do in one disk i/o operation?
* That requires lpTable to be moved to head of lpSeg, yet lpTable is
* variable-size. So will have to MOVE the 64KB of data.
* ANS: CANNOT have "lpTable" part of lpSeg (requiring this routine to
* shift data in lpSeg), as this routine is used to
* WriteNewLp, while lpSeg still holding other useful data!
*/
#define LP_SIZE_IN_FILE(nLp) \
(lpfTable[nLp].nBytes + LP_HEADER_SIZE(lpfTable[nLp].nRecords))
/* Size of an lp in file -- contents PLUS header. */
WORD WriteLargePage0(
UWORD lpFile, UWORD seg, LP_TABLE_ELEMENT lpTable[],
register LpX * lpX)
{
LPageHeaderFixed lphf;
UWORD gapSize, writeSize;
UBYTE zeroes[512]; /* 512 bytes on stack. TBD: can be larger? */
UWORD zeroSeg, zeroAddr;
BOOL tryAgain = TRUE;
again:
lpfTable[lpX->nLp] = lpX->x;
CHECK_DOS(SeekLargePage(lpX->nLp));
/* --- Write header for lp.
* Note: if header changes, so must LP_HEADER_SIZE.
*/
lphf.x = lpX->x;
lphf.nByteContinuation = 0;
CHECK_DOS(writefull(lpFile, (char *) &lphf, sizeof(lphf)));
if (lpX->x.nRecords)
CHECK_DOS(writefull(lpFile,
(char *) lpTable, lpX->x.nRecords * sizeof(LP_TABLE_ELEMENT)));
/* --- Table describing the records. */
if (lpX->x.nBytes)
CHECK_DOS(writedosfull(lpFile, seg, lpX->recAddr0, lpX->x.nBytes));
if (cleanGap) { /* Fill gap with nulls. */
zeroSeg = DAllocBiggest(32768, &writeSize);
if (writeSize <= sizeof(zeroes))
zeroSeg = SFree(zeroSeg);
if (zeroSeg)
zeroAddr = 0; /* Allocated a large far buffer */
else {
zeroSeg = dataseg();/* Use stack buffer */
zeroAddr = (UWORD) zeroes;
writeSize = sizeof(zeroes);
}
far_setmem(zeroSeg, zeroAddr, writeSize, 0);
gapSize = (65535 - LP_SIZE_IN_FILE(lpX->nLp)) + 1;
while (gapSize) {
if (writeSize > gapSize)
writeSize = gapSize;
gapSize -= writeSize;
CHECK_DOS(writedosfull(
lpFile, zeroSeg, zeroAddr, writeSize));
}
if (zeroSeg != dataseg())
zeroSeg = SFree(zeroSeg);
}
return (SUCCESS);
doserr:
if (tryAgain) { /* Attempt to overcome spurious disk error. */
tryAgain = FALSE;
goto again;
}
TroubleWritingFileMsg();
return (FAIL);
}
/* --------------- WriteCurLp ---------------
* Write current large page back into buffer.
*/
local WORD WriteCurLp(void)
{
WORD result;
result = WriteLargePage0(lpFile, lpSeg, &lpTable[lpTableOffset], &curLpX);
curLpIsDirty = FALSE;
return result;
}
/* --------------- ReadCurLp ---------------
* Read current large page back into buffer.
* TBD: If no lp specified yet, does nothing. Should it load first frame
* instead?
*/
local void ReadCurLp(void)
{
if (CUR_LP < nLps)
ReadLargePage(CUR_LP);
}
/* --------------- RecordFits ---------------
*/
BOOL RecordFits(UWORD srcBytes)
{
if (LP_N_RECS == MAX_RECORDS_PER_LP)
return FALSE;
/* --- If added this record, check whether header + body still
* fit.
*/
if (LP_HEADER_SIZE(LP_N_RECS + 1) + (ULONG)LP_N_BYTES + (ULONG)srcBytes >
(ULONG)MAX_LARGE_PAGE_SIZE)
return FALSE;
return TRUE;
}
/* --------------- LpfFrameLimitAddr ---------------
* For current lpf frame, RETURN limitAddr.
* == current size of frame.
*/
UWORD LpfFrameLimitAddr()
{
return BODY_ADDR + BODY_BYTES;
}
/* ------------------------------------------------------------ */
/* --------------- Other routines --------------- */
/* --------------- IsLpfId ---------------
* RETURN TRUE if id matches Large-Page-File's id.
*/
BOOL IsLpfId(ULONG id)
{
return (id == LARGE_PAGE_FILE_ID);
}
/* ------------------------------------------------------------------------
* Delta management.
* ------------------------------------------------------------------------
*/
/* --------------- delta buffer ---------------
* Buffer to hold one delta (difference between two bitmaps).
* (seg:addr) is start of buffer, (seg:limit) is just past end of buffer,
* "nBytes" is length of delta.
*/
typedef struct {
UWORD seg, addr, limit, nBytes;
} DeltaX;
DeltaX deltaX = {0};
extern UWORD allocSeg;
/* --------------- ComputeDeltaX ---------------
* Given prevFrame & current frame in hidbm, compute delta in save_bitmap.
* IMPLICIT INPUT: deltaX, hidbmSeg, prevFrameSeg.
* RETURN TRUE if succeeded in creating delta, FALSE if delta didn't fit.
* SIDE_EFFECT: set deltaX.nBytes, store that many bytes at deltaX.(seg:addr).
* Note: if algorithm is changed, must be careful that can't exceed
* MAX_LARGE_PAGE_SIZE.
*/
#define MRSD_ASM 1 /* Make RunSkipDump in assembly. */
#define CLIP_X(stopLabel) { \
if (x == w) { \
x = 0; \
if (inFinalRow) { \
mustStop = TRUE; \
goto stopLabel; } \
if (src2 == finalRow2) {\
w = finalRowWidth; \
inFinalRow = TRUE; }\
} \
}
#define CROSSED_W(n) (xWas + (n) > width)
/* Test avoids using SHORT ops when line-wrap.
* This is specified, so can write algorithm to play onto wider screen,
* without having to test shortDump & shortRun for line-wrap on any pixel.
* "width" rather than "w", since is from previous line, not current line.
* This only matters on last line.
*/
#define PUT_DUMP(body, cnt) if (cnt) { \
if ((cnt) <= MAX_SHORT_DUMP && !CROSSED_W(cnt)) \
*dst++ = OP_DUMP | (cnt); \
else{ \
*dst++ = LONG_OP; \
*((UWORD far *)dst)++ = \
(LONG_DUMP<<8) | (cnt); } \
if (dst - dst0 > finalDst-(cnt)) \
goto incompressible; \
far_movmem_ptrs( body, dst, cnt ); \
dst += cnt; }
/* NOTE the check for zero cnt.
* NOTE that cnt must already have been limited to MAX_DUMP.
*/
#define PUT_RUN(wordCnt,runPix) if (wordCnt) { \
if ((wordCnt) <= MAX_SHORT_RUN && !CROSSED_W(wordCnt)) { \
*dst++ = OP_RUN; \
*dst++ = wordCnt; } \
else{ \
*dst++ = LONG_OP; \
*((UWORD far *)dst)++ = \
(LONG_RUN<<8) | (wordCnt); } \
*dst++ = runPix; \
}
local BOOL ComputeDeltaX(BOOL skipPermitted)
{
/* Inputs: */
UBYTE far *src1, far * src2, far * final2, far * dst;
UWORD width, finalDst;
/* src1 points to previous frame, src2 to screen, limit2 to final
* screen
* byte, dst points to "finalDst+1" byte buffer for Body,
* width is # pixels in a row. For 64 KB buffer, finalDst==0xFFFF.
*/
/* Local variables: */
UBYTE far *finalRow2, far * dst0;
UBYTE pixel, runPix, skipInDump, runInDump;
UWORD x, w, finalRowWidth, wordCnt, xWas;
BOOL inFinalRow, mustStop;
#if 0
UWORD deltaSize;
#endif
if (!deltaX.limit)
BugExit("LPJ"); /* "ComputeDelta -- deltaX.limit"); */
src1 = BYTE_FAR_PTR(prevFrameSeg, 0);
src2 = BYTE_FAR_PTR(hidbmSeg, 0);
final2 = BYTE_FAR_PTR(hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1);
dst = BYTE_FAR_PTR(deltaX.seg, deltaX.addr);
width = hidbm.box.w;
finalDst = deltaX.limit;
w = width;
finalRowWidth = (UWORD)(final2 - src2 + 1) % width;
if (!finalRowWidth)
finalRowWidth = width; /* # pixels in final row. */
finalRow2 = final2 - (finalRowWidth - 1); /* Ptr to start of final
* row. */
inFinalRow = (finalRow2 == src2);
/* FALSE unless src2 is a single row. */
if (inFinalRow)
w = finalRowWidth; /* In case single-row src is shorter than
* width. */
#if MRSD_ASM /* RunSkipDump in assembly. */
return MakeRunSkipDump(prevFrameSeg, hidbmSeg, NBYTES_IN_BITMAP(&hidbm) - 1,
deltaX.seg, deltaX.addr, deltaX.limit, &deltaX.nBytes,
w, FP_OFF(finalRow2), finalRowWidth, inFinalRow, skipPermitted);
#else /* ---- RunSkipDump in C. --- most likely out of date ------ */
dst0 = dst; /* Remember, so can calculate Body size. */
*(UWORD far *) dst = BMBODY_RUNSKIPDUMP; /* 2-byte header with type. */
dst += 2;
x = 0; /* Keep track of pixel-position, so break
* at row end. */
mustStop = FALSE;
notInASequence:
wordCnt = 0;
/*--- "pixel" must be set when jump to beDump with "wordCnt != 0".*/
beDump:
xWas = x + width - wordCnt; /* Where in line dump started. */
if (xWas >= width)
xWas -= width;
/* "width" logic in case x < wordCnt, because already
* line-wrapped.
*/
if (FP_OFF(dst) > finalDst - 3)
goto incompressible;
/* "3" is longDump overhead. Dump DATA is checked inside
* PUT_DUMP.
*/
skipInDump = 0;
runInDump = (wordCnt ? 1 : 0); /* Any pixel is run==1. */
inDump:
CLIP_X(stopDump); /* If mustStop, goto to "stopDump" to get
* final dump. */
runPix = pixel;
pixel = *src2++;
x++;
wordCnt++; /* Get&count byte. */
if (pixel == *src1++)
skipInDump++; /* # skippable bytes. */
else
skipInDump = 0;
if (pixel == runPix)
runInDump++; /* # runnable bytes. */
else
runInDump = 1; /* Any pixel is run==1. */
if (skipInDump == MIN_SKIP && skipPermitted) { /* Minimum worthwhile
* skip. */
PUT_DUMP(src2 - wordCnt, wordCnt - MIN_SKIP);
wordCnt = MIN_SKIP;
goto beSkip; /* "Skip 2" so far. */
}
if (runInDump == MIN_RUN) { /* Minimum worthwhile run. */
PUT_DUMP(src2 - wordCnt, wordCnt - MIN_RUN);
wordCnt = MIN_RUN;
goto beRun; /* "Run 4" so far. */
}
/* --- Byte still in Dump. */
if (wordCnt == MAX_LONG_DUMP) {
stopDump:
PUT_DUMP(src2 - wordCnt, wordCnt);
if (mustStop)
goto stop;
goto notInASequence;
}
goto inDump;
beRun:
xWas = x + width - wordCnt; /* Where in line run started. */
if (xWas >= width)
xWas -= width;
if (FP_OFF(dst) > finalDst - 4)
goto incompressible; /* "4" is length of longRun. */
skipInDump = 0; /* ACTUALLY skip is in run, not in dump... */
runPix = pixel; /* Only need do once per run. */
inRun:
CLIP_X(stopRun); /* If mustStop, goto to "stopRun" to get
* final run. */
pixel = *src2++;
x++;
wordCnt++; /* Get byte & count it. */
if (pixel == *src1++)
skipInDump++; /* # skippable bytes. */
else
skipInDump = 0;
/* Note: check for byte runnable comes later. */
if (skipInDump == MIN_RUN_SKIP && skipPermitted) {
wordCnt -= MIN_RUN_SKIP;
PUT_RUN(wordCnt, runPix);
wordCnt = MIN_RUN_SKIP;
goto beSkip; /* "Skip 4" so far. */
}
if (pixel != runPix) { /* Byte not runnable -- requires Dump. */
wordCnt--; /* Retract pixel from Run. */
PUT_RUN(wordCnt, runPix);
wordCnt = 1;
goto beDump; /* "Dump 1" so far. */
}
/* --- Byte runnable. */
if (wordCnt == MAX_LONG_RUN) {
stopRun:
PUT_RUN(wordCnt, runPix);
if (mustStop)
goto stop;
goto notInASequence;
}
goto inRun;
beSkip:
xWas = x + width - wordCnt; /* Where in line skip started. */
if (xWas >= width)
xWas -= width;
if (FP_OFF(dst) > finalDst - 3)
goto incompressible; /* "3" is length of LongSkip. */
inSkip:
runPix = pixel;
pixel = *src2++;
x++;
wordCnt++; /* Get byte & count it. */
if (pixel != *src1++) { /* Byte not skippable. */
wordCnt--; /* Retract pixel from Skip. */
if (wordCnt <= MAX_SHORT_SKIP) {
*dst++ = OP_SKIP | wordCnt;
} else if (wordCnt <= 2 * MAX_SHORT_SKIP) {
*dst++ = OP_SKIP | MAX_SHORT_SKIP;
wordCnt -= MAX_SHORT_SKIP;
*dst++ = OP_SKIP | wordCnt;
} else {
UWORD wordCnt0 = wordCnt;
while (wordCnt0) {
wordCnt = MIN(wordCnt0, MAX_LONG_SKIP);
wordCnt0 -= wordCnt;
*dst++ = LONG_OP;
*dst++ = (UBYTE)wordCnt; /* Low byte of UWORD. */
*dst++ = wordCnt >> 8; /* High byte of UWORD. */
}
}
wordCnt = 1;
goto beDump; /* "Dump 1" so far. */
}
/* --- Byte skippable. */
/* --- Final Skip is NOT output prior to Stop. */
CLIP_X(stop);
goto inSkip; /* Skip may be any length up to 64KB-1. */
stop:
if (FP_OFF(dst) > finalDst - 3)
goto incompressible; /* "3" is length of Stop. */
*dst++ = LONG_OP; /* Stop represented as "LongOp #0". */
/* 68000 note: Don't use word op to put cnt, as may not be
* word-aligned.
*/
*dst++ = 0; /* High byte of cnt==0. */
*dst++ = 0; /* Low byte of cnt==0. */
deltaX.nBytes = dst - dst0;
/* "body size": # bytes in Body, excluding any header.
* NOTE: incompressible tests guarantee that deltaX.nBytes won't quite
* reach 64 KB, which would overflow UWORD.
*/
return TRUE; /* deltaX.nBytes is set as a side-effect. */
/* NOTE: if deltaX.nBytes == 3, all there is is a Stop.
* Client may wish to set deltaX.nBytes == 0 & have no data in this
* case.
*/
incompressible:
return FALSE;
#endif /* RunSkipDump in C. */
}
/* --------------- AbandonChangesMaybeExit ---------------
*/
local void AbandonChangesMaybeExit(BOOL closeAndExit)
{
CUR_LP = (UWORD)-1; /* None loaded -- abandon lpBuffer. */
curLpIsDirty = FALSE;
/* --- Drastic solution -- don't try to continue. */
if (closeAndExit)
DiskFullChangesLostExit(); /* closes lp. */
}
/* --------------- FindFreeLp ---------------
*/
local UWORD FindFreeLp(void)
{
register UWORD nLp;
for (nLp = 0; nLp < nLps; nLp++) {
if (lpfTable[nLp].nRecords == 0)
return nLp; /* Found empty lp in middle of file. */
}
if (nLps == MAX_LARGE_PAGE) {
BugExit("LPK"); /* "Too many pages in anim"); */
}
return nLps++; /* Page # of next lp to use (lps #d from 0). */
}
/* --------------- LpTableToLpBufferHeader ---------------
* Assuming space made available, put correct contents in header,
* based on lpTable & variables.
* ASSUMES LP_N_RECS & LP_N_BYTES has already been changed to reflect # recs
* & new contents in lp.
* Copy these changes to lpfTable, so FindLargePageForRecord will see truth.
*/
local void LpTableToLpBufferHeader(void)
{
far_movmem(dataseg(), (UWORD)&curLpX.x, lpSeg, 0, sizeof(LP_DESCRIPTOR));
*WORD_FAR_PTR(lpSeg, sizeof(LPageHeaderFixed) - 2) = 0; /* no cont'. */
/* ASSUMES nByteContinuation is last field. */
far_movmem(dataseg(), (UWORD)lpTable, lpSeg, sizeof(LPageHeaderFixed),
sizeof(LP_TABLE_ELEMENT) * LP_N_RECS);
lpfTable[CUR_LP] = curLpX.x; /* IMPORTANT! */
}
/* --------------- UpdateLpBufferHeader ---------------
* Shift the data in the lp buffer, and re-write the header, so that it
* matches lpTable.
*/
local void UpdateLpBufferHeader(void)
{
UWORD newRecAddr0 = LP_HEADER_SIZE(LP_N_RECS);
far_movmem_same_seg(lpSeg, LP_REC_ADDR0, newRecAddr0, LP_N_BYTES);
/* --- "current record" info has changed location. */
REC_ADDR = REC_ADDR - LP_REC_ADDR0 + newRecAddr0;
BODY_ADDR = BODY_ADDR - LP_REC_ADDR0 + newRecAddr0;
LP_REC_ADDR0 = newRecAddr0;
LpTableToLpBufferHeader();
curLpIsDirty = TRUE;
}
/* --------------- WriteNewLp ---------------
* Extract a portion of lp buffer as a new lp.
* Return FAIL if trouble writing (presumably, disk is full).
*/
#define SV_BASE_REC svLpX.x.baseRecord
local WORD WriteNewLp(UWORD startRecord, UWORD endRecord)
{
register UWORD nRecord;
WORD result;
LpX svLpX;
svLpX = curLpX;
CUR_LP = FindFreeLp();
lpTableOffset = startRecord - SV_BASE_REC;
/* Fake out WriteCurLp. */
/* NOTE: need unmodified startRecord & endRecord for this. */
LP_BASE_REC = startRecord;
LP_N_RECS = endRecord - startRecord + 1;
startRecord -= SV_BASE_REC;
endRecord -= SV_BASE_REC;
/* NOTE: use startRecord/endRecord RELATIVE to SV_BASE_REC. */
/* --- Get from real LP_REC_ADDR0 to fake LP_REC_ADDR0 (at
* startRecord).
*/
for (nRecord = 0; nRecord < startRecord; nRecord++) {
LP_REC_ADDR0 += lpTable[nRecord];
}
/* --- Sum of bytes for desired records. */
LP_N_BYTES = 0; /* So far */
for (nRecord = startRecord; nRecord <= endRecord; nRecord++) {
LP_N_BYTES += lpTable[nRecord];
}
result = WriteCurLp(); /* New lp. */
lpTableOffset = 0; /* Done faking out WriteCurLp. */
curLpX = svLpX; /* Restore access to "current lp". */
/* Including LP_REC_ADDR0! */
return result;
}
/* --------------- CurRec_NewBody ---------------
* Place a new body into CurRec. If necessary, records before and/or after
* current are moved into new lps.
* IMPLICIT INPUT: curRecX, deltaX, lpSeg, curLpX.
*/
local void CurRec_NewBody(void)
{
WORD result = SUCCESS;
/* 1. Determine how much room for new body. */
#define AVAIL_BODY_SIZE (GAP_SIZE + BODY_BYTES)
UWORD availBodySize = AVAIL_BODY_SIZE;
BOOL newExtraBytes = (EXTRA_BYTES == 0);
/* If there was no record at all, create an ID+FLAGS UWORD. */
if (newExtraBytes)
availBodySize -= 2; /* For ID+FLAGS. */
/* 2A. If won't fit, move other records to new lps. */
if (deltaX.nBytes > availBodySize) {
/* --- Dumbest possible approach: split into 3 buffers. */
UWORD newRecAddr0 = LP_HEADER_SIZE(1);
/* --- Following records. */
if (CUR_REC != LAST_RECORD_IN_BUF && result == SUCCESS) {
result = WriteNewLp(CUR_REC + 1, LAST_RECORD_IN_BUF);
/* if (result != SUCCESS) goto fail; TBD... */
/* Cur lp now has just base-rec thru cur-rec. */
LP_N_RECS = (CUR_REC + 1) - LP_BASE_REC;
LP_N_BYTES = (REC_ADDR + REC_BYTES) - LP_REC_ADDR0;
UpdateLpBufferHeader();
availBodySize = AVAIL_BODY_SIZE;
if (newExtraBytes)
availBodySize -= 2; /* For ID+FLAGS. */
if (deltaX.nBytes <= availBodySize)
goto nowFits;
}
if (CUR_REC > LP_BASE_REC && result == SUCCESS)
result = WriteNewLp(LP_BASE_REC, CUR_REC - 1); /* Previous records */
if (result != SUCCESS) {
/* TBD: Don't use new lps. */
BugExit("NewLp");
}
/* --- Prepare lp with just CUR_REC. */
LP_BASE_REC = CUR_REC;
LP_N_RECS = 1;
if (newExtraBytes)
EXTRA_BYTES = 2;
else
far_movmem(lpSeg, REC_ADDR, lpSeg, newRecAddr0, EXTRA_BYTES);
BODY_BYTES = deltaX.nBytes;
LP_REC_ADDR0 = REC_ADDR = newRecAddr0;
BODY_ADDR = REC_ADDR + EXTRA_BYTES;
REC_BYTES = EXTRA_BYTES + BODY_BYTES;
LP_N_BYTES = lpTable[0] = REC_BYTES;
LpTableToLpBufferHeader(); /* Buffer now has single frame in
* it! */
/* NOTE: should NOT do i/o,
* just preparing variables.
*/
} else { /* 2B. Make room for new body, & set
* variables. */
UWORD curRecLimit;
UWORD sizeChange;
nowFits:
curRecLimit = REC_ADDR + REC_BYTES;
sizeChange = deltaX.nBytes - BODY_BYTES;
/* NOTE: May be NEGATIVE number. Relying on 16-bit wrapping! */
#define BYTES_AFTER_BODY (LP_TOTAL_BYTES - curRecLimit)
if (newExtraBytes) {
EXTRA_BYTES = 2;
sizeChange += EXTRA_BYTES;
BODY_ADDR = REC_ADDR + EXTRA_BYTES;
/* No longer access old body. */
}
#define NEW_REC_LIM (curRecLimit + sizeChange)
/* ASSUMES body is last field in record. */
/* ASSUMES lp starts at addr 0 w/i lpSeg. */
far_movmem_same_seg(lpSeg, curRecLimit, NEW_REC_LIM, BYTES_AFTER_BODY);
BODY_BYTES = deltaX.nBytes; /* When newExtra, not same as
* sizeChange */
REC_BYTES += sizeChange;
LP_N_BYTES += sizeChange;
lpTable[CUR_REC - LP_BASE_REC] = REC_BYTES;
}
/* 3. Get new body into lp buffer. */
if (newExtraBytes)
*WORD_FAR_PTR(lpSeg, REC_ADDR) = DEFAULT_BITMAP_ID_FLAGS;
far_movmem(deltaX.seg, deltaX.addr, lpSeg, BODY_ADDR, BODY_BYTES);
curLpIsDirty = TRUE;
}
/* --------------- DAllocBiggest ---------------
* Allocate the requested block. If can't, find and allocate
* the biggest possible.
* TBD: Is there a DOS or BIOS call to find biggest available block?
* RETURNS seg # of allocation, NULL if failed.
* SIDE_EFFECT: sets "*allocSizeP" to # bytes actually allocated.
*/
local UWORD DAllocBiggest(UWORD nBytes, UWORD * allocSizeP)
{
UWORD seg, allocParas;
/* --- First, try to allocate full request, kicking out other stuff
* if necessary.
*/
seg = DAlloc(nBytes);
if (seg) {
*allocSizeP = nBytes;
return seg;
}
/* --- Couldn't get all, get what we can. */
#define N_PARAGRAPHS(nBytes) ( (nBytes >> 4) + (nBytes & 0x0f ? 1 : 0) )
/* NOTE: Don't do as "(nBytes+15) >> 4", since may overflow UWORD. */
seg = allocsome(N_PARAGRAPHS(nBytes), &allocParas);
*allocSizeP = allocParas << 4;
if (!seg) /* Only happens if memory completely full. */
*allocSizeP = 0;
return seg;
}
/* --------------- ComputeDelta ---------------
* Compute delta into a large temporary buffer.
* IMPLICIT INPUT: curRecX, lpSeg.
* SIDE_EFFECT: sets deltaX.
*/
local void ComputeDelta(void)
{
BOOL skipPermitted = (curFrame != FIRST_FRAME_N);
/* Disallow skip code on first frame, so don't need to blank
* frame before drawing delta -- will overwrite every pixel
* via Dumps and Runs.
*/
deltaX.seg = allocSeg;
deltaX.addr = 0;
deltaX.limit = MAX_LARGE_PAGE_SIZE;
bigEnough:
if (!ComputeDeltaX(skipPermitted)) {
/* Handle incompressible frame. */
UWORD rawSize = N_BYTES_IN_BITMAP;
deltaX.nBytes = rawSize + sizeof(UWORD);
/* UWORD for header, then raw dump of screen data. */
if (deltaX.limit - deltaX.addr < deltaX.nBytes)
BugExit("LPM");
/* "Buffer too small for incompressible frame"); */
*WORD_FAR_PTR(deltaX.seg, deltaX.addr) = BMBODY_UNCOMPRESSED;
/* header contains type "uncompressed". */
far_movmem(hidbmSeg, 0, deltaX.seg, deltaX.addr + 2, rawSize);
}
}
/* --------------- WriteFinalAnimFrame ---------------
* Compute and transfer to file the current delta-frame,
* which must be the last frame in the file.
* (If not last frame, must use WriteAnimFrameAndReadNext).
*/
void WriteFinalAnimFrame(void)
{
ComputeDelta();
CurRec_NewBody(); /* Implicit: deltaX. */
}
/* --------------- WriteAnimFrameAndReadNext ---------------
* Transfer to file the specified delta-frame.
* Also compute & transfer the next delta-frame, as it is affected by the
* changes in the current anim-frame.
* TBD: Some caller's may wish to MaybeWriteCurLp, so that lp buffer is
* free for immediate use by menus/pop-ups.
*/
void WriteAnimFrameAndReadNext(void)
{
ComputeDelta();
/* ---2. Exchange prevFrame with new-frame in hidbm.
* Because new-frame is next-frame's prevFrame.
*/
far_swapmem(BSEG(&hidbm), 0, prevFrameSeg, 0, N_BYTES_IN_BITMAP);
/* ---3. Apply old-delta to hidbm. (Convert prev- to old- frame.) */
PlayDeltaFrame(&hidbm); /* Implicit: curRecX, lpSeg. */
CurRec_NewBody(); /* Implicit: deltaX. */
/* ---4. If at anim end, simply set up first frame. */
/* FUTURE: may compute delta between last & first frame.
* Could store in special frame before the normal first frame.
*/
curFrame++;
if (curFrame > LAST_FRAME_N) {
FirstAnimFrame0();
goto done;
}
/* ---5. Read & apply next delta. */
ReadLpfFrame(curFrame);
PlayDeltaFrame(&hidbm); /* Convert old-frame to next-frame. */
/* ---6. Recompute next-frame's delta (since prevFrame is new.) */
ComputeDelta();
CurRec_NewBody();
done: ;
}
/* --------------- AddFrame ---------------
* ASSUMES current frame is last frame in lpfile.
* Add an identical frame after it, and make it current.
* NOTE: Be careful to leave valid cur rec & cur lp variables.
*/
void AddFrame()
{
UWORD afterRecord = CUR_REC; /* Hold, in case lp changes affect CUR_REC.*/
WORD afterFrame = curFrame;
if ( MAX_RECORDS_PER_LP - LP_N_RECS < 1 || /* lpTable overflow*/
MAX_LARGE_PAGE_SIZE - LP_N_BYTES <
LP_HEADER_SIZE(LP_N_RECS+1) /* lp overflow */
) {
/* --- Create new lp, with only an empty frame. */
MaybeWriteCurLp();
LP_BASE_REC = afterRecord + 1;
LP_N_BYTES = 0;
setmem( &lpTable[0], sizeof(LP_TABLE_ELEMENT), 0 );
/* Zero the new frame. */
/* --- SWITCH ACCESS from current lp to new lp. */
CUR_LP = FindFreeLp();
LP_N_RECS = 1;
WriteCurLp();
} else {
/* Add frame to current lp.
* Zero the new frame. record # relative to CUR_LP's base rec.
*/
lpTable[afterRecord+1 - LP_BASE_REC] = 0;
LP_N_RECS += 1;
}
totalNRecords += 1;
AnimFramesX(animFrames + 1);
UpdateLpBufferHeader();
ReadLpfFrame(curFrame = afterFrame+1); /* Make this frame current. */
}
/* --------------- ConvertFinalFrameToLastDelta ---------------
*/
ConvertFinalFrameToLastDelta()
{
animFrames--;
lpfHdr.hasLastDelta = lpfHdr.lastDeltaValid = TRUE;
curFrame--;
}