-
Notifications
You must be signed in to change notification settings - Fork 2
/
LPFILE.C
1929 lines (1635 loc) · 56.1 KB
/
LPFILE.C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*--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;