{$G+} Program FliPlayer; { v1.1 made by Thaco } { (c) EPOS, August 1992 } Const CLOCK_HZ =4608; { Frequency of clock } MONItoR_HZ =70; { Frequency of monitor } CLOCK_SCALE =CLOCK_HZ div MONItoR_HZ; BUFFERSIZE =$FFFE; { Size of the framebuffer, must be an even number } CDATA =$040; { Port number of timer 0 } CMODE =$043; { Port number of timers control Word } CO80 =$3; { Number For standard Text mode } KEYBOARD =28; { Numbers returned by PorT[$64] indicating what hardware caused inT 09/the - } MOUSE =60; { - number on PorT[$60] } MCGA =$13; { Number For MCGA mode } MCGACheck:Boolean =True; { Variable For MCGA checking } UseXMS:Boolean =True; { Variable For XMS usage } XMSError:Byte =0; { Variable indicating the errornumber returned from the last XMS operation } Type EMMStructure =Record BytestoMoveLo, { Low Word of Bytes to move. NB: Must be even! } BytestoMoveHi, { High Word of Bytes to move } SourceHandle, { Handle number of source (SH=0 => conventional memory) } SourceoffsetLo, { Low Word of source offset, or ofS if SH=0 } SourceoffsetHi, { High Word of source offset, or SEG if SH=0 } DestinationHandle, { Handle number of destination (DH=0 => conventional memory) } DestinationoffsetLo, { Low Word of destination offset, or ofS if DH=0 } DestinationoffsetHi :Word; { High Word of destination offset, or SEG if DH=0 } end; HeaderType =Array[0..128] of Byte; { A bufferType used to read all kinds of headers } Var Key, { Variable used to check if a key has been pressed } OldKey :Byte; { Variable used to check if a key has been pressed } XMSRecord :EMMStructure; { Variable For passing values to the XMS routine } InputFile :File; { Variable For the incomming .FLI File } Header :HeaderType; { Buffer used to read all kinds of headers } Counter, { General purpose counter } Speed :Integer; { Timedifference in video tics from one frame to the next } FileCounter, { Variable telling the point to read from in the File stored in XMS } FileSize, { Size of the .FLI-File } FrameSize, { Variable indicating the datasize of current frame } NextTime, { Variable saying when it is time to move on to the next frame } TimeCounter, { Holding the current time in video tics } SecondPos :LongInt; { Number of Bytes to skip from the start of the .FLI File when starting - } { - from the beginning again } Buffer, { Pointer to the Framebuffer } XMSEntryPoint :Pointer; { Entry point of the XMS routine in memory } SpeedString :String[2]; { String used to parse the -sNN command } FileName :String[13]; { String holding the name of the .FLI-File } BufferHandle, { Handle number returned from the XMS routine } BytesRead, { Variable telling the numbers of Bytes read from the .FLI File } FrameNumber, { Number of the current frame } Frames, { total number of frames } Chunks :Word; { total number of chunks in a frame } Function UpCaseString(Streng:String):String; { takes a String and convert all letters to upperCase } Var DummyString :String; Counter :Integer; begin DummyString:=''; For Counter:=1 to Length(Streng) do DummyString:=DummyString+UpCase(Streng[Counter]); UpCaseString:=DummyString; end; Procedure InitMode(Mode:Word); Assembler; { Uses BIOS interrupts to set a videomode } Asm mov ax,Mode int 10h end; Function ModeSupport(Mode:Word):Boolean; Assembler; { Uses BIOS interrupts to check if a videomode is supported } Label Exit, Last_Modes, No_Support, Supported; Var DisplayInfo :Array[1..64] of Byte; { Array For storing Functionality/state inFormation } Asm push es mov ah,1Bh { the Functionality/state inFormation request at int 10h } mov bx,0 { 0 = return Functionality/state inFormation } push ds { push DS on the stack and pop it into ES so ES:DI could be used to - } pop es { - address DisplayInfo, as demanded of the interrupt Function } mov di,offset DisplayInfo int 10h les di,[dWord ptr es:di] { The first dWord in the buffer For state inFormation is the address - } { - of static funtionality table } mov cx,Mode { Can only check For the 0h-13h modes } cmp cx,13h ja No_Support { Return 'no support' For modes > 13h } mov ax,1 { Shift the right Byte the right - } { - times and test For the right - } cmp cx,10h { - bit For knowing if the - } jae Last_Modes { - videomode is supported - } { - } shl ax,cl { - } test ax,[Word ptr es:di+0] { - } jz No_Support { - } jmp Supported { - } { - } Last_Modes: { - } sub cx,10h { - } shl ax,cl { - } test al,[Byte ptr es:di+2] { - } jz No_Support { - } Supported: mov al,1 { AL=1 makes the Function return True } jmp Exit No_Support: mov al,0 { AL=0 makes the Function return True } Exit: pop es end; Function NoXMS:Boolean; Assembler; { checks out if there is a XMS driver installed, and in Case it initialize the XMSEntryPoint Variable } Label JumpOver; Asm push es mov ax,4300h { AX = 4300h => inSTALLATION CHECK } int 2Fh { use int 2Fh Extended MEMorY SPECifICATION (XMS) } mov bl,1 { use BL as a flag to indicate success } cmp al,80h { is a XMS driver installed? } jne JumpOver mov ax,4310h { AX = 4310h => GET DRIVER ADDRESS } int 2Fh mov [Word ptr XMSEntryPoint+0],BX { initialize low Word of XMSEntryPoint } mov [Word ptr XMSEntryPoint+2],ES { initialize high Word of XMSEntryPoint } mov bl,0 { indicate success } JumpOver: mov al,bl { make the Function return True (AH=1) or False (AH=0) } pop es end; Function XMSMaxAvail:Word; Assembler; { returns size of largest contiguous block of XMS in kilo (1024) Bytes } Label JumpOver; Asm mov ah,08h { 'Query free Extended memory' Function } mov XMSError,0 { clear error Variable } call [dWord ptr XMSEntryPoint] or ax,ax { check For error } jnz JumpOver mov XMSError,bl { errornumber stored in BL } JumpOver: { AX=largest contiguous block of XMS } end; Function XMSGetMem(SizeInKB:Word):Word; Assembler; { allocates specified numbers of kilo (1024) Bytes of XMS and return a handle to this XMS block } Label JumpOver; Asm mov ah,09h { 'Allocate Extended memory block' Function } mov dx,SizeInKB { number of KB requested } mov XMSError,0 { clear error Variable } call [dWord ptr XMSEntryPoint] or ax,ax { check For error } jnz JumpOver mov XMSError,bl { errornumber stored in BL } JumpOver: mov ax,dx { return handle number to XMS block } end; Procedure XMSFreeMem(Handle:Word); Assembler; Label JumpOver; Asm mov ah,0Ah { 'Free Extended memory block' Function } mov dx,Handle { XMS's handle number to free } mov XMSError,0 { clear error Variable } call [dWord ptr XMSEntryPoint] or ax,ax { check For error } jnz JumpOver mov XMSError,bl { errornumber stored in BL } JumpOver: end; Procedure XMSMove(Var EMMParamBlock:EMMStructure); Assembler; Label JumpOver; Asm push ds push es push ds pop es mov ah,0Bh { 'Move Extended memory block' Function } mov XMSError,0 { clear error Variable } lds si,EMMParamBlock { DS:SI -> data to pass to the XMS routine } call [dWord ptr es:XMSEntryPoint] or ax,ax { check For error } jnz JumpOver mov XMSError,bl { errornumber stored in BL } JumpOver: pop es pop ds end; Procedure ExitDuetoXMSError; begin InitMode(CO80); WriteLn('ERRor! XMS routine has reported error ',XMSError); XMSFreeMem(BufferHandle); Halt(0); end; Procedure GetBlock(Var Buffer; Size:Word); { reads a specified numbers of data from a diskFile or XMS into a buffer } Var XMSRecord :EMMStructure; NumberofBytes :Word; begin if UseXMS then begin NumberofBytes:=Size; if Size MOD 2=1 then Inc(NumberofBytes); { one must allways ask For a EQUAL number of Bytes } With XMSRecord do begin BytestoMoveLo :=NumberofBytes; BytestoMoveHi :=0; SourceHandle :=BufferHandle; SourceoffsetLo :=FileCounter MOD 65536; SourceoffsetHi :=FileCounter div 65536; DestinationHandle :=0; DestinationoffsetLo:=ofs(Buffer); DestinationoffsetHi:=Seg(Buffer); end; XMSMove(XMSRecord); if XMSError<>0 then ExitDuetoXMSError; Inc(FileCounter,Size); end else BlockRead(InputFile,Buffer,Size); end; Procedure InitClock; Assembler; {Taken from the FLILIB source} Asm mov al,00110100b { put it into liNear count instead of divide by 2 } out CMODE,al xor al,al out CDATA,al out CDATA,al end; Function GetClock:LongInt; Assembler; {Taken from the FLILIB source} { this routine returns a clock With occassional spikes where time will look like its running backwards 1/18th of a second. The resolution of the clock is 1/(18*256) = 1/4608 second. 66 ticks of this clock are supposed to be equal to a monitor 1/70 second tick.} Asm mov ah,0 { get tick count from Dos and use For hi 3 Bytes } int 01ah { lo order count in DX, hi order in CX } mov ah,dl mov dl,dh mov dh,cl mov al,0 { read lo Byte straight from timer chip } out CMODE,al { latch count } mov al,1 out CMODE,al { set up to read count } in al,CDATA { read in lo Byte (and discard) } in al,CDATA { hi Byte into al } neg al { make it so counting up instead of down } end; Procedure TreatFrame(Buffer:Pointer;Chunks:Word); Assembler; { this is the 'workhorse' routine that takes a frame and put it on the screen } { chunk by chunk } Label Color_Loop, Copy_Bytes, Copy_Bytes2, Exit, Fli_Black, Fli_Brun, Fli_Color, Fli_Copy, Fli_Lc, Fli_Loop, Jump_Over, Line_Loop, Line_Loop2, Next_Line, Next_Line2, Pack_Loop, Pack_Loop2; Asm cli { disable interrupts } push ds push es lds si,Buffer { let DS:SI point at the frame to be drawn } Fli_Loop: { main loop that goes through all the chunks in a frame } cmp Chunks,0 { are there any more chunks to draw? } je Exit dec Chunks { decrement Chunks For the chunk to process now } mov ax,[Word ptr ds:si+4] { let AX have the ChunkType } add si,6 { skip the ChunkHeader } cmp ax,0Bh { is it a FLI_COLor chunk? } je Fli_Color cmp ax,0Ch { is it a FLI_LC chunk? } je Fli_Lc cmp ax,0Dh { is it a FLI_BLACK chunk? } je Fli_Black cmp ax,0Fh { is it a FLI_BRUN chunk? } je Fli_Brun cmp ax,10h { is it a FLI_COPY chunk? } je Fli_Copy jmp Fli_Loop { This command should not be necessary since the Program should make one - } { - of the other jumps } Fli_Color: mov bx,[Word ptr ds:si] { number of packets in this chunk (allways 1?) } add si,2 { skip the NumberofPackets } mov al,0 { start at color 0 } xor cx,cx { reset CX } Color_Loop: or bx,bx { set flags } jz Fli_Loop { Exit if no more packages } dec bx { decrement NumberofPackages For the package to process now } mov cl,[Byte ptr ds:si+0] { first Byte in packet tells how many colors to skip } add al,cl { add the skiped colors to the start to get the new start } mov dx,$3C8 { PEL Address Write Mode Register } out dx,al { tell the VGA card what color we start changing } inc dx { at the port abow the PEL_A_W_M_R is the PEL Data Register } mov cl,[Byte ptr ds:si+1] { next Byte in packet tells how many colors to change } or cl,cl { set the flags } jnz Jump_Over { if NumberstoChange=0 then NumberstoChange=256 } inc ch { CH=1 and CL=0 => CX=256 } Jump_Over: add al,cl { update the color to start at } mov di,cx { since each color is made of 3 Bytes (Red, Green & Blue) we have to - } shl cx,1 { - multiply CX (the data counter) With 3 } add cx,di { - CX = old_CX shl 1 + old_CX (the fastest way to multiply With 3) } add si,2 { skip the NumberstoSkip and NumberstoChange Bytes } rep outsb { put the color data to the VGA card FAST! } jmp Color_Loop { finish With this packet - jump back } Fli_Lc: mov ax,0A000h mov es,ax { let ES point at the screen segment } mov di,[Word ptr ds:si+0] { put LinestoSkip into DI - } mov ax,di { - to get the offset address to this line we have to multiply With 320 - } shl ax,8 { - DI = old_DI shl 8 + old_DI shl 6 - } shl di,6 { - it is the same as DI = old_DI*256 + old_DI*64 = old_DI*320 - } add di,ax { - but this way is faster than a plain mul } mov bx,[Word ptr ds:si+2] { put LinestoChange into BX } add si,4 { skip the LinestoSkip and LinestoChange Words } xor cx,cx { reset cx } Line_Loop: or bx,bx { set flags } jz Fli_Loop { Exit if no more lines to change } dec bx mov dl,[Byte ptr ds:si] { put PacketsInLine into DL } inc si { skip the PacketsInLine Byte } push di { save the offset address of this line } Pack_Loop: or dl,dl { set flags } jz Next_Line { Exit if no more packets in this line } dec dl mov cl,[Byte ptr ds:si+0] { put BytestoSkip into CL } add di,cx { update the offset address } mov cl,[Byte ptr ds:si+1] { put BytesofDatatoCome into CL } or cl,cl { set flags } jns Copy_Bytes { no SIGN means that CL number of data is to come - } { - else the next data should be put -CL number of times } mov al,[Byte ptr ds:si+2] { put the Byte to be Repeated into AL } add si,3 { skip the packet } neg cl { Repeat -CL times } rep stosb jmp Pack_Loop { finish With this packet } Copy_Bytes: add si,2 { skip the two count Bytes at the start of the packet } rep movsb jmp Pack_Loop { finish With this packet } Next_Line: pop di { restore the old offset address of the current line } add di,320 { offset address to the next line } jmp Line_Loop Fli_Black: mov ax,0A000h mov es,ax { let ES:DI point to the start of the screen } xor di,di mov cx,32000 { number of Words in a screen } xor ax,ax { color 0 is to be put on the screen } rep stosw jmp Fli_Loop { jump back to main loop } Fli_Brun: mov ax,0A000h mov es,ax { let ES:DI point at the start of the screen } xor di,di mov bx,200 { numbers of lines in a screen } xor cx,cx Line_Loop2: mov dl,[Byte ptr ds:si] { put PacketsInLine into DL } inc si { skip the PacketsInLine Byte } push di { save the offset address of this line } Pack_Loop2: or dl,dl { set flags } jz Next_Line2 { Exit if no more packets in this line } dec dl mov cl,[Byte ptr ds:si] { put BytesofDatatoCome into CL } or cl,cl { set flags } js Copy_Bytes2 { SIGN meens that CL number of data is to come - } { - else the next data should be put -CL number of times } mov al,[Byte ptr ds:si+1] { put the Byte to be Repeated into AL } add si,2 { skip the packet } rep stosb jmp Pack_Loop2 { finish With this packet } Copy_Bytes2: inc si { skip the count Byte at the start of the packet } neg cl { Repeat -CL times } rep movsb jmp Pack_Loop2 { finish With this packet } Next_Line2: pop di { restore the old offset address of the current line } add di,320 { offset address to the next line } dec bx { any more lines to draw? } jnz Line_Loop2 jmp Fli_Loop { jump back to main loop } Fli_Copy: mov ax,0A000h mov es,ax { let ES:DI point to the start of the screen } xor di,di mov cx,32000 { number of Words in a screen } rep movsw jmp Fli_Loop { jump back to main loop } Exit: sti { enable interrupts } pop es pop ds end; begin WriteLn; WriteLn('.FLI-Player v1.1 by Thaco'); WriteLn(' (c) EPOS, August 1992'); WriteLn; if ParamCount=0 then { if no input parameters then Write the 'usage Text' } begin WriteLn('USAGE: FLIPLAY '); WriteLn(' '+#24+' '+#24); WriteLn(' ³ ÀÄÄ Filename of .FLI File'); WriteLn(' ÀÄÄÄÄÄÄÄÄÄÄÄÄ -d = Do not use XMS'); WriteLn(' -i = InFormation about the Program'); WriteLn(' -n = No checking of MCGA mode support'); WriteLn(' -sNN = Set playspeed to NN video ticks (0-99)'); WriteLn(' ( NN=70 ÷ frame Delay of 1 second )'); Halt(0); end; For Counter:=1 to ParamCount do { search through the input parameters For a -Info option } if Pos('-I',UpCaseString(ParamStr(Counter)))<>0 then begin WriteLn('Program inFormation:'); WriteLn('This Program plays animations (sequences of pictures) made by Programs like',#10#13, 'Autodesk Animator (so called .FLI-Files). The Program decodes the .FLI File,',#10#13, 'frame by frame, and Uses the systemclock For mesuring the time-Delay between',#10#13, 'each frame.'); WriteLn('Basis For the Program was the FliLib package made by Jim Kent, but since the',#10#13, 'original source was written in C, and I am not a good C-Writer, I decided',#10#13, 'to Write my own .FLI-player in Turbo Pascal v6.0.'); WriteLn('This Program was made by Eirik Milch Pedersen (thaco@solan.Unit.no).'); WriteLn('Copyright Eirik Pedersens Own SoftwareCompany (EPOS), August 1992'); WriteLn; WriteLn('Autodesk Animator is (c) Autodesk Inc'); WriteLn('FliLib is (c) Dancing Flame'); WriteLn('Turbo Pascal is (c) Borland International Inc'); Halt(0); end; Speed:=-1; Counter:=1; While (Copy(ParamStr(Counter),1,1)='-') and (ParamCount>=Counter) do { search through the input parameters to assemble them } begin if Pos('-D',UpCaseString(ParamStr(Counter)))<>0 then { do not use XMS For storing the File into memory } UseXMS:=False else if Pos('-N',UpCaseString(ParamStr(Counter)))<>0 then { do not check For a vga card present } MCGACheck:=False else if Pos('-S',UpCaseString(ParamStr(Counter)))<>0 then { speed override has been specified } begin SpeedString:=Copy(ParamStr(Counter),3,2); { cut out the NN parameter } if not(SpeedString[1] in ['0'..'9']) or { check if the NN parameter is legal } (not(SpeedString[2] in ['0'..'9',' ']) and (Length(SpeedString)=2)) then begin WriteLn('ERRor! Can not parse speed ''',SpeedString,'''.'); Halt(0); end; Speed:=Byte(SpeedString[1])-48; { take the first number, in ASCII, and convert it to a standard number } if Length(SpeedString)=2 then { if there is two numbers then multiply the first With 10 and add the next } Speed:=Speed*10+Byte(SpeedString[2])-48; Speed:=Speed*CLOCK_SCALE; { convert the speed to number of clock tics } end; Inc(Counter); end; if ParamCount0 then { has an error occured during opening the File? } begin WriteLn('ERRor! Can not open File ''',FileName,'''.'); Halt(0); end; if not(MCGACheck) or ModeSupport(MCGA) then InitMode(MCGA) else begin WriteLn('ERRor! Video mode 013h - 320x200x256 colors - is not supported.'); Halt(0); end; BlockRead(InputFile,Header,128); { read the .FLI main header } if not((Header[4]=$11) and (Header[5]=$AF)) then { check if the File has got the magic number } begin InitMode(CO80); WriteLn('ERRor! File ''',FileName,''' is of a wrong File Type.'); Halt(0); end; if NoXMS then { if no XMS driver present then do not use XMS } UseXMS:=False; if UseXMS then begin FileSize:=Header[0]+256*(LongInt(Header[1])+256*(LongInt(Header[2])+256*LongInt(Header[3]))); if XMSMaxAvail<=(FileSize+1023) SHR 10 then { is there enough XMS (rounded up to Nearest KB) availible? } begin WriteLn('ERRor! not enough XMS For the File'); Halt(0); end else begin Seek(InputFile,0); { skip back to start of .FLI-File to put it all into XMS } BufferHandle:=XMSGetMem((FileSize+1023) SHR 10); { allocate XMS For the whole .FLI File } FileCounter:=0; Repeat BlockRead(InputFile,Buffer^,BUFFERSIZE,BytesRead); { read a part from the .FLI File } if BytesRead MOD 2=1 then { since BUFFERSIZE shoud be an even number, the only time this triggers is the last part } Inc(BytesRead); { must be done because the XMS routine demands an even number of Bytes to be moved } if BytesRead<>0 then begin With XMSRecord do { put data into the XMSRecord } begin BytestoMoveLo :=BytesRead; BytestoMoveHi :=0; SourceHandle :=0; SourceoffsetLo :=ofs(Buffer^); SourceoffsetHi :=Seg(Buffer^); DestinationHandle :=BufferHandle; DestinationoffsetLo:=FileCounter MOD 65536; DestinationoffsetHi:=FileCounter div 65536; end; XMSMove(XMSRecord); { move Bytes to XMS } if XMSError<>0 then { have any XMS errors occured? } ExitDuetoXMSError; Inc(FileCounter,BytesRead); { update the offset into XMS where to put the next Bytes } end; Until BytesRead<>BUFFERSIZE; { Repeat Until Bytes read <> Bytes tried to read => end of File } end; FileCounter:=128; { we continue (after reading the .FLI File into XMS) right after the .FLI main header } end; Frames:=Header[6]+Header[7]*256; { get the number of frames from the .FLI-header } if Speed=-1 then { if speed is not set by a speed override then get it from the .FLI-header } Speed:=(Header[16]+Integer(Header[17])*256)*CLOCK_SCALE; InitClock; { initialize the System Clock } OldKey:=PorT[$60]; { get the current value from the keyboard } Key:=OldKey; { and set the 'current key' Variable to the same value } GetBlock(Header,16); { read the first frame-header } FrameSize:=Header[0]+256*(LongInt(Header[1])+256*(LongInt(Header[2])+256*LongInt(Header[3])))-16; { calculate framesize } SecondPos:=128+16+FrameSize; { calculate what position to skip to when the .FLI is finished and is going to start again - } { the position = .FLI-header + first_frame-header + first_framesize } Chunks:=Header[6]+Header[7]*256; { calculate number of chunks in frame } GetBlock(Buffer^,FrameSize); { read the frame into the framebuffer } TreatFrame(Buffer,Chunks); { treat the first frame } TimeCounter:=GetClock; { get the current time } { The first frame must be handeled separatly from the rest. This is because the rest of the frames are updates/changes of the first frame. At the end of the .FLI-File there is one extra frame who handles the changes from the last frame to the first frame. } Repeat FrameNumber:=1; { we start at the first frame (after the initial frame) } Repeat GetBlock(Header,16); { read frame-header } FrameSize:=Header[0]+256*(LongInt(Header[1])+256*(LongInt(Header[2])+256*LongInt(Header[3])))-16; { size of frame } if FrameSize<>0 then { sometimes there are no changes from one frame to the next (used For extra Delays). In such - } { - Cases the size of the frame is 0 and we don't have to process them } begin Chunks:=Header[6]+Header[7]*256; { calculate number of chunks in the frame } GetBlock(Buffer^,FrameSize); { read the frame into the framebuffer } TreatFrame(Buffer,Chunks); { treat the frame } end; NextTime:=TimeCounter+Speed; { calculate the Delay to the next frame } While TimeCounterFrames) or (Key<>OldKey); { Repeated Until we come to the last frame or a key is pressed } if UseXMS then FileCounter:=SecondPos else Seek(InputFile,SecondPos); { set current position in the File to the second frame } Until Key<>OldKey; { Exit the loop if a key has been pressed } InitMode(CO80); { get back to Text mode } Close(InputFile); { be a kind boy and close the File beFore we end the Program } FreeMem(Buffer,BUFFERSIZE); { and free the framebuffer } if UseXMS then XMSFreeMem(BufferHandle); END.