.VQA files by Aaron Glover (arn@ibm.net) Each VQA has a 62-byte header, as follows: VQAHeader: record TextFORM: array[0..3] of Char; {Always 'FORM'} FileBTF: LongInt; {Reverse - Bytes to follow} TextWVQAVQHD: array[0..7] of Char; {Always 'WVQAVQHD'} RStartPos: LongInt; {Reverse - Relative start position} Unknown1: Word; Unknown2: Word; NumFrames: Word; Width: Word; Height: Word; Unknown3: Word; Unknown4: Word; Unknown5: Word; Unknown6: Word; Unknown7: LongInt; Unknown8: Word; {This changes} Unknown9: Word; Unknown10: Word; Unknown11: array[0..13] of Char; end; Following the header, there are a number of `sub-files' that each have a header of 8 bytes. The first four are the name (or type) of the sub-file, the next four are a reverse LongInt that equals the number of sub-file data bytes to follow (sub-file size minus sub-file header size). By `reverse LongInt', I mean a 4-byte Integer value stored backwards. For example, take the the decimal value 77236. In hexadecimal it's 12DB4h, and would be stored in a binary file as the bytes B4 2D 01 00. As a reverse LongInt, it would be stored as 00 01 2D B4. More human readable, but not how computers work. Some sub-file names seem to start with a null byte (00h). There is a reason for this, which will become apparent later. Just ignore the null byte and assume the next one is the start of the sub-file header. So, after the header, you should find the something like the following sub-files: FINF SND2 SND2 VQFR SND2 VQFR SND2 VQFR SND2 VQFR ... Each VQFR sub-file itself has sub-files. If you treat each VQFR sub- file as if the `data bytes to follow' value was zero, you should get something like: FINF SND2 SND2 VQFR CBF0 CBP0 CPL0 VPTZ SND2 VQFR CBP0 VPTZ SND2 VQFR CBP0 VPTZ SND2 VQFR CBP0 VPTZ ... FINF sub-file type: First is a Word value that, if you multiply by two, gives the position of the first data sub-file (relative to the start of the VQA), then another Word value that seems to be always 4000h. Following that is an array of LongInt values that, when multiplied by two, give the position of each of the frame data sub-files (relative to the start of the VQA). Each frame comprises of a SND2 sub-file and a VQFR sub-file that follows immediately after. This is why some of the sub-file names start with a null byte. Since you have to multiply by two, each offset value must be even. So if it would normally be odd, a null is inserted as the first byte to make the sub-file's name offset even. Whew! Try saying that five times fast! The number of elements in the array is FrameNum (from the VQA header) minus one. I've noticed some of the LongInt values in this array are 40000000h too large. I don't know why this is, at the moment I subtract 40000000h from values over 40000000h, it seems to work OK. SND2 sub-file type: I bet you've guessed this one. Well, so did I. Audio, right? I've had a go at decoding them, and they seem to be in the same format as the .AUD files, but I can't work them out (yet). CBF0 sub-file type: An array of eight-byte (4x2 pixel) uncompressed screen graphics. I'll explain what they're used for when we get to the VPTZ sub-file type. Just remember that it's an array of graphics that measure 4x2 screen pixels. CBP0 sub-file type: Eight of these (in frame order) appended together make up a complete CBF0 sub-file that replaces the previous CBF0 sub-file information. After you've displayed each eighth frame, you need to replace the current CBF0 information with the new one you've made up from eight CBP0 sub-files. Just do it, OK? This will make more sense when we get to the VPTZ sub-file type. CPL0 sub-file type: The palette for the VQA file. An array of Red, Green and Blue byte values (in that order). Note that there are sometimes less than 256 colours in the palette. VPTZ sub-file type: Well, here it is. This is the heart of the VQA file, the graphics. Each VPTZ sub-file is compressed with the Format 80 method as described later in this document. When you decompress a VPTZ sub-file, you get an 80x156 graphic. The top half (the first 78 lines) is the basis of the finished frame, while the bottom half is a modifier for the pixels in the top half. The final size of each VQA frame is 320x156. With the top half (basis of the finished frame, remember) being 80x78, you can see that we need to multiply by four in the X (horizontal) direction, and by two in the Y (vertical) direction. Imagine that each pixel in the top half in fact represents eight screen pixels, arranged in a 4x2 format. I must distinguish between pixels and screen pixels. By pixel, I mean one byte read from the decompressed VPTZ graphic, which, when displayed on screen, measures 4x2 screen pixels. Now, if you view a VQA, you can see that there is a higher resolution used than each pixel being 4x2 screen pixels. This is where the bottom half and the CBF0 sub-file type comes in. The bottom half is an overlay of modifiers for the top half. That is, the top-left pixel in the bottom half is a modifier for the top- left pixel in the top half. The bottom half pixel values range from 00h to 0Fh. 0Fh means `no modifcation'. The corresponding pixel value in the top half is copied eight times to produce the 4x2 screen pixel format. 00-0Eh are modifiers. If you treat these pixel values as the high byte in a Word value, and treat the corresponding pixel value in the top half as the low byte, you get the CBF0 array element number of the 4x2 screen graphic you should display for that pixel. Make sense? That is how the higher resolution is achieved. Perhaps I should clarify. If TopByte is the top half pixel byte value, and BottomByte is the bottom half pixel byte value, then the 4x2 screen pixel graphic is element number (BottomByte * 256 + TopByte) in the CBF0 array. Just display the frames in order, and presto! You have a VQA movie. Format 80 compression method by Vladan Bato (bat22@geocities.com) ---------- Format80 ---------- There are several different commands, with different sizes : form 1 to 5 bytes. The positions mentioned below always refer to the destination buffer (i.e. the uncompressed image). The relative positions are relative to the current position in the destination buffer, which is one byte beyond the last written byte. I will give some sample code at the end. (1) 1 byte +---+---+---+---+---+---+---+---+ | 1 | 0 | | | | | | | +---+---+---+---+---+---+---+---+ \_______________________/ | Count This one means : copy next Count bytes as is from Source to Dest. (2) 2 bytes +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ | 0 | | | | | | | | | | | | | | | | | +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+ \___________/\__________________________________________________/ | | Count-3 Relative Pos. This means copy Count bytes from Dest at Current Pos.-Rel. Pos. to Current position. Note that you have to add 3 to the number you find in the bits 4-6 of the first byte to obtain the Count. Note that if the Rel. Pos. is 1, that means repeat Count times the previous byte. (3) 3 bytes +---+---+---+---+---+---+---+---+ +---------------+---------------+ | 1 | 1 | | | | | | | | | | +---+---+---+---+---+---+---+---+ +---------------+---------------+ \_______________________/ Pos | Count-3 Copy Count bytes from Pos, where Pos is absolute from the start of the destination buffer. (Pos is a word, that means that the images can't be larger than 64K) (4) 4 bytes +---+---+---+---+---+---+---+---+ +-------+-------+ +-------+ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | | | | | | +---+---+---+---+---+---+---+---+ +-------+-------+ +-------+ Count Color Write Color Count times. (Count is a word, color is a byte) (5) 5 bytes +---+---+---+---+---+---+---+---+ +-------+-------+ +-------+-------+ | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | | | | | | +---+---+---+---+---+---+---+---+ +-------+-------+ +-------+-------+ Count Pos Copy Count bytes from Dest. starting at Pos. Pos is absolute from the start of the Destination buffer. Both Count and Pos are words. These are all the commands I found out. Maybe there are other ones, but I haven't seen them yet. All the images end with a 80h command. To make things more clearer here's a piece of code that will uncompress the image. DP = destination pointer SP = source pointer Source and Dest are the two buffers SP:=0; DP:=0; repeat Com:=Source[SP]; inc(SP); b7:=Com shr 7; {b7 is bit 7 of Com} case b7 of 0 : begin {copy command (2)} {Count is bits 4-6 + 3} Count:=(Com and $7F) shr 4 + 3; {Position is bits 0-3, with bits 0-7 of next byte} Posit:=(Com and $0F) shl 8+Source[SP]; Inc(SP); {Starting pos=Cur pos. - calculated value} Posit:=DP-Posit; for i:=Posit to Posit+Count-1 do begin Dest[DP]:=Dest[i]; Inc(DP); end; end; 1 : begin {Check bit 6 of Com} b6:=(Com and $40) shr 6; case b6 of 0 : begin {Copy as is command (1)} Count:=Com and $3F; {mask 2 topmost bits} if Count=0 then break; {EOF marker} for i:=1 to Count do begin Dest[DP]:=Source[SP]; Inc(DP); Inc(SP); end; end; 1 : begin {large copy, very large copy and fill commands} {Count = (bits 0-5 of Com) +3} {if Com=FEh then fill, if Com=FFh then very large copy} Count:=Com and $3F; if Count<$3E then {large copy (3)} begin Inc(Count,3); {Next word = pos. from start of image} Posit:=Word(Source[SP]); Inc(SP,2); for i:=Posit to Posit+Count-1 do begin Dest[DP]:=Dest[i]; Inc(DP); end; end else if Count=$3F then {very large copy (5)} begin {next 2 words are Count and Pos} Count:=Word(Source[SP]); Posit:=Word(Source[SP+2]); Inc(SP,4); for i:=Posit to Posit+Count-1 do begin Dest[DP]:=Dest[i]; Inc(DP); end; end else begin {Count=$3E, fill (4)} {Next word is count, the byte after is color} Count:=Word(Source[SP]); Inc(SP,2); b:=Source[SP]; Inc(SP); for i:=0 to Count-1 do begin Dest[DP]:=b; inc(DP); end; end; end; end; end; end; until false; Note that you won't be able to compile this code, because the typecasting won't work. (But I'm sure you'll be able to fix it).