Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
9169 | turbocat | 1 | /* |
2 | * OpenTyrian: A modern cross-platform port of Tyrian |
||
3 | * Copyright (C) 2007-2009 The OpenTyrian Development Team |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or |
||
6 | * modify it under the terms of the GNU General Public License |
||
7 | * as published by the Free Software Foundation; either version 2 |
||
8 | * of the License, or (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License |
||
16 | * along with this program; if not, write to the Free Software |
||
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | */ |
||
19 | #include "animlib.h" |
||
20 | |||
21 | #include "file.h" |
||
22 | #include "keyboard.h" |
||
23 | #include "network.h" |
||
24 | #include "nortsong.h" |
||
25 | #include "palette.h" |
||
26 | #include "sizebuf.h" |
||
27 | #include "video.h" |
||
28 | |||
29 | #include |
||
30 | |||
31 | /*** Structs ***/ |
||
32 | /* The actual header has a lot of fields that are basically useless to us since |
||
33 | * we both set our own framerate and the format itself only allows for |
||
34 | * 320x200x8. Should a (nonexistent) ani be played that doesn't have the same |
||
35 | * assumed values we are going to use, TOO BAD. It'll just be treated as |
||
36 | * corrupt in playback. |
||
37 | */ |
||
38 | #define PALETTE_OFFSET 0x100 // 128 + sizeof(header) |
||
39 | #define PAGEHEADER_OFFSET 0x500 // PALETTE_OFFSET + sizeof(palette) |
||
40 | #define ANIM_OFFSET 0x0B00 // PAGEHEADER_OFFSET + sizeof(largepageheader) * 256 |
||
41 | #define ANI_PAGE_SIZE 0x10000 // 65536. |
||
42 | typedef struct anim_FileHeader_s |
||
43 | { |
||
44 | unsigned int nlps; /* Number of 'pages', max 256. */ |
||
45 | unsigned int nRecords; /* Number of 'records', max 65535 */ |
||
46 | } anim_FileHeader_t; |
||
47 | typedef struct anim_LargePageHeader_s |
||
48 | { |
||
49 | unsigned int baseRecord; /* The first record's number */ |
||
50 | unsigned int nRecords; /* Number of records. Supposedly there are bit flags but I saw no such code */ |
||
51 | unsigned int nBytes; /* Number of bytes used, excluding headers */ |
||
52 | } anim_LargePageHeader_t; |
||
53 | |||
54 | |||
55 | /*** Globals ***/ |
||
56 | Uint8 CurrentPageBuffer[65536]; |
||
57 | anim_LargePageHeader_t PageHeader[256]; |
||
58 | unsigned int CurrentPageRecordSizes[256]; |
||
59 | |||
60 | anim_LargePageHeader_t CurrentPageHeader; |
||
61 | anim_FileHeader_t FileHeader; |
||
62 | |||
63 | unsigned int Curlpnum; |
||
64 | |||
65 | FILE * InFile; |
||
66 | |||
67 | |||
68 | /*** Function decs ***/ |
||
69 | int JE_playRunSkipDump( Uint8 *, unsigned int ); |
||
70 | void JE_closeAnim( void ); |
||
71 | int JE_loadAnim( const char * ); |
||
72 | int JE_renderFrame( unsigned int ); |
||
73 | int JE_findPage ( unsigned int ); |
||
74 | int JE_drawFrame( unsigned int ); |
||
75 | int JE_loadPage( unsigned int ); |
||
76 | |||
77 | /*** Implementation ***/ |
||
78 | |||
79 | |||
80 | /* Loads the given page into memory. |
||
81 | * |
||
82 | * Returns 0 on success or nonzero on failure (bad data) |
||
83 | */ |
||
84 | int JE_loadPage( unsigned int pagenumber ) |
||
85 | { |
||
86 | unsigned int i, pageSize; |
||
87 | |||
88 | |||
89 | if (Curlpnum == pagenumber) { return(0); } /* Already loaded */ |
||
90 | Curlpnum = pagenumber; |
||
91 | |||
92 | /* We need to seek to the page and load it into our buffer. |
||
93 | * Pages have a fixed size of 0x10000; any left over space is padded |
||
94 | * unless it's the end of the file. |
||
95 | * |
||
96 | * Pages repeat their headers for some reason. They then have two bytes of |
||
97 | * padding folowed by a word for every record. THEN the data starts. |
||
98 | */ |
||
99 | fseek(InFile, ANIM_OFFSET + (pagenumber * ANI_PAGE_SIZE), SEEK_SET); |
||
100 | efread(&CurrentPageHeader.baseRecord, 2, 1, InFile); |
||
101 | efread(&CurrentPageHeader.nRecords, 2, 1, InFile); |
||
102 | efread(&CurrentPageHeader.nBytes, 2, 1, InFile); |
||
103 | |||
104 | fseek(InFile, 2, SEEK_CUR); |
||
105 | for (i = 0; i < CurrentPageHeader.nRecords; i++) |
||
106 | { |
||
107 | efread(&CurrentPageRecordSizes[i], 2, 1, InFile); |
||
108 | } |
||
109 | |||
110 | /* What remains is the 'compressed' data */ |
||
111 | efread(CurrentPageBuffer, 1, CurrentPageHeader.nBytes, InFile); |
||
112 | |||
113 | /* Okay, we've succeeded in all our IO checks. Now, make sure the |
||
114 | * headers aren't lying or damaged or something. |
||
115 | */ |
||
116 | pageSize = 0; |
||
117 | for (i = 0; i < CurrentPageHeader.nRecords; i++) |
||
118 | { |
||
119 | pageSize += CurrentPageRecordSizes[i]; |
||
120 | } |
||
121 | |||
122 | if(pageSize != CurrentPageHeader.nBytes) { return(-1); } |
||
123 | |||
124 | /* So far, so good */ |
||
125 | return(0); |
||
126 | } |
||
127 | |||
128 | int JE_drawFrame( unsigned int framenumber ) |
||
129 | { |
||
130 | int ret; |
||
131 | |||
132 | |||
133 | ret = JE_loadPage(framenumber); |
||
134 | if (ret) { return(ret); } |
||
135 | |||
136 | ret = JE_renderFrame (framenumber); |
||
137 | if (ret) { return(ret); } |
||
138 | |||
139 | return(0); |
||
140 | } |
||
141 | |||
142 | int JE_findPage( unsigned int framenumber ) |
||
143 | { |
||
144 | unsigned int i; |
||
145 | |||
146 | |||
147 | for (i = 0; i < FileHeader.nlps; i++) |
||
148 | { |
||
149 | if (PageHeader[i].baseRecord <= framenumber |
||
150 | && PageHeader[i].baseRecord + PageHeader[i].nRecords > framenumber) |
||
151 | { |
||
152 | return(i); |
||
153 | } |
||
154 | } |
||
155 | |||
156 | return(-1); /* Did not find */ |
||
157 | } |
||
158 | |||
159 | int JE_renderFrame( unsigned int framenumber ) |
||
160 | { |
||
161 | unsigned int i, offset, destframe; |
||
162 | |||
163 | |||
164 | destframe = framenumber - CurrentPageHeader.baseRecord; |
||
165 | |||
166 | offset = 0; |
||
167 | for (i = 0; i < destframe; i++) |
||
168 | { |
||
169 | offset += CurrentPageRecordSizes[i]; |
||
170 | } |
||
171 | |||
172 | return (JE_playRunSkipDump(CurrentPageBuffer + offset + 4, CurrentPageRecordSizes[destframe] - 4)); |
||
173 | } |
||
174 | |||
175 | void JE_playAnim( const char *animfile, JE_byte startingframe, JE_byte speed ) |
||
176 | { |
||
177 | unsigned int i; |
||
178 | int pageNum; |
||
179 | |||
180 | if (JE_loadAnim(animfile) != 0) |
||
181 | { |
||
182 | return; /* Failed to open or process file */ |
||
183 | } |
||
184 | |||
185 | /* Blank screen */ |
||
186 | JE_clr256(VGAScreen); |
||
187 | JE_showVGA(); |
||
188 | |||
189 | |||
190 | /* re FileHeader.nRecords-1: It's -1 in the pascal too. |
||
191 | * The final frame is a delta of the first, and we don't need that. |
||
192 | * We could also, if we ever ended up needing to loop anis, check |
||
193 | * the bools in the header to see if we should render the last |
||
194 | * frame. But that's never going to be encessary :) |
||
195 | */ |
||
196 | for (i = startingframe; i < FileHeader.nRecords-1; i++) |
||
197 | { |
||
198 | /* Handle boring crap */ |
||
199 | setjasondelay(speed); |
||
200 | |||
201 | /* Load required frame. The loading function is smart enough to not re-load an already loaded frame */ |
||
202 | pageNum = JE_findPage(i); |
||
203 | if(pageNum == -1) { break; } |
||
204 | if (JE_loadPage(pageNum) != 0) { break; } |
||
205 | |||
206 | /* render frame. */ |
||
207 | if (JE_renderFrame(i) != 0) { break; } |
||
208 | JE_showVGA(); |
||
209 | |||
210 | |||
211 | /* Return early if user presses a key */ |
||
212 | service_SDL_events(true); |
||
213 | if (newkey) |
||
214 | { |
||
215 | break; |
||
216 | } |
||
217 | |||
218 | /* Wait until we need the next frame */ |
||
219 | NETWORK_KEEP_ALIVE(); |
||
220 | wait_delay(); |
||
221 | } |
||
222 | |||
223 | JE_closeAnim(); |
||
224 | } |
||
225 | |||
226 | /* loadAnim opens the file and loads data from it into the header structs. |
||
227 | * It should take care to clean up after itself should an error occur. |
||
228 | */ |
||
229 | int JE_loadAnim( const char *filename ) |
||
230 | { |
||
231 | unsigned int i, fileSize; |
||
232 | char temp[4]; |
||
233 | |||
234 | |||
235 | Curlpnum = -1; |
||
236 | InFile = dir_fopen(data_dir(), filename, "rb"); |
||
237 | if(InFile == NULL) |
||
238 | { |
||
239 | return(-1); |
||
240 | } |
||
241 | |||
242 | fileSize = ftell_eof(InFile); |
||
243 | if(fileSize < ANIM_OFFSET) |
||
244 | { |
||
245 | /* We don't know the exact size our file should be yet, |
||
246 | * but we do know it should be way more than this */ |
||
247 | fclose(InFile); |
||
248 | return(-1); |
||
249 | } |
||
250 | |||
251 | /* Read in the header. The header is 256 bytes long or so, |
||
252 | * but that includes a lot of padding as well as several |
||
253 | * vars we really don't care about. We shall check the ID and extract |
||
254 | * the handful of vars we care about. Every value in the header that |
||
255 | * is constant will be ignored. |
||
256 | */ |
||
257 | |||
258 | efread(&temp, 1, 4, InFile); /* The ID, should equal "LPF " */ |
||
259 | fseek(InFile, 2, SEEK_CUR); /* skip over this word */ |
||
260 | efread(&FileHeader.nlps, 2, 1, InFile); /* Number of pages */ |
||
261 | efread(&FileHeader.nRecords, 4, 1, InFile); /* Number of records */ |
||
262 | |||
263 | if (memcmp(temp, "LPF ", 4) != 0 |
||
264 | || FileHeader.nlps == 0 || FileHeader.nRecords == 0 |
||
265 | || FileHeader.nlps > 256 || FileHeader.nRecords > 65535) |
||
266 | { |
||
267 | fclose(InFile); |
||
268 | return(-1); |
||
269 | } |
||
270 | |||
271 | /* Read in headers */ |
||
272 | fseek(InFile, PAGEHEADER_OFFSET, SEEK_SET); |
||
273 | for (i = 0; i < FileHeader.nlps; i++) |
||
274 | { |
||
275 | efread(&PageHeader[i].baseRecord, 2, 1, InFile); |
||
276 | efread(&PageHeader[i].nRecords, 2, 1, InFile); |
||
277 | efread(&PageHeader[i].nBytes, 2, 1, InFile); |
||
278 | } |
||
279 | |||
280 | |||
281 | /* Now we have enough information to calculate the 'expected' file size. |
||
282 | * Our calculation SHOULD be equal to fileSize, but we won't begrudge |
||
283 | * padding */ |
||
284 | if (fileSize < (FileHeader.nlps-1) * ANI_PAGE_SIZE + ANIM_OFFSET |
||
285 | + PageHeader[FileHeader.nlps-1].nBytes |
||
286 | + PageHeader[FileHeader.nlps-1].nRecords * 2 + 8) |
||
287 | { |
||
288 | fclose(InFile); |
||
289 | return(-1); |
||
290 | } |
||
291 | |||
292 | |||
293 | /* Now read in the palette. */ |
||
294 | fseek(InFile, PALETTE_OFFSET, SEEK_SET); |
||
295 | for (i = 0; i < 256; i++) |
||
296 | { |
||
297 | efread(&colors[i].b, 1, 1, InFile); |
||
298 | efread(&colors[i].g, 1, 1, InFile); |
||
299 | efread(&colors[i].r, 1, 1, InFile); |
||
300 | efread(&colors[i].unused, 1, 1, InFile); |
||
301 | } |
||
302 | set_palette(colors, 0, 255); |
||
303 | |||
304 | /* Whew! That was hard. Let's go grab some beers! */ |
||
305 | return(0); |
||
306 | } |
||
307 | |||
308 | void JE_closeAnim( void ) |
||
309 | { |
||
310 | fclose(InFile); |
||
311 | } |
||
312 | |||
313 | /* RunSkipDump decompresses the video. There are three operations, run, skip, |
||
314 | * and dump. They can be used in either byte or word variations, making six |
||
315 | * possible actions, and there's a seventh 'stop' action, which looks |
||
316 | * like 0x80 0x00 0x00. |
||
317 | * |
||
318 | * Run is a memset. |
||
319 | * Dump is a memcpy. |
||
320 | * Skip leaves the old data intact and simply increments the pointers. |
||
321 | * |
||
322 | * returns 0 on success or 1 if decompressing failed. Failure to decompress |
||
323 | * indicates a broken or malicious file; playback should terminate. |
||
324 | */ |
||
325 | int JE_playRunSkipDump( Uint8 *incomingBuffer, unsigned int IncomingBufferLength ) |
||
326 | { |
||
327 | sizebuf_t Buffer_IN, Buffer_OUT; |
||
328 | sizebuf_t * pBuffer_IN = &Buffer_IN, * pBuffer_OUT = &Buffer_OUT; |
||
329 | |||
330 | #define ANI_SHORT_RLE 0x00 |
||
331 | #define ANI_SHORT_SKIP 0x80 |
||
332 | #define ANI_LONG_OP 0x80 |
||
333 | #define ANI_LONG_COPY_OR_RLE 0x8000 |
||
334 | #define ANI_LONG_RLE 0x4000 |
||
335 | #define ANI_STOP 0x0000 |
||
336 | |||
337 | SZ_Init(pBuffer_IN, incomingBuffer, IncomingBufferLength); |
||
338 | SZ_Init(pBuffer_OUT, VGAScreen->pixels, VGAScreen->h * VGAScreen->pitch); |
||
339 | |||
340 | |||
341 | /* 320x200 is the only supported format. |
||
342 | * Assert is here as a hint should our screen size ever changes. |
||
343 | * As for how to decompress to the wrong screen size... */ |
||
344 | assert(VGAScreen->h * VGAScreen->pitch == 320 * 200); |
||
345 | |||
346 | |||
347 | while (1) |
||
348 | { |
||
349 | /* Get one byte. This byte may have flags that tell us more */ |
||
350 | unsigned int opcode = MSG_ReadByte(pBuffer_IN); |
||
351 | |||
352 | /* Before we continue, check the error states/ |
||
353 | * We should *probably* check these after every read and write, but |
||
354 | * I've rigged it so that the buffers will never go out of bounds. |
||
355 | * So we can afford to be lazy; if the buffer overflows below it will |
||
356 | * silently fail its writes and we'll catch the failure on our next |
||
357 | * run through the loop. A failure means we should be |
||
358 | * leaving ANYWAY. The contents of our buffers doesn't matter. |
||
359 | */ |
||
360 | if (SZ_Error(pBuffer_IN) || SZ_Error(pBuffer_OUT)) |
||
361 | { |
||
362 | return(-1); |
||
363 | } |
||
364 | |||
365 | /* Divide into 'short' and 'long' */ |
||
366 | if (opcode == ANI_LONG_OP) /* long ops */ |
||
367 | { |
||
368 | opcode = MSG_ReadWord(pBuffer_IN); |
||
369 | |||
370 | if (opcode == ANI_STOP) /* We are done decompressing. Leave */ |
||
371 | { |
||
372 | break; |
||
373 | } |
||
374 | else if (!(opcode & ANI_LONG_COPY_OR_RLE)) /* If it's not those two, it's a skip */ |
||
375 | { |
||
376 | unsigned int count = opcode; |
||
377 | SZ_Seek(pBuffer_OUT, count, SEEK_CUR); |
||
378 | } |
||
379 | else /* Now things get a bit more interesting... */ |
||
380 | { |
||
381 | opcode &= ~ANI_LONG_COPY_OR_RLE; /* Clear that flag */ |
||
382 | |||
383 | if (opcode & ANI_LONG_RLE) /* RLE */ |
||
384 | { |
||
385 | unsigned int count = opcode & ~ANI_LONG_RLE; /* Clear flag */ |
||
386 | |||
387 | /* Extract another byte */ |
||
388 | unsigned int value = MSG_ReadByte(pBuffer_IN); |
||
389 | |||
390 | /* The actual run */ |
||
391 | SZ_Memset(pBuffer_OUT, value, count); |
||
392 | } |
||
393 | else |
||
394 | { /* Long copy */ |
||
395 | unsigned int count = opcode; |
||
396 | |||
397 | /* Copy */ |
||
398 | SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count); |
||
399 | } |
||
400 | } |
||
401 | } /* End of long ops */ |
||
402 | else /* short ops */ |
||
403 | { |
||
404 | if (opcode & ANI_SHORT_SKIP) /* Short skip, move pointer only */ |
||
405 | { |
||
406 | unsigned int count = opcode & ~ANI_SHORT_SKIP; /* clear flag to get count */ |
||
407 | SZ_Seek(pBuffer_OUT, count, SEEK_CUR); |
||
408 | } |
||
409 | else if (opcode == ANI_SHORT_RLE) /* Short RLE, memset the destination */ |
||
410 | { |
||
411 | /* Extract a few more bytes */ |
||
412 | unsigned int count = MSG_ReadByte(pBuffer_IN); |
||
413 | unsigned int value = MSG_ReadByte(pBuffer_IN); |
||
414 | |||
415 | /* Run */ |
||
416 | SZ_Memset(pBuffer_OUT, value, count); |
||
417 | } |
||
418 | else /* Short copy, memcpy from src to dest. */ |
||
419 | { |
||
420 | unsigned int count = opcode; |
||
421 | |||
422 | /* Dump */ |
||
423 | SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count); |
||
424 | } |
||
425 | } /* End of short ops */ |
||
426 | } |
||
427 | |||
428 | /* And that's that */ |
||
429 | return(0); |
||
430 | }>>>>>>=>>>> |
||
431 |