forked from OpenRA/OpenRA
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathvqa_frmt.txt
350 lines (295 loc) · 11.7 KB
/
vqa_frmt.txt
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
.VQA files
by Aaron Glover ([email protected])
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 ([email protected])
----------
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).