#include #include #include "common.hh" #include "TreFile.hh" struct TreHeader { // little endian char magic[4]; // == "XTRE" uint32_t unknown; uint32_t table1Ofs; uint32_t table2Ofs; uint32_t table3Ofs; uint32_t dataOfs; } __attribute__((__packed__)); struct Table1Entry { // little endian uint32_t crc; uint32_t table3Ptr; } __attribute__((__packed__)); TreFile::TreFile(char const* base, size_t length) : base_(base), length_(length) { if (length_ < sizeof(TreHeader)) throw FormatException{"Size < header size"}; TreHeader const* header = reinterpret_cast(base); if (memcmp(header->magic, "XTRE", 4) != 0) throw FormatException{"Wrong magic, not a TRE?"}; if ((header->table1Ofs > length_) || (header->table2Ofs > length_) || (header->table3Ofs > length_) || (header->dataOfs > length_)) throw FormatException{"Table offset exceeds length"}; if ((header->table1Ofs > header->table2Ofs) || (header->table2Ofs > header->table3Ofs) || (header->table3Ofs > header->dataOfs)) throw FormatException{"Table offsets not in ascending order"}; const size_t numTable1Entries = (header->table2Ofs-header->table1Ofs)/sizeof(Table1Entry); table3Size_ = (header->dataOfs-header->table3Ofs)/sizeof(Table3Entry); #ifndef NDEBUG printf("Table 1: start %u, length %u (%lu entries)\n", header->table1Ofs, header->table2Ofs-header->table1Ofs, numTable1Entries); printf("Table 2: start %u, length %u\n", header->table2Ofs, header->table3Ofs-header->table2Ofs); printf("Table 3: start %u, length %u (%lu entries)\n", header->table3Ofs, header->dataOfs-header->table3Ofs, table3Size_); printf("Data: start %u, length %lu\n", header->dataOfs, length_ - header->dataOfs); #endif // Check Table 3 table3_ = reinterpret_cast(base+header->table3Ofs); for (size_t i = 0;i < table3Size_;++i) { if ((table3_[i].dataPtr > length_) || (table3_[i].dataPtr < header->dataOfs)) throw FormatException{"Data pointer out of range"}; if ((table3_[i].dataPtr + table3_[i].length) > length_) { fprintf(stderr, "Data length exceeds file length: %.8x %.8x\n", table3_[i].dataPtr, table3_[i].length); } } // Read Table 2 std::map table2Pos; size_t pos = header->table2Ofs; while((pos+5) < header->table3Ofs) { uint8_t const* nameLen = reinterpret_cast(base+pos); if (pos+*nameLen+5 > header->table3Ofs) throw FormatException{"Table 2 entry exceeds table "s + std::to_string(*nameLen)}; std::string nameStr(base+pos+1, *nameLen); uint32_t const* table3Ptr = reinterpret_cast(base+pos+*nameLen+1); if ((*table3Ptr < header->table3Ofs) || (*table3Ptr >= header->dataOfs)) { throw FormatException{"Table3 pointer out of range"}; } const size_t table3Index = (*table3Ptr - header->table3Ofs)/sizeof(Table3Entry); auto ins = table2_.emplace(nameStr, table3Index); if (!ins.second) { if (ins.first->second == table3Index) printf("Duplicate entry for name %s\n", nameStr.c_str()); else printf("Collision for name %s: prev %lu, new %lu\n", nameStr.c_str(), ins.first->second, table3Index); } // Store an position-indexed copy of Table2 to resolve Table1->Table2 ptr's table2Pos.emplace(pos, *table3Ptr); pos += 1+*nameLen+4; } // Read Table 1 Table1Entry const* table1 = reinterpret_cast(base+header->table1Ofs); for (size_t i = 0;i < numTable1Entries;++i) { if ((table1[i].crc == 0x00000000) && (table1[i].table3Ptr == 0xffffffff)) continue; // Unused entry if ((table1[i].table3Ptr < header->table2Ofs) || (table1[i].table3Ptr >= header->dataOfs)) { throw FormatException{"Table3 pointer out of range"}; } uint32_t table3Ptr = table1[i].table3Ptr; if (table3Ptr < header->table3Ofs) { auto it = table2Pos.find(table3Ptr); if (it == table2Pos.end()) { fprintf(stderr, "Table1->Table2 pointer: Table 2 entry not found: crc %.8x ptr %.8x\n", table1[i].crc, table3Ptr); continue; } printf("Table1->Table2 pointer resolved: Table 2 %.8x -> Table 3 %.8x\n", table3Ptr, it->second); table3Ptr = it->second; } const size_t table3Index = (table3Ptr - header->table3Ofs)/sizeof(Table3Entry); auto ins = table1_.emplace(static_cast(table1[i].crc), table3Index); if (!ins.second) { if (ins.first->second == table3Index) printf("Duplicate entry for CRC %.8x\n", table1[i].crc); else printf("Collision for CRC %.8x: prev %lu, new %lu\n", table1[i].crc, ins.first->second, table3Index); } } } TreFile::~TreFile() { } std::vector TreFile::getNames() const { std::vector ret; for (auto ent : table2_) { ret.push_back(ent.first); } return ret; } std::vector TreFile::getCRCs() const { std::vector ret; for (auto ent : table1_) { ret.push_back(ent.first); } return ret; } TreFile::Object TreFile::openName(std::string const& name) const { return openIdx_(findName_(name)); } TreFile::Object TreFile::openCRC(uint32_t crc) const { return openIdx_(findCRC_(crc)); } TreFile::Stat TreFile::statName(std::string const& name) const { return statIdx_(findName_(name)); } TreFile::Stat TreFile::statCRC(uint32_t crc) const { return statIdx_(findCRC_(crc)); } void TreFile::dumpName(std::string path, std::string const& name) const { auto idx = findName_(name); if (path.back() != '/') path.push_back('/'); path.append(name); dumpIdx_(path, idx); } void TreFile::dumpCRC(std::string path, uint32_t crc) const { auto idx = findCRC_(crc); char crcStr[9]; snprintf(crcStr, 9, "%.8X", crc); if (path.back() != '/') path.push_back('/'); path.append(crcStr); dumpIdx_(path, idx); } void TreFile::dumpAll(std::string path) const { if (path.back() != '/') path.push_back('/'); for (auto ent : table1_) { char crcStr[9]; snprintf(crcStr, 9, "%.8X", ent.first); std::string name = path; name.append(crcStr); dumpIdx_(name, ent.second); } for (auto ent : table2_) { std::string name = path; name.append(ent.first); dumpIdx_(name, ent.second); } } void TreFile::printStructure() { printf("Table 1: %lu entries\n", table1_.size()); printf("Table 2: %lu entries\n", table2_.size()); printf("Table 3: %lu entries\n", table3Size_); printf("Files by CRC:\n"); for(auto const& ent : table1_) { printf("\t%.8x -> (ofs %.8x, len %.8x, flags %.2hhx)\n", ent.first, table3_[ent.second].dataPtr, table3_[ent.second].length, table3_[ent.second].flags); } printf("Files by Name:\n"); for(auto const& ent : table2_) { printf("\t%s -> (ofs %.8x, len %.8x, flags %.2hhx)\n", ent.first.c_str(), table3_[ent.second].dataPtr, table3_[ent.second].length, table3_[ent.second].flags); } } size_t TreFile::findName_(std::string const& name) const { auto it = table2_.find(name); if (it == table2_.end()) throw Exception{name + " not found"}; return it->second; } size_t TreFile::findCRC_(uint32_t crc) const { auto it = table1_.find(crc); if (it == table1_.end()) { char crcStr[9]; snprintf(crcStr, 9, "%.8X", crc); throw Exception{"CRC "s + crcStr + " not found"s}; } return it->second; } void TreFile::dumpIdx_(std::string const& name, size_t table3Idx) const { if(table3_[table3Idx].flags&0x80) throw Exception{"Compressed TRE objects NYI"}; FILEUPtr outFile{fopen(name.c_str(), "wb")}; if (!outFile) throw POSIXException{errno, "Could not open " + name}; if (fwrite(base_+table3_[table3Idx].dataPtr, table3_[table3Idx].length, 1, outFile.get()) != 1) throw POSIXException{errno, "Could not write"}; } TreFile::Object TreFile::openIdx_(size_t table3Idx) const { if(table3_[table3Idx].flags&0x80) throw Exception{"Compressed TRE objects NYI"}; if ((table3_[table3Idx].dataPtr + table3_[table3Idx].length) > length_) throw FormatException{"length exceeds file size"}; return Object(base_+table3_[table3Idx].dataPtr, table3_[table3Idx].length); } TreFile::Stat TreFile::statIdx_(size_t table3Idx) const { Stat ret; ret.size = table3_[table3Idx].length; ret.flags = table3_[table3Idx].flags; return ret; }