mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-17 06:06:00 -06:00
373 lines
8.9 KiB
C++
373 lines
8.9 KiB
C++
/* rifffile.cpp
|
|
|
|
Copyright (c) 1996, 1988 by Timothy J. Weber.
|
|
|
|
See rifffile.txt for documentation.
|
|
*/
|
|
|
|
#include "rifffile.h"
|
|
|
|
using namespace std;
|
|
|
|
/***************************************************************************
|
|
macros and constants
|
|
***************************************************************************/
|
|
|
|
// define REVERSE_ENDIANISM if the endianism of the host platform is not Intel
|
|
// (Intel is little-endian)
|
|
#ifdef REVERSE_ENDIANISM
|
|
#define SWAP_32(int32) ( \
|
|
((((DWORD) int32) & 0x000000FFL) << 24) + \
|
|
((((DWORD) int32) & 0x0000FF00L) << 8) + \
|
|
((((DWORD) int32) & 0x00FF0000L) >> 8) + \
|
|
((((DWORD) int32) & 0xFF000000L) >> 24))
|
|
#endif
|
|
|
|
struct TypeRecord {
|
|
char* typeName; // four-letter name
|
|
char* realName; // English name
|
|
};
|
|
|
|
const int numExtraTypes = 24;
|
|
const TypeRecord extraTypes[numExtraTypes] = {
|
|
{ "DISP", "Display name" },
|
|
{ "IARL", "Archival location" },
|
|
{ "IART", "Artist" },
|
|
{ "ICMS", "Commissioned" },
|
|
{ "ICMT", "Comments" },
|
|
{ "ICOP", "Copyright" },
|
|
{ "ICRD", "Creation date" },
|
|
{ "ICRP", "Cropped" },
|
|
{ "IDIM", "Dimensions" },
|
|
{ "IDPI", "Dots Per Inch" },
|
|
{ "IENG", "Engineer" },
|
|
{ "IGNR", "Genre" },
|
|
{ "IKEY", "Keywords" },
|
|
{ "ILGT", "Lightness" },
|
|
{ "IMED", "Medium" },
|
|
{ "INAM", "Name" },
|
|
{ "IPLT", "Palette Setting" },
|
|
{ "IPRD", "Product" },
|
|
{ "ISBJ", "Subject" },
|
|
{ "ISFT", "Software" },
|
|
{ "ISHP", "Sharpness" },
|
|
{ "ISRC", "Source" },
|
|
{ "ISRF", "Source Form" },
|
|
{ "ITCH", "Technician" },
|
|
};
|
|
|
|
/***************************************************************************
|
|
typedefs and class definitions
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
prototypes for static functions
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
static variables
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
member functions for RiffFile
|
|
***************************************************************************/
|
|
|
|
RiffFile::RiffFile(const char *name):
|
|
fp(fopen(name, "rb"))
|
|
{
|
|
if (fp && !rewind()) {
|
|
fclose(fp);
|
|
fp = 0;
|
|
}
|
|
}
|
|
|
|
RiffFile::~RiffFile()
|
|
{
|
|
if (fp)
|
|
fclose(fp);
|
|
}
|
|
|
|
bool RiffFile::rewind()
|
|
{
|
|
// clear the chunk stack
|
|
while (!chunks.empty())
|
|
chunks.pop();
|
|
|
|
// rewind to the start of the file
|
|
if (fseek(fp, 0, SEEK_SET))
|
|
return false;
|
|
|
|
// look for a valid RIFF header
|
|
RiffChunk topChunk(*this);
|
|
|
|
if (feof(fp) || strcmp(topChunk.name, "RIFF"))
|
|
return false;
|
|
|
|
// found; push it on the stack, and leave the put pointer in the same place
|
|
// as the get pointer.
|
|
formSize = topChunk.size;
|
|
chunks.push(topChunk);
|
|
return true;
|
|
}
|
|
|
|
bool RiffFile::push(const char* chunkType)
|
|
{
|
|
// can't descend if we haven't started out yet.
|
|
if (chunks.empty())
|
|
return false;
|
|
|
|
// first, go to the start of the current chunk, if we're looking for a named
|
|
// chunk.
|
|
if (chunkType)
|
|
if (fseek(fp, chunks.top().start, SEEK_SET))
|
|
return false;
|
|
|
|
// read chunks until one matches or we exhaust this chunk
|
|
while (!feof(fp) && ftell(fp) < chunks.top().after) {
|
|
RiffChunk chunk(*this);
|
|
|
|
if (!feof(fp)) {
|
|
// see if the subchunk type matches
|
|
if (!chunkType || strcmp(chunk.name, chunkType) == 0) {
|
|
// found; synchronize the put pointer, push the chunk, and succeed
|
|
chunks.push(chunk);
|
|
return true;
|
|
} else {
|
|
// not found; go to the next one.
|
|
if (fseek(fp, chunk.after, SEEK_SET))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// couldn't find it; synchronize the put pointer and return error.
|
|
fseek(fp, chunks.top().start, SEEK_SET);
|
|
return false;
|
|
}
|
|
|
|
bool RiffFile::pop()
|
|
{
|
|
// if we've only got the top level chunk (or not even that), then we can't
|
|
// go up.
|
|
if (chunks.size() < 2)
|
|
return false;
|
|
|
|
// Position the get and put pointers at the end of the current subchunk.
|
|
fseek(fp, chunks.top().after, SEEK_SET);
|
|
|
|
// Pop up the stack.
|
|
chunks.pop();
|
|
return true;
|
|
}
|
|
|
|
long RiffFile::chunkSize() const
|
|
{
|
|
if (!chunks.empty())
|
|
return chunks.top().size;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
const char* RiffFile::chunkName() const
|
|
{
|
|
if (!chunks.empty())
|
|
return chunks.top().name;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
const char* RiffFile::subType() const
|
|
{
|
|
if (!chunks.empty() && chunks.top().subType[0])
|
|
return chunks.top().subType;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool RiffFile::getNextExtraItem(string& type, string& value)
|
|
{
|
|
// if the current chunk is LIST/INFO, then try to read another subchunk.
|
|
if (strcmp(chunkName(), "LIST") == 0
|
|
&& strcmp(subType(), "INFO") == 0)
|
|
{
|
|
if (push()) {
|
|
if (readExtraItem(type, value))
|
|
return true;
|
|
else
|
|
// unrecognized type. Continue on.
|
|
return getNextExtraItem(type, value);
|
|
} else {
|
|
// got to the end of the LIST/INFO chunk. Pop back out and continue
|
|
// looking.
|
|
pop();
|
|
return getNextExtraItem(type, value);
|
|
}
|
|
// we're not in a LIST/INFO chunk, so look for the next DISP or LIST/INFO.
|
|
} else {
|
|
push();
|
|
|
|
if (strcmp(chunkName(), "DISP") == 0) {
|
|
// DISP chunk: read and pop back out.
|
|
return readExtraItem(type, value);
|
|
} else if (strcmp(chunkName(), "LIST") == 0
|
|
&& strcmp(subType(), "INFO") == 0)
|
|
{
|
|
// LIST/INFO chunk: read first element
|
|
return getNextExtraItem(type, value);
|
|
} else {
|
|
// Some other chunk. Pop back out and move on.
|
|
if (pop())
|
|
return getNextExtraItem(type, value);
|
|
else
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reads extra data from the current chunk, and pops out of it.
|
|
bool RiffFile::readExtraItem(string& type, string& value)
|
|
{
|
|
// see if it's one we recognize
|
|
bool found = false;
|
|
for (int i = 0; i < numExtraTypes; i++) {
|
|
if (strcmp(chunkName(), extraTypes[i].typeName) == 0) {
|
|
type = extraTypes[i].realName;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
// DISP chunks skip four bytes before the display name starts.
|
|
if (strcmp(chunkName(), "DISP") == 0) {
|
|
fgetc(filep());
|
|
fgetc(filep());
|
|
fgetc(filep());
|
|
fgetc(filep());
|
|
}
|
|
|
|
// read the value, if we recognize the type
|
|
if (found) {
|
|
int c;
|
|
value = "";
|
|
while ((c = fgetc(filep())) != '\0' && c != EOF)
|
|
value += char(c);
|
|
}
|
|
|
|
// whether we recognize it or not, pop back out.
|
|
pop();
|
|
return found;
|
|
}
|
|
|
|
/***************************************************************************
|
|
member functions for RiffChunk
|
|
***************************************************************************/
|
|
|
|
RiffChunk::RiffChunk(RiffFile& parent)
|
|
{
|
|
// read the chunk name
|
|
fread(name, 1, 4, parent.filep());
|
|
name[4] = '\0';
|
|
|
|
// read the chunk size
|
|
fread(&size, 4, 1, parent.filep());
|
|
|
|
#ifdef REVERSE_ENDIANISM
|
|
// reverse the endianism of the chunk size.
|
|
size = SWAP_32(size);
|
|
#endif
|
|
|
|
// if this is a RIFF or LIST chunk, read its subtype.
|
|
if (strcmp(name, "RIFF") == 0
|
|
|| strcmp(name, "LIST") == 0)
|
|
{
|
|
fread(subType, 1, 4, parent.filep());
|
|
subType[4] = '\0';
|
|
|
|
// subtract the subtype from the size of the data.
|
|
size -= 4;
|
|
} else
|
|
*subType = '\0';
|
|
|
|
// the chunk starts after the name and size.
|
|
start = ftell(parent.filep());
|
|
|
|
// the next chunk starts after this one, but starts on a word boundary.
|
|
after = start + size;
|
|
if (after % 2)
|
|
after++;
|
|
}
|
|
|
|
/***************************************************************************
|
|
main()
|
|
***************************************************************************/
|
|
|
|
#ifdef TEST_RIFFFILE
|
|
|
|
#include <iostream>
|
|
|
|
static void reportProblem()
|
|
{
|
|
cout << " *** ERROR: Result incorrect." << endl;
|
|
}
|
|
|
|
static void checkResult(bool got, bool expected)
|
|
{
|
|
if (got)
|
|
cout << "success." << endl;
|
|
else
|
|
cout << "fail." << endl;
|
|
|
|
if (got != expected)
|
|
reportProblem();
|
|
}
|
|
|
|
static void pause()
|
|
{
|
|
cout << "Press Enter to continue." << endl;
|
|
cin.get();
|
|
}
|
|
|
|
static void showChunk(RiffFile& file, int indent, bool expandLists)
|
|
{
|
|
for (int i = 0; i < indent; i++)
|
|
cout << ' ';
|
|
|
|
cout << "Chunk type: " << file.chunkName();
|
|
|
|
if (file.subType())
|
|
cout << " (" << file.subType() << ")";
|
|
|
|
cout << ", " << file.chunkSize() << " bytes" << endl;
|
|
|
|
// show all subchunks
|
|
if (strcmp(file.chunkName(), "RIFF") == 0
|
|
|| (expandLists && strcmp(file.chunkName(), "LIST") == 0))
|
|
{
|
|
while (file.push()) {
|
|
showChunk(file, indent + 2, expandLists);
|
|
file.pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, const char* argv[])
|
|
{
|
|
if (argc == 1)
|
|
cout << "Lists the chunks in a named RIFF file." << endl;
|
|
else {
|
|
RiffFile file(argv[1]);
|
|
|
|
if (!file.filep()) {
|
|
cout << "No RIFF form in file " << argv[1] << "." << endl;
|
|
} else {
|
|
cout << "Without expanding lists:" << endl;
|
|
showChunk(file, 0, false);
|
|
|
|
cout << endl << "Expanding lists:" << endl;
|
|
showChunk(file, 0, true);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|