Files
wc3re/IffFile.cc

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);
}