Added an MmapFile helper and changed iffexplore and treexplore to use it. WIP: MveDecoder Misc. changes/fixes
312 lines
8.3 KiB
C++
312 lines
8.3 KiB
C++
#include <cstdio>
|
|
#include <cstdint>
|
|
|
|
#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<TreHeader const*>(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<Table3Entry const*>(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<size_t, uint32_t> table2Pos;
|
|
|
|
size_t pos = header->table2Ofs;
|
|
while((pos+5) < header->table3Ofs) {
|
|
uint8_t const* nameLen = reinterpret_cast<uint8_t const*>(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<uint32_t const*>(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<Table1Entry const*>(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<uint32_t>(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<std::string> TreFile::getNames() const
|
|
{
|
|
std::vector<std::string> ret;
|
|
for (auto ent : table2_) {
|
|
ret.push_back(ent.first);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::vector<uint32_t> TreFile::getCRCs() const
|
|
{
|
|
std::vector<uint32_t> 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;
|
|
}
|