421 lines
11 KiB
C++
Executable File
421 lines
11 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
#include "console/console.h"
|
|
#include "console/consoleTypes.h"
|
|
#include "core/chunkFile.h"
|
|
|
|
InfiniteBitStream gChunkBuffer;
|
|
InfiniteBitStream gHeaderBuffer;
|
|
|
|
const U32 ChunkFile::csmFileFourCC = makeFourCCTag('T', 'C', 'H', 'K');
|
|
const U32 ChunkFile::csmFileVersion = 2;
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool ChunkFile::save(const char * filename)
|
|
{
|
|
// Just to be sure, wipe stuff.
|
|
gChunkBuffer.reset();
|
|
|
|
// Get a stream...
|
|
FileStream s;
|
|
if(!ResourceManager->openFileForWrite(s, filename))
|
|
{
|
|
Con::errorf("ChunkFile::save - cannot open '%s' for write.", filename);
|
|
return false;
|
|
}
|
|
|
|
// write preamble!
|
|
s.write(csmFileFourCC);
|
|
s.write(csmFileVersion);
|
|
|
|
// Now save out the chunks!
|
|
saveInner(s, getRoot());
|
|
|
|
// Free any memory we had to use for buffering.
|
|
gChunkBuffer.reset();
|
|
gChunkBuffer.compact();
|
|
|
|
// All done!
|
|
return true;
|
|
}
|
|
|
|
bool ChunkFile::saveInner(Stream &s, SimChunk *c)
|
|
{
|
|
// First, write the chunk to a buffer so we can meditate on it
|
|
gChunkBuffer.reset();
|
|
c->writeChunk(gChunkBuffer);
|
|
|
|
// Buffer the header...
|
|
gHeaderBuffer.reset();
|
|
|
|
// Prepare the header
|
|
gHeaderBuffer.writeCussedU32(c->getChunkVersion());
|
|
gHeaderBuffer.writeCussedU32(gChunkBuffer.getPosition());
|
|
gHeaderBuffer.writeString((const char *)c->getFourCCString(), 4);
|
|
gHeaderBuffer.writeCussedU32(c->size()); // Child count.
|
|
gHeaderBuffer.write(gChunkBuffer.getCRC());
|
|
|
|
// Write the header (first size, then data)!
|
|
AssertFatal(gHeaderBuffer.getPosition() < 256,
|
|
"ChunkFile::saveInner - got too big header!");
|
|
s.write(U8(gHeaderBuffer.getPosition()));
|
|
gHeaderBuffer.writeToStream(s);
|
|
gHeaderBuffer.reset();
|
|
|
|
// Write the chunk!
|
|
gChunkBuffer.writeToStream(s);
|
|
gChunkBuffer.reset();
|
|
|
|
// Recurse!
|
|
for(U32 i=0; i<c->size(); i++)
|
|
{
|
|
// We have to assume people will stuff all sorts of crap into this thing.
|
|
SimChunk *sc = dynamic_cast<SimChunk*>((*c)[i]);
|
|
|
|
if(sc)
|
|
saveInner(s, sc);
|
|
else
|
|
{
|
|
Con::errorf("ChunkFile::saveInner - got unexpected class '%s' as child of object %d!",
|
|
(c ? (*c)[i]->getClassName() : "NULL OBJECT"), c->getId());
|
|
|
|
// Write a null header, eww gross.
|
|
gHeaderBuffer.writeCussedU32(0);
|
|
gHeaderBuffer.writeCussedU32(0);
|
|
gHeaderBuffer.writeString("", 4);
|
|
gHeaderBuffer.writeCussedU32(0); // Child count.
|
|
gHeaderBuffer.write(0);
|
|
|
|
// That should allow us to recover gracefully when we load...
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool ChunkFile::load(Stream &s)
|
|
{
|
|
// check preamble!
|
|
U32 fourcc, ver;
|
|
s.read(&fourcc);
|
|
s.read(&ver);
|
|
|
|
if(fourcc != csmFileFourCC)
|
|
{
|
|
Con::errorf("ChunkFile::load - unexpected header value!");
|
|
return false;
|
|
}
|
|
|
|
if(ver != csmFileVersion)
|
|
{
|
|
// Be clever
|
|
if(ver > csmFileVersion)
|
|
{
|
|
Con::errorf("ChunkFile::load - encountered file header version from the future!");
|
|
}
|
|
else
|
|
{
|
|
Con::errorf("ChunkFile::load - encountered file header version from the past!");
|
|
}
|
|
|
|
// Error out.
|
|
return false;
|
|
}
|
|
|
|
// Now recursively load all our children!
|
|
mRoot = loadInner(s);
|
|
|
|
// Add us to the ChunkFileGroup
|
|
Sim::getChunkFileGroup()->addObject(mRoot);
|
|
|
|
return true;
|
|
}
|
|
|
|
SimChunk *ChunkFile::loadInner(Stream &s, U32 childCount)
|
|
{
|
|
U8 headerSize;
|
|
U32 fourCC, ver, crc, size;
|
|
S32 children;
|
|
|
|
// Read the header size and prepare to read the header bitstream...
|
|
s.read(&headerSize);
|
|
|
|
U8 *headerBuff = new U8[headerSize];
|
|
s.read(headerSize, headerBuff);
|
|
|
|
BitStream hData(headerBuff, headerSize);
|
|
|
|
// Read actual header values.
|
|
ver = hData.readCussedU32();
|
|
size = hData.readCussedU32();
|
|
|
|
// Get the fourcc...
|
|
char fcctmp[256]; // For safety we make it 256, longest size string we can read.
|
|
hData.readString(fcctmp);
|
|
fourCC = makeFourCCTag(fcctmp[0], fcctmp[1], fcctmp[2], fcctmp[3]);
|
|
|
|
children = hData.readCussedU32();
|
|
hData.read(&crc);
|
|
|
|
delete[] headerBuff;
|
|
|
|
// Create the chunk...
|
|
SimChunk *sc = SimChunk::createChunkFromFourCC(fourCC);
|
|
|
|
// Do some version sanity checking.
|
|
if(ver > sc->getChunkVersion())
|
|
{
|
|
// uh oh, it's a chunk from the future!
|
|
Con::warnf("ChunkFile::loadInner - found a '%s' chunk with version %d; highest supported version is %d. Treating as unknown chunk...",
|
|
fcctmp, ver, sc->getChunkVersion());
|
|
|
|
// Let's use an unknown chunk instead.
|
|
delete sc;
|
|
sc = new UnknownChunk();
|
|
}
|
|
|
|
// Read the chunk data into a buffer...
|
|
U8 *buff = new U8[size];
|
|
s.read(size, buff);
|
|
|
|
BitStream cData(buff, size);
|
|
sc->readChunk(cData, size, ver, crc, fourCC);
|
|
|
|
delete[] buff;
|
|
|
|
// Register it!
|
|
sc->registerObject();
|
|
|
|
// Recurse on children, adding them to our SimChunk.
|
|
for(S32 i=0; i<children; i++)
|
|
sc->addObject(loadInner(s, childCount));
|
|
|
|
// All done!
|
|
return sc;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
ResourceInstance *ChunkFile::constructChunkFile(Stream &stream)
|
|
{
|
|
ChunkFile *cf = new ChunkFile();
|
|
if(cf->load(stream))
|
|
{
|
|
return cf;
|
|
}
|
|
else
|
|
{
|
|
delete cf;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
Vector<SimChunk::FourCCToAcr*> SimChunk::smFourCCList;
|
|
|
|
void SimChunk::initChunkMappings()
|
|
{
|
|
Con::printf("Initializing chunk mappings...");
|
|
|
|
for(AbstractClassRep *rep = AbstractClassRep::getClassList(); rep; rep = rep->getNextClass())
|
|
{
|
|
ConsoleObject *obj = rep->create();
|
|
|
|
SimChunk *chunk;
|
|
|
|
if(obj && (chunk = dynamic_cast<SimChunk *>(obj)))
|
|
{
|
|
Con::printf(" o '%s' maps to %s", chunk->getFourCCString(), chunk->getClassName());
|
|
|
|
FourCCToAcr * fcta = new FourCCToAcr();
|
|
|
|
fcta->fourCC = chunk->getFourCC();
|
|
fcta->acr = rep;
|
|
|
|
smFourCCList.push_back(fcta);
|
|
}
|
|
|
|
delete obj;
|
|
}
|
|
|
|
// Also register the .chunk file format.
|
|
ResourceManager->registerExtension(".chunk", ChunkFile::constructChunkFile);
|
|
}
|
|
|
|
SimChunk *SimChunk::createChunkFromFourCC(U32 fourCC)
|
|
{
|
|
// Try to find a match.
|
|
for(U32 i=0; i<smFourCCList.size(); i++)
|
|
{
|
|
if(smFourCCList[i]->fourCC == fourCC)
|
|
return (SimChunk*)smFourCCList[i]->acr->create();
|
|
}
|
|
|
|
// Unknown 4cc, let's use the UnknownChunk
|
|
U8 c[4];
|
|
c[0] = fourCC >> 24;
|
|
c[1] = fourCC >> 16;
|
|
c[2] = fourCC >> 8;
|
|
c[3] = fourCC >> 0;
|
|
Con::warnf("SimChunk::createChunkFromFourCC - encountered unknown fourCC '%c%c%c%c'", c[0], c[1], c[2], c[3]);
|
|
|
|
return new UnknownChunk();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CONOBJECT(SimChunk);
|
|
|
|
void SimChunk::writeChunk(BitStream &b)
|
|
{
|
|
|
|
}
|
|
|
|
void SimChunk::readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
|
|
{
|
|
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
IMPLEMENT_CONOBJECT(UnknownChunk);
|
|
|
|
UnknownChunk::UnknownChunk()
|
|
{
|
|
mChunkFourCC = mChunkVersion = mChunkCRC = mDataLength = 0;
|
|
mDataBuffer = NULL;
|
|
}
|
|
|
|
UnknownChunk::~UnknownChunk()
|
|
{
|
|
if(mDataBuffer)
|
|
dFree(mDataBuffer);
|
|
mDataBuffer = NULL;
|
|
}
|
|
|
|
void UnknownChunk::writeChunk(BitStream &b)
|
|
{
|
|
b.write(mDataLength, mDataBuffer);
|
|
}
|
|
|
|
void UnknownChunk::readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
|
|
{
|
|
mDataBuffer = (U8*)dMalloc(length);
|
|
s.read(length, mDataBuffer);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
class TextChunk : public SimChunk
|
|
{
|
|
typedef SimChunk Parent;
|
|
|
|
protected:
|
|
const char * mText;
|
|
|
|
public:
|
|
DECLARE_CHUNK(TextChunk, ('T','E','X','T'), 2);
|
|
|
|
TextChunk()
|
|
{
|
|
mText = StringTable->insert("");
|
|
}
|
|
|
|
~TextChunk()
|
|
{
|
|
// No deletion, it's a string table entry.
|
|
}
|
|
|
|
static void initPersistFields()
|
|
{
|
|
Parent::initPersistFields();
|
|
|
|
addField("textData", TypeString, Offset(mText, TextChunk));
|
|
}
|
|
|
|
void readChunk(BitStream &s, const U32 length, const U32 version, const U32 crc, const U32 fourCC)
|
|
{
|
|
switch(version)
|
|
{
|
|
// Deal with older stuff.
|
|
case 1:
|
|
mText = s.readSTString();
|
|
break;
|
|
|
|
// Shiny new format!
|
|
case 2:
|
|
// Resize the buffer..
|
|
U32 newSize;
|
|
s.read(&newSize);
|
|
|
|
char *tmpBuff = new char[newSize+1];
|
|
s.readLongString(newSize, (char*)tmpBuff);
|
|
|
|
mText = StringTable->insert(tmpBuff);
|
|
|
|
delete[] tmpBuff;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void writeChunk(BitStream &s)
|
|
{
|
|
s.write((U32)dStrlen(mText));
|
|
s.writeLongString(dStrlen(mText), mText);
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_CONOBJECT(TextChunk);
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
ConsoleFunction(saveChunkFile, bool, 3, 3, "(SimChunk chunk, Filename file)"
|
|
"Write a chunk hierarchy to a file.")
|
|
{
|
|
SimChunk *rootChunk = NULL;
|
|
const char *file = argv[2];
|
|
|
|
if(!Sim::findObject(argv[1], rootChunk))
|
|
{
|
|
Con::errorf("writeChunkFile - Unable to locate root chunk '%s'", argv[1]);
|
|
return false;
|
|
}
|
|
|
|
ChunkFile *cf = new ChunkFile();
|
|
|
|
cf->setRoot(rootChunk);
|
|
|
|
bool res = true;
|
|
|
|
if(!cf->save(file))
|
|
{
|
|
Con::errorf("writeChunkFile - Failed to save '%s' to '%s'", argv[1], file);
|
|
res = false;
|
|
}
|
|
|
|
delete cf;
|
|
|
|
return res;
|
|
}
|
|
|
|
ConsoleFunction(loadChunkFile, S32, 2, 2, "(Filename file)"
|
|
"Read a chunk hierarchy from a file.")
|
|
{
|
|
Resource<ChunkFile> ri = ResourceManager->load(argv[1]);
|
|
|
|
if(bool(ri) == false)
|
|
{
|
|
Con::errorf("loadChunkFile - failed to open '%s'", argv[1]);
|
|
return 0;
|
|
}
|
|
|
|
// Otherwise we're ok.
|
|
return ri->getRoot()->getId();
|
|
} |