#include #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)*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(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(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::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(new Form(parent, "FORM", base+8, static_cast(header.length), footprint)); else return std::unique_ptr(new Object(parent, std::string(reinterpret_cast(header.typeID), 4), base+8, static_cast(header.length), footprint)); } namespace { IffFile::Object& _getObject(std::string const& spec, std::vector > 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(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(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(base_)); else if (state == State::STARTASCII) return std::string(reinterpret_cast(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(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)*children_.capacity() + subtype_.size() - sizeof(Object); }