197 lines
5.0 KiB
C++
197 lines
5.0 KiB
C++
#include <cstring>
|
|
|
|
#include "IffFile.hh"
|
|
#include "util.hh"
|
|
#include "common.hh"
|
|
|
|
struct ChunkHeader {
|
|
unsigned char typeID[4];
|
|
uint32_t length;
|
|
};
|
|
|
|
IffFile::IffFile(Resource::Handle res)
|
|
: res_(std::move(res)), footprint_(sizeof(IffFile))
|
|
{
|
|
size_t pos = 0;
|
|
while (pos < res_->size()) {
|
|
roots_.push_back(parseObject(*this, res_->data()+pos, res_->size()-pos, footprint_));
|
|
pos += roots_.back()->size() + 8;
|
|
}
|
|
footprint_ += sizeof(std::unique_ptr<Object>)*roots_.capacity();
|
|
}
|
|
|
|
IffFile::~IffFile()
|
|
{
|
|
}
|
|
|
|
static void _printStructure(IffFile::Object const& obj, unsigned level)
|
|
{
|
|
for (unsigned i = 0;i < level;++i)
|
|
putchar('\t');
|
|
printf("%s Length %lu (0x%.lx)", obj.getType().c_str(), obj.size(), obj.size());
|
|
|
|
if (obj.isForm()) {
|
|
auto& form = dynamic_cast<IffFile::Form const&>(obj);
|
|
printf(", Subtype %s\n", form.getSubtype().c_str());
|
|
for(auto it = form.childrenBegin();it != form.childrenEnd();++it)
|
|
_printStructure(*it, level+1);
|
|
} else {
|
|
try {
|
|
printf(" = \"%s\"\n", static_cast<std::string>(obj).c_str());
|
|
} catch(FormatException &ex) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
void IffFile::printStructure(unsigned level) const
|
|
{
|
|
for (auto& root : *this)
|
|
_printStructure(root, level);
|
|
}
|
|
|
|
std::unique_ptr<IffFile::Object>
|
|
IffFile::parseObject(IffFile& parent, uint8_t const* base, size_t length,
|
|
size_t& footprint)
|
|
{
|
|
if (length < 8)
|
|
throw FormatException{"length < header size"};
|
|
|
|
ChunkHeader header;
|
|
memcpy(&header.typeID, base, 4);
|
|
header.length = readU32BE(base+4);
|
|
|
|
for (unsigned i = 0;i < 4;++i)
|
|
if (!isprint(header.typeID[i]))
|
|
throw FormatException{"Not an IFF chunk"};
|
|
|
|
if (header.length > length-8)
|
|
throw FormatException{"length < size in header"};
|
|
|
|
if(memcmp(header.typeID, "FORM", 4) == 0)
|
|
return std::unique_ptr<Object>(new Form(parent, "FORM", base+8,
|
|
static_cast<size_t>(header.length),
|
|
footprint));
|
|
else
|
|
return std::unique_ptr<Object>(new Object(parent, std::string(reinterpret_cast<char const*>(header.typeID), 4),
|
|
base+8, static_cast<size_t>(header.length),
|
|
footprint));
|
|
}
|
|
|
|
namespace {
|
|
IffFile::Object& _getObject(std::string const& spec,
|
|
std::vector<std::unique_ptr<IffFile::Object> > const& container)
|
|
{
|
|
auto sepPos = spec.find(':');
|
|
auto name = spec.substr(0, sepPos);
|
|
unsigned matchCnt = 0;
|
|
if (sepPos != std::string::npos)
|
|
matchCnt = std::stoul(spec.substr(sepPos+1));
|
|
|
|
for (auto& obj : container) {
|
|
if (obj->isForm()) {
|
|
auto form = dynamic_cast<IffFile::Form const*>(obj.get());
|
|
if (form->getSubtype() == name) {
|
|
if (!matchCnt) {
|
|
return *obj;
|
|
} else {
|
|
--matchCnt;
|
|
}
|
|
}
|
|
} else if (obj->getType() == name) {
|
|
if (!matchCnt) {
|
|
return *obj;
|
|
} else {
|
|
--matchCnt;
|
|
}
|
|
}
|
|
}
|
|
|
|
throw PathException{"Not found: " + spec};
|
|
}
|
|
|
|
}
|
|
|
|
IffFile::Object& IffFile::getObject(std::string const& spec) const
|
|
{
|
|
return _getObject(spec, roots_);
|
|
}
|
|
|
|
IffFile::Object& IffFile::Form::getObject(std::string const& spec) const
|
|
{
|
|
return _getObject(spec, children_);
|
|
}
|
|
|
|
IffFile::Object::Object(IffFile& parent, std::string type, uint8_t const* base,
|
|
size_t length, size_t& footprint)
|
|
: parent_(parent), base_(base), length_(length), type_(std::move(type))
|
|
{
|
|
footprint += sizeof(Object)+type_.capacity();
|
|
}
|
|
|
|
IffFile::Object::operator std::string() const
|
|
{
|
|
// Check if BLOB is string
|
|
enum class State { STARTASCII, NULLS, NOT_STRING };
|
|
State state = State::STARTASCII;
|
|
for (char const& c : *this) {
|
|
switch(state) {
|
|
case State::STARTASCII:
|
|
if (isprint(static_cast<unsigned char>(c)))
|
|
continue;
|
|
if (c == '\0')
|
|
state = State::NULLS;
|
|
else
|
|
state = State::NOT_STRING;
|
|
break;
|
|
case State::NULLS:
|
|
if (c != '\0')
|
|
state = State::NOT_STRING;
|
|
break;
|
|
case State::NOT_STRING:
|
|
break;
|
|
}
|
|
|
|
if (state == State::NOT_STRING)
|
|
break;
|
|
}
|
|
|
|
if (state == State::NULLS)
|
|
return std::string(reinterpret_cast<char const*>(base_));
|
|
else if (state == State::STARTASCII)
|
|
return std::string(reinterpret_cast<char const*>(base_), length_);
|
|
else
|
|
throw FormatException{"BLOB not string"};
|
|
}
|
|
|
|
IffFile::Form::Form(IffFile& parent, std::string type, uint8_t const* base,
|
|
size_t length, size_t& footprint)
|
|
: Object(parent, std::move(type), base, length, footprint)
|
|
{
|
|
if (length < 4)
|
|
throw FormatException{"length < subtype id length"};
|
|
|
|
unsigned strLen = 4;
|
|
for (unsigned i = 0;i < 4;++i) {
|
|
if ((i > 0) && (base_[i] == '\0')) {
|
|
strLen = i;
|
|
break;
|
|
}
|
|
if (!isprint(base_[i]))
|
|
throw FormatException{"Subtype not printable"};
|
|
}
|
|
subtype_ = std::string(reinterpret_cast<char const*>(base_), strLen);
|
|
|
|
size_t pos = 4;
|
|
while (pos+8 < length) {
|
|
children_.push_back(parseObject(parent_, base_+pos, length-pos, footprint));
|
|
pos += 8 + children_.back()->size();
|
|
|
|
if (pos%2 != 0)
|
|
++pos;
|
|
}
|
|
|
|
footprint += sizeof(Form) + sizeof(std::unique_ptr<Object>)*children_.capacity() + subtype_.size() - sizeof(Object);
|
|
}
|
|
|