// $Id: rifffile.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are 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 Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // *********RIFF file handling class /* */ #include "camtypes.h" #include "rifffile.h" //#include "errors.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "riffdata.h" DECLARE_SOURCE("$Revision: 1282 $"); CC_IMPLEMENT_MEMDUMP(RIFFFile, CC_CLASS_MEMDUMP) #define new CAM_DEBUG_NEW #define RIFFFILE_RETURNERR {Error = TRUE; return FALSE;} #define ALIGNSIZE(x) ((((x) & 1) == 0)?(x):((x) + 1)) /******************************************************************************************** > RIFFFile::RIFFFile(void) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: Returns: Purpose: Constructor for the RIFFFile class SeeAlso: ********************************************************************************************/ RIFFFile::RIFFFile(void) { File = NULL; Error = FALSE; AquiredData = NULL; } /******************************************************************************************** > RIFFFile::~RIFFFile(void) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: Returns: Purpose: Destructor for the RIFFFile class SeeAlso: ********************************************************************************************/ RIFFFile::~RIFFFile(void) { // get rid of all the list entries in the file Levels.DeleteAll(); // Get rid of the aquired object, if there is one if(AquiredData != 0) CCFree(AquiredData); } /******************************************************************************************** > FOURCC RIFFFile::Init(CCLexFile *pFile, BOOL NoHeader = FALSE) Author: Ben_Summers (Xara Group Ltd) Created: 21/03/95 Inputs: File to read from, whether it has a header Returns: 0 = not a RIFF file 1 = error occured other = RIFF form type Purpose: Initialises the class for reading in a RIFF file. If NoHeader is TRUE then the file has no header and a RIFF chunk is expected to start immediately. SeeAlso: ********************************************************************************************/ FOURCC RIFFFile::Init(CCLexFile *pFile, BOOL NoHeader) { // store Mr. NiceCCFile File = pFile; FOURCC FileType = 0; // if this file has a header, check it and get the type if(!NoHeader) { // read in the header of the file RIFFFile_Header Header; if(File->read(&Header, sizeof(Header)).bad()) return FALSE; // and check it for being a valid form FileType = CheckRIFFHeader((ADDR)&Header, File->Size()); // and was it? if(FileType == 0) return 0; // not a RIFF form } // OK, set up lots of nice variables for the funky business of RIFF reading ObjType = RIFFOBJECTTYPE_NONE; RepeatObject = FALSE; Error = FALSE; Location = NoHeader?0:sizeof(RIFFFile_Header); Size = File->Size(); GotData = FALSE; CurrentLevel = 0; AquiredData = 0; AlignedObjectSize = 0; return FileType; } /******************************************************************************************** > FOURCC RIFFFile::CheckRIFFHeader(ADDR Header, DWORD Size) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: None Returns: 0 = not a RIFF file other = RIFF form type Purpose: Checks to see if the header supplied is that of a valid RIFF form. Useful for HowCompatable functions in filter code. SeeAlso: ********************************************************************************************/ FOURCC RIFFFile::CheckRIFFHeader(ADDR Header, DWORD Size) { PORTNOTE("byteorder", "TODO: Check byte ordering") RIFFFile_Header *Hdr = (RIFFFile_Header *)Header; if(RIFFDATA_FOURCC(Hdr->CK.ckID) != RIFFTYPE_RIFF) return 0; // file doesn't start "RIFF" if(RIFFDATA_DWORD(Hdr->CK.ckSize) > (Size - sizeof(RIFFFile_Header)) && RIFFDATA_DWORD(Hdr->CK.ckSize) < (Size - sizeof(RIFFFile_Header))) return 0; // Size doesn't match that in header. Be a bit relaxed about exact lengths return Hdr->FormType; } /******************************************************************************************** > BOOL RIFFFile::NextObject(void) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: None Returns: whether an error occured Purpose: Initialises the class for reading in a RIFF file SeeAlso: ********************************************************************************************/ BOOL RIFFFile::NextObject(void) { if(Error) return FALSE; // do we need to return the current object again? if(RepeatObject) { RepeatObject = FALSE; return TRUE; // all data nicely set up already } // skip over any data it it hasn't already been got if(ObjType == RIFFOBJECTTYPE_CHUNK && (GotData == FALSE || Location != (ObjLocation + AlignedObjectSize))) { if(File->seek(ObjLocation + AlignedObjectSize).bad()) { TRACEUSER( "Ben", _T("RIFFFile: Error on seek when jumping object data\n")); RIFFFILE_RETURNERR; } Location = ObjLocation + AlignedObjectSize; // as we may come this way again... GotData = TRUE; } // have we gone past the end of the file? if(Location >= Size) //if (File->eof()) { // Yes. Firstly, ensure that all the lists got the end list thingy - is the list empty? if(!Levels.IsEmpty()) { TRACEUSER( "Ben", _T("RIFFFile: Ending... terminating list\n")); RIFFFileLevel *Level = (RIFFFileLevel *)Levels.GetTail(); ERROR3IF(Level == NULL, "List class returned a null tail for a non empty list! Help!"); // set up a nice list end object ObjType = RIFFOBJECTTYPE_LISTEND; ObjChunkType = Level->Type; ObjLevel = CurrentLevel; ObjLocation = Location; GotData = TRUE; // level down one CurrentLevel--; // get rid of the last level on the list delete Levels.RemoveTail(); return TRUE; } // And if they all have, say that the file ended ObjType = RIFFOBJECTTYPE_FILEEND; ObjChunkType = 0; ObjSize = 0; ObjLevel = 0; ObjLocation = Location; GotData = TRUE; TRACEUSER( "Ben", _T("returning file end\n")); return TRUE; } // check to see if the current list has ended if(!Levels.IsEmpty()) { RIFFFileLevel *Level = (RIFFFileLevel *)Levels.GetTail(); ERROR3IF(Level == NULL, "List class returned a null tail for a non empty list! Help!"); if(Location >= Level->End) { // RIFF list has ended ObjType = RIFFOBJECTTYPE_LISTEND; ObjChunkType = Level->Type; ObjLevel = CurrentLevel; ObjSize = 0; ObjLocation = Location; GotData = TRUE; // down a level CurrentLevel--; // delete the last entry in the list delete Levels.RemoveTail(); return TRUE; } } // right then, let's see what the next object in the file is. RIFFck ChunkHeader; if(File->read(&ChunkHeader, sizeof(ChunkHeader)).bad()) { TRACEUSER( "Ben", _T("RIFFFile: file error on reading chunk header\n")); RIFFFILE_RETURNERR; } Location += sizeof(ChunkHeader); // is it a list chunk? if(RIFFDATA_FOURCC(ChunkHeader.ckID) == RIFFTYPE_LIST) { // yep... process it RIFFFile_List ListHdr; // read in a list header if(File->read(&ListHdr, sizeof(ListHdr)).bad()) { TRACEUSER( "Ben", _T("RIFFFile: file error on reading list id\n")); RIFFFILE_RETURNERR; } Location += sizeof(ListHdr); // make a new entry in the list RIFFFileLevel *NewLevel; if((NewLevel = new RIFFFileLevel) == 0) RIFFFILE_RETURNERR; NewLevel->Start = Location; NewLevel->End = Location + ALIGNSIZE(RIFFDATA_DWORD(ChunkHeader.ckSize)) - sizeof(ListHdr); NewLevel->Type = RIFFDATA_FOURCC(ListHdr.ListType); // and add it to the list Levels.AddTail(NewLevel); // up a level CurrentLevel++; // return a nice list start object ObjType = RIFFOBJECTTYPE_LISTSTART; ObjChunkType = RIFFDATA_FOURCC(ListHdr.ListType); ObjSize = 0; ObjLevel = CurrentLevel; ObjLocation = Location; GotData = TRUE; return TRUE; } // and after all that excitement, if it was just a normal, boring chunk, return stuff about that ObjType = RIFFOBJECTTYPE_CHUNK; ObjChunkType = RIFFDATA_FOURCC(ChunkHeader.ckID); ObjSize = RIFFDATA_DWORD(ChunkHeader.ckSize); ObjLevel = CurrentLevel; ObjLocation = Location; // set up some useful data GotData = FALSE; AlignedObjectSize = ALIGNSIZE(ObjSize); // and that's all folks return TRUE; } /******************************************************************************************** > BOOL RIFFFile::GetChunkData(ADDR Block, INT32 Size) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: Pointer to the block where you want the data, and the size of the block Returns: whether an error occured Purpose: Gets the all data from a RIFF chunk SeeAlso: ********************************************************************************************/ BOOL RIFFFile::GetChunkData(ADDR Block, INT32 BSize) { // first of all, check to see if the caller has been a little bit silly if(ObjType != RIFFOBJECTTYPE_CHUNK) { Error = TRUE; // only chunks have getable data ERROR2(FALSE, "RIFFFile::GetChunkData called for a non-chunk object\n"); } if(BSize < ObjSize) { Error = TRUE; // wrong size... ERROR2(FALSE, "RIFFFile::GetChunkData called with a block which is just *too* small\n"); } // has the data already been got for this object? if(GotData) { // yep, go back and get it again TRACEUSER( "Ben", _T("Chunk data asked for more than once\n")); if(File->seek(ObjLocation).bad()) RIFFFILE_RETURNERR; Location = ObjLocation; } // and get that data! if(File->read(Block, ObjSize).bad()) { TRACEUSER( "Ben", _T("RIFFFile: file error when reading chunk data\n")); RIFFFILE_RETURNERR; } // ensure alignment is maintained within the file if(ObjSize != AlignedObjectSize) { // read a byte to align this to the correct position BYTE junk; if(File->read(&junk, sizeof(junk)).bad()) RIFFFILE_RETURNERR; } // update locations Location += AlignedObjectSize; // and set an essential flag! GotData = TRUE; return TRUE; } /******************************************************************************************** > BOOL RIFFFile::GetChunkData(ADDR Block, INT32 Size, INT32 Offset) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: Pointer to the block where you want the data, and the size of the block and the offset within the block of the data you want Returns: whether an error occured Purpose: Gets a certain amount of the data from a RIFF chunk. ATM it's a bit inefficient SeeAlso: ********************************************************************************************/ BOOL RIFFFile::GetChunkData(ADDR Block, INT32 BSize, INT32 Offset) { // first of all, check to see if the caller has been a little bit silly if(ObjType != RIFFOBJECTTYPE_CHUNK) { Error = TRUE; // only chunks have getable data ERROR2(FALSE, "RIFFFile::GetChunkData called for a non-chunk object\n"); } // check offset is OK if(Offset >= ObjSize) Offset = ObjSize - 1; // check that we'll be grabbing a nice amount of data if((Offset + BSize) > ObjSize) BSize = ObjSize - Offset; // don't do anything if there's no data to get if(BSize <= 0) return TRUE; // where in the file do we wish to get data from? INT32 TargetLocation = ObjLocation + Offset; // seek to the data we want, but only if it's necessary if(TargetLocation != Location) { if(File->seek(TargetLocation).bad()) RIFFFILE_RETURNERR; Location = TargetLocation; } // and get that data! if(File->read(Block, BSize).bad()) { TRACEUSER( "Ben", _T("RIFFFile: file error when reading chunk data\n")); RIFFFILE_RETURNERR; } // update locations Location += BSize; // and set an essential flag! GotData = TRUE; return TRUE; } /******************************************************************************************** > BOOL RIFFFile::RIFFFile::SkipToListEnd(UINT32 Level) Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: The level number of the list to skip to the end of Returns: whether an error occured Purpose: Skips to the end of a given level. ListEnd objects are not returned for the lists you skip SeeAlso: ********************************************************************************************/ BOOL RIFFFile::SkipToListEnd(UINT32 LevelNumber) { RIFFFileLevel *Level; RIFFFileLevel *Next; TRACEUSER( "Ben", _T("SkipToListEnd called to level %d\n"), LevelNumber); Level = (RIFFFileLevel *)Levels.FindItem(LevelNumber - 1); // - 1 because level 0 doesn't have an entry as // it's the root level of the file ERROR2IF(Level == 0, FALSE, "Couldn't find given level to jump to in list"); // seek to the end of the level if(File->seek(Level->End).bad()) { TRACEUSER( "Ben", _T("RIFFFile: file error when seeking to end of level\n")); RIFFFILE_RETURNERR; } // update the location Location = Level->End; // update the level we're at CurrentLevel = LevelNumber - 1; // remove the last few entries from the list, including this one while(Level != 0) { Next = (RIFFFileLevel *)Levels.GetNext(Level); delete Levels.RemoveItem(Level); Level = Next; } return TRUE; } /******************************************************************************************** > BOOL RIFFFile::EnsureAquiredSizeIsAtLeast(INT32 Size) Author: Ben_Summers (Xara Group Ltd) Created: 06/06/95 Inputs: minimum size required Returns: whether an error occured Purpose: Ensures that the aquired block is at least a certain size SeeAlso: ********************************************************************************************/ BOOL RIFFFile::EnsureAquiredSizeIsAtLeast(INT32 Size) { if(AquiredData == 0 || AquiredDataSize < Size) { if(AquiredData != 0) { CCFree(AquiredData); AquiredData = 0; } if((AquiredData = (BYTE *)CCMalloc(Size)) == 0) RIFFFILE_RETURNERR; AquiredDataSize = Size; } return TRUE; } /******************************************************************************************** > BOOL RIFFFile::AquireChunkData() Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: none Returns: whether an error occured Purpose: Gets the data from a RIFF chunk and puts it into a buffer maintained by the class. Any previous data is lost. SeeAlso: ********************************************************************************************/ BOOL RIFFFile::AquireChunkData() { // check to see if the current object is a chunk ERROR2IF(ObjType != RIFFOBJECTTYPE_CHUNK, FALSE, "Tried an AquiredChunkData on a non-chunk RIFF object\n"); // ensure that AquiredData points at enough space for this to be hoopy if(!EnsureAquiredSizeIsAtLeast(ObjSize)) return FALSE; // there is at enough space in AquiredData to store the current object // use GetChunkData on it return GetChunkData(AquiredData, AquiredDataSize); } /******************************************************************************************** > BOOL RIFFFile::AquireListContents(INT32 *ListSize) Author: Ben_Summers (Xara Group Ltd) Created: 06/06/95 Inputs: none Returns: whether an error occured Purpose: Aquires the data of an entire list. Fills in ListSize to be the size of the data. SeeAlso: ********************************************************************************************/ BOOL RIFFFile::AquireListContents(INT32 *ListSize) { ERROR2IF(ObjType != RIFFOBJECTTYPE_LISTSTART, FALSE, "Tried an GetListContents on a non-list start RIFF object\n"); // OK, first thing we need to do is to get the list's header... where is it? INT32 StartLocation = Location - sizeof(RIFFck) - sizeof(RIFFFile_List); // seek there if(File->seek(StartLocation).bad()) { RIFFFILE_RETURNERR; } // OK, get the chunk header (not the list header which gives it's type RIFFck ChunkHeader; if(File->read(&ChunkHeader, sizeof(ChunkHeader)).bad()) { RIFFFILE_RETURNERR; } // right then, we now know how much data we need to extract from the file... INT32 ContentsSize = RIFFDATA_DWORD(ChunkHeader.ckSize) + sizeof(RIFFck); // now we need a block of data to get it into if(!EnsureAquiredSizeIsAtLeast(ContentsSize)) return FALSE; // and getting it would be a cunning plan! seek to the beginning if(File->seek(StartLocation).bad()) { RIFFFILE_RETURNERR; } // grab the data if(File->read(AquiredData, ContentsSize).bad()) { RIFFFILE_RETURNERR; } // seek back to the location we were at the beginning if(File->seek(Location).bad()) { RIFFFILE_RETURNERR; } // and set up the return values *ListSize = ContentsSize; return TRUE; } /******************************************************************************************** > BOOL RIFFFile::RepeatCurrent() Author: Ben_Summers (Xara Group Ltd) Created: 21 03 95 Inputs: none Returns: whether an error occured Purpose: Makes the next NextObject return the current object SeeAlso: ********************************************************************************************/ BOOL RIFFFile::RepeatCurrent() { if(RepeatObject == TRUE) { TRACEUSER( "Ben", _T("RepeatObject already set for this object...\n")); } RepeatObject = TRUE; return TRUE; } /******************************************************************************************** > BOOL RIFFFile::GetListContents(ADDR *Block, INT32 *Size) Author: Ben_Summers (Xara Group Ltd) Created: 19/04/95 Inputs: pointer to a pointer for the block address, pointer to size to return size Returns: whether an error occured Purpose: gets the contents from a list. This is in the raw form from the RIFF file SeeAlso: ********************************************************************************************/ BOOL RIFFFile::GetListContents(ADDR *Block, INT32 *Size) { ERROR2IF(ObjType != RIFFOBJECTTYPE_LISTSTART, FALSE, "Tried an GetListContents on a non-list start RIFF object\n"); // OK, first thing we need to do is to get the list's header... where is it? INT32 StartLocation = Location - sizeof(RIFFck) - sizeof(RIFFFile_List); // seek there if(File->seek(StartLocation).bad()) { RIFFFILE_RETURNERR; } // OK, get the chunk header (not the list header which gives it's type RIFFck ChunkHeader; if(File->read(&ChunkHeader, sizeof(ChunkHeader)).bad()) { RIFFFILE_RETURNERR; } // right then, we now know how much data we need to extract from the file... INT32 ContentsSize = RIFFDATA_DWORD(ChunkHeader.ckSize) + sizeof(RIFFck); // now we need a block of data to get it into ADDR Contents = (ADDR)CCMalloc(ContentsSize); if(Contents == 0) return FALSE; // and getting it would be a cunning plan! seek to the beginning if(File->seek(StartLocation).bad()) { RIFFFILE_RETURNERR; } // grab the data if(File->read(Contents, ContentsSize).bad()) { RIFFFILE_RETURNERR; } // seek back to the location we were at the beginning if(File->seek(Location).bad()) { RIFFFILE_RETURNERR; } // and set up the return values *Block = Contents; *Size = ContentsSize; return TRUE; } /******************************************************************************************** > BOOL RIFFFile::FindChunkWithinListContents(ADDR ListContents, INT32 ListSize, FOURCC Type, INT32 Entry, ADDR *Chunk, INT32 *ChunkSize); Author: Ben_Summers (Xara Group Ltd) Created: 06/06/95 Inputs: ListContents Pointer to list contents ListSize Size of the list Type Type of chunk required Entry The number of that type of chunk to skip Chunk Pointer to a pointer to a chunk to be filled in ChunkSize Pointer to a value to be filled in with the chunk length. Returns: whether the chunk could be found Purpose: Finds a chunk within a list ********************************************************************************************/ BOOL RIFFFile::FindChunkWithinListContents(ADDR ListContents, INT32 ListSize, FOURCC Type, INT32 Entry, ADDR *Chunk, INT32 *ChunkSize) { RIFFck *Header; INT32 OffsetWithin = sizeof(RIFFck) + sizeof(RIFFFile_List); while(OffsetWithin < ListSize) { Header = (RIFFck *)(ListContents + OffsetWithin); INT32 Size = RIFFDATA_DWORD(Header->ckSize); if(RIFFDATA_FOURCC(Header->ckID) == Type) { if(Entry <= 0) { // return this one... if(Chunk != 0) (*Chunk) = ListContents + OffsetWithin + sizeof(RIFFck); if(ChunkSize != 0) (*ChunkSize) = Size; return TRUE; } else { Entry--; } } OffsetWithin += Size + sizeof(RIFFck); } return FALSE; }