/* * OpenTyrian Classic: A modern cross-platform port of Tyrian * Copyright (C) 2007-2009 The OpenTyrian Development Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "animlib.h" #include "file.h" #include "keyboard.h" #include "network.h" #include "nortsong.h" #include "palette.h" #include "sizebuf.h" #include "video.h" #include /*** Structs ***/ /* The actual header has a lot of fields that are basically useless to us since * we both set our own framerate and the format itself only allows for * 320x200x8. Should a (nonexistent) ani be played that doesn't have the same * assumed values we are going to use, TOO BAD. It'll just be treated as * corrupt in playback. */ #define PALETTE_OFFSET 0x100 // 128 + sizeof(header) #define PAGEHEADER_OFFSET 0x500 // PALETTE_OFFSET + sizeof(palette) #define ANIM_OFFSET 0x0B00 // PAGEHEADER_OFFSET + sizeof(largepageheader) * 256 #define ANI_PAGE_SIZE 0x10000 // 65536. typedef struct anim_FileHeader_s { unsigned int nlps; /* Number of 'pages', max 256. */ unsigned int nRecords; /* Number of 'records', max 65535 */ } anim_FileHeader_t; typedef struct anim_LargePageHeader_s { unsigned int baseRecord; /* The first record's number */ unsigned int nRecords; /* Number of records. Supposedly there are bit flags but I saw no such code */ unsigned int nBytes; /* Number of bytes used, excluding headers */ } anim_LargePageHeader_t; /*** Globals ***/ Uint8 CurrentPageBuffer[65536]; anim_LargePageHeader_t PageHeader[256]; unsigned int CurrentPageRecordSizes[256]; anim_LargePageHeader_t CurrentPageHeader; anim_FileHeader_t FileHeader; unsigned int Curlpnum; FILE * InFile; /*** Function decs ***/ int JE_playRunSkipDump( Uint8 *, unsigned int ); void JE_closeAnim( void ); int JE_loadAnim( const char * ); int JE_renderFrame( unsigned int ); int JE_findPage ( unsigned int ); int JE_drawFrame( unsigned int ); int JE_loadPage( unsigned int ); /*** Implementation ***/ /* Loads the given page into memory. * * Returns 0 on success or nonzero on failure (bad data) */ int JE_loadPage( unsigned int pagenumber ) { unsigned int i, pageSize; if (Curlpnum == pagenumber) { return(0); } /* Already loaded */ Curlpnum = pagenumber; /* We need to seek to the page and load it into our buffer. * Pages have a fixed size of 0x10000; any left over space is padded * unless it's the end of the file. * * Pages repeat their headers for some reason. They then have two bytes of * padding folowed by a word for every record. THEN the data starts. */ fseek(InFile, ANIM_OFFSET + (pagenumber * ANI_PAGE_SIZE), SEEK_SET); efread(&CurrentPageHeader.baseRecord, 2, 1, InFile); efread(&CurrentPageHeader.nRecords, 2, 1, InFile); efread(&CurrentPageHeader.nBytes, 2, 1, InFile); fseek(InFile, 2, SEEK_CUR); for (i = 0; i < CurrentPageHeader.nRecords; i++) { efread(&CurrentPageRecordSizes[i], 2, 1, InFile); } /* What remains is the 'compressed' data */ efread(CurrentPageBuffer, 1, CurrentPageHeader.nBytes, InFile); /* Okay, we've succeeded in all our IO checks. Now, make sure the * headers aren't lying or damaged or something. */ pageSize = 0; for (i = 0; i < CurrentPageHeader.nRecords; i++) { pageSize += CurrentPageRecordSizes[i]; } if(pageSize != CurrentPageHeader.nBytes) { return(-1); } /* So far, so good */ return(0); } int JE_drawFrame( unsigned int framenumber ) { int ret; ret = JE_loadPage(framenumber); if (ret) { return(ret); } ret = JE_renderFrame (framenumber); if (ret) { return(ret); } return(0); } int JE_findPage( unsigned int framenumber ) { unsigned int i; for (i = 0; i < FileHeader.nlps; i++) { if (PageHeader[i].baseRecord <= framenumber && PageHeader[i].baseRecord + PageHeader[i].nRecords > framenumber) { return(i); } } return(-1); /* Did not find */ } int JE_renderFrame( unsigned int framenumber ) { unsigned int i, offset, destframe; destframe = framenumber - CurrentPageHeader.baseRecord; offset = 0; for (i = 0; i < destframe; i++) { offset += CurrentPageRecordSizes[i]; } return (JE_playRunSkipDump(CurrentPageBuffer + offset + 4, CurrentPageRecordSizes[destframe] - 4)); } void JE_playAnim( const char *animfile, JE_byte startingframe, JE_byte speed ) { unsigned int i; int pageNum; if (JE_loadAnim(animfile) != 0) { return; /* Failed to open or process file */ } /* Blank screen */ JE_clr256(VGAScreen); JE_showVGA(); /* re FileHeader.nRecords-1: It's -1 in the pascal too. * The final frame is a delta of the first, and we don't need that. * We could also, if we ever ended up needing to loop anis, check * the bools in the header to see if we should render the last * frame. But that's never going to be encessary :) */ for (i = startingframe; i < FileHeader.nRecords-1; i++) { /* Handle boring crap */ setjasondelay(speed); /* Load required frame. The loading function is smart enough to not re-load an already loaded frame */ pageNum = JE_findPage(i); if(pageNum == -1) { break; } if (JE_loadPage(pageNum) != 0) { break; } /* render frame. */ if (JE_renderFrame(i) != 0) { break; } JE_showVGA(); /* Return early if user presses a key */ service_SDL_events(true); if (newkey) { break; } /* Wait until we need the next frame */ NETWORK_KEEP_ALIVE(); wait_delay(); } JE_closeAnim(); } /* loadAnim opens the file and loads data from it into the header structs. * It should take care to clean up after itself should an error occur. */ int JE_loadAnim( const char *filename ) { unsigned int i, fileSize; char temp[4]; Curlpnum = -1; InFile = dir_fopen(data_dir(), filename, "rb"); if(InFile == NULL) { return(-1); } fileSize = ftell_eof(InFile); if(fileSize < ANIM_OFFSET) { /* We don't know the exact size our file should be yet, * but we do know it should be way more than this */ fclose(InFile); return(-1); } /* Read in the header. The header is 256 bytes long or so, * but that includes a lot of padding as well as several * vars we really don't care about. We shall check the ID and extract * the handful of vars we care about. Every value in the header that * is constant will be ignored. */ efread(&temp, 1, 4, InFile); /* The ID, should equal "LPF " */ fseek(InFile, 2, SEEK_CUR); /* skip over this word */ efread(&FileHeader.nlps, 2, 1, InFile); /* Number of pages */ efread(&FileHeader.nRecords, 4, 1, InFile); /* Number of records */ if (memcmp(temp, "LPF ", 4) != 0 || FileHeader.nlps == 0 || FileHeader.nRecords == 0 || FileHeader.nlps > 256 || FileHeader.nRecords > 65535) { fclose(InFile); return(-1); } /* Read in headers */ fseek(InFile, PAGEHEADER_OFFSET, SEEK_SET); for (i = 0; i < FileHeader.nlps; i++) { efread(&PageHeader[i].baseRecord, 2, 1, InFile); efread(&PageHeader[i].nRecords, 2, 1, InFile); efread(&PageHeader[i].nBytes, 2, 1, InFile); } /* Now we have enough information to calculate the 'expected' file size. * Our calculation SHOULD be equal to fileSize, but we won't begrudge * padding */ if (fileSize < (FileHeader.nlps-1) * ANI_PAGE_SIZE + ANIM_OFFSET + PageHeader[FileHeader.nlps-1].nBytes + PageHeader[FileHeader.nlps-1].nRecords * 2 + 8) { fclose(InFile); return(-1); } /* Now read in the palette. */ fseek(InFile, PALETTE_OFFSET, SEEK_SET); for (i = 0; i < 256; i++) { efread(&colors[i].b, 1, 1, InFile); efread(&colors[i].g, 1, 1, InFile); efread(&colors[i].r, 1, 1, InFile); efread(&colors[i].unused, 1, 1, InFile); } set_palette(colors, 0, 255); /* Whew! That was hard. Let's go grab some beers! */ return(0); } void JE_closeAnim( void ) { fclose(InFile); } /* RunSkipDump decompresses the video. There are three operations, run, skip, * and dump. They can be used in either byte or word variations, making six * possible actions, and there's a seventh 'stop' action, which looks * like 0x80 0x00 0x00. * * Run is a memset. * Dump is a memcpy. * Skip leaves the old data intact and simply increments the pointers. * * returns 0 on success or 1 if decompressing failed. Failure to decompress * indicates a broken or malicious file; playback should terminate. */ int JE_playRunSkipDump( Uint8 *incomingBuffer, unsigned int IncomingBufferLength ) { sizebuf_t Buffer_IN, Buffer_OUT; sizebuf_t * pBuffer_IN = &Buffer_IN, * pBuffer_OUT = &Buffer_OUT; #define ANI_SHORT_RLE 0x00 #define ANI_SHORT_SKIP 0x80 #define ANI_LONG_OP 0x80 #define ANI_LONG_COPY_OR_RLE 0x8000 #define ANI_LONG_RLE 0x4000 #define ANI_STOP 0x0000 SZ_Init(pBuffer_IN, incomingBuffer, IncomingBufferLength); SZ_Init(pBuffer_OUT, (Uint8 *)VGAScreen->pixels, VGAScreen->h * VGAScreen->pitch); /* 320x200 is the only supported format. * Assert is here as a hint should our screen size ever changes. * As for how to decompress to the wrong screen size... */ assert(VGAScreen->h * VGAScreen->pitch == 320 * 200); while (1) { /* Get one byte. This byte may have flags that tell us more */ unsigned int opcode = MSG_ReadByte(pBuffer_IN); /* Before we continue, check the error states/ * We should *probably* check these after every read and write, but * I've rigged it so that the buffers will never go out of bounds. * So we can afford to be lazy; if the buffer overflows below it will * silently fail its writes and we'll catch the failure on our next * run through the loop. A failure means we should be * leaving ANYWAY. The contents of our buffers doesn't matter. */ if (SZ_Error(pBuffer_IN) || SZ_Error(pBuffer_OUT)) { return(-1); } /* Divide into 'short' and 'long' */ if (opcode == ANI_LONG_OP) /* long ops */ { opcode = MSG_ReadWord(pBuffer_IN); if (opcode == ANI_STOP) /* We are done decompressing. Leave */ { break; } else if (!(opcode & ANI_LONG_COPY_OR_RLE)) /* If it's not those two, it's a skip */ { unsigned int count = opcode; SZ_Seek(pBuffer_OUT, count, SEEK_CUR); } else /* Now things get a bit more interesting... */ { opcode &= ~ANI_LONG_COPY_OR_RLE; /* Clear that flag */ if (opcode & ANI_LONG_RLE) /* RLE */ { unsigned int count = opcode & ~ANI_LONG_RLE; /* Clear flag */ /* Extract another byte */ unsigned int value = MSG_ReadByte(pBuffer_IN); /* The actual run */ SZ_Memset(pBuffer_OUT, value, count); } else { /* Long copy */ unsigned int count = opcode; /* Copy */ SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count); } } } /* End of long ops */ else /* short ops */ { if (opcode & ANI_SHORT_SKIP) /* Short skip, move pointer only */ { unsigned int count = opcode & ~ANI_SHORT_SKIP; /* clear flag to get count */ SZ_Seek(pBuffer_OUT, count, SEEK_CUR); } else if (opcode == ANI_SHORT_RLE) /* Short RLE, memset the destination */ { /* Extract a few more bytes */ unsigned int count = MSG_ReadByte(pBuffer_IN); unsigned int value = MSG_ReadByte(pBuffer_IN); /* Run */ SZ_Memset(pBuffer_OUT, value, count); } else /* Short copy, memcpy from src to dest. */ { unsigned int count = opcode; /* Dump */ SZ_Memcpy2(pBuffer_OUT, pBuffer_IN, count); } } /* End of short ops */ } /* And that's that */ return(0); } // kate: tab-width 4; vim: set noet: