#include "espfile.h" #include "espexceptions.h" #include "subrecord.h" #include "tes3subrecord.h" #include #include #include ESP::File::File(const std::string& fileName) { m_File.open(fileName, std::fstream::in | std::fstream::binary); init(); } ESP::File::File(const std::wstring& fileName) { #ifdef _WIN32 m_File.open(fileName, std::fstream::in | std::fstream::binary); #else // Linux: properly encode wstring → UTF-8. The old naive // `string(w.begin(), w.end())` copy truncated any codepoint > 0x7F // (e.g. ö U+00F6, – U+2013) which broke paths like "Mörskom Estate" or // "Official Master Files – Cleaned". std::string narrowName; narrowName.reserve(fileName.size()); for (wchar_t wc : fileName) { const auto cp = static_cast(wc); if (cp < 0x80) { narrowName.push_back(static_cast(cp)); } else if (cp < 0x800) { narrowName.push_back(static_cast(0xC0 | (cp >> 6))); narrowName.push_back(static_cast(0x80 | (cp & 0x3F))); } else if (cp < 0x10000) { narrowName.push_back(static_cast(0xE0 | (cp >> 12))); narrowName.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); narrowName.push_back(static_cast(0x80 | (cp & 0x3F))); } else { narrowName.push_back(static_cast(0xF0 | (cp >> 18))); narrowName.push_back(static_cast(0x80 | ((cp >> 12) & 0x3F))); narrowName.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F))); narrowName.push_back(static_cast(0x80 | (cp & 0x3F))); } } m_File.open(narrowName, std::fstream::in | std::fstream::binary); #endif init(); } class membuf : public std::basic_streambuf { public: membuf(const char* start, size_t size) { // baad me! this is intended for an istream only so we're not modifying char* startMod = const_cast(start); setg(startMod, startMod, startMod + size); } }; void ESP::File::init() { if (!m_File.is_open()) { throw ESP::InvalidFileException("file not found"); } m_File.exceptions(std::ios_base::badbit); uint8_t type[4]; if (!m_File.read(reinterpret_cast(type), 4)) { throw ESP::InvalidFileException("file incomplete"); } if (memcmp(type, "TES3", 4) == 0) { ESP::TES3Record rec; rec.readFrom(m_File); while (!m_File.eof() && !m_File.fail()) { ESP::TES3SubRecord subRec; bool success = subRec.readFrom(m_File); int headerSize = sizeof(m_TES3Header); if (success) { if (subRec.type() != TES3SubRecord::TYPE_UNKNOWN) { switch (subRec.type()) { case TES3SubRecord::TYPE_HEDR: if (subRec.data().size() != sizeof(m_TES3Header)) { printf("invalid header size\n"); m_Header.version = 0.0f; m_Header.numRecords = 1; // prevent this esp appear like a dummy } else { memcpy(&m_TES3Header, &subRec.data()[0], sizeof(m_TES3Header)); } m_Header.version = m_TES3Header.version; m_Header.numRecords = m_TES3Header.numRecords; m_Author = reinterpret_cast(m_TES3Header.author); m_Description = reinterpret_cast(m_TES3Header.description); break; case TES3SubRecord::TYPE_MAST: if (subRec.data().size() > 0) m_Masters.insert(reinterpret_cast(&subRec.data()[0])); break; } } } } } else if (memcmp(type, "TES4", 4) == 0) { m_File.seekg(0); m_MainRecord = readRecord(); const std::vector& data = m_MainRecord.data(); if (data.empty()) { throw ESP::InvalidRecordException("record has no data"); } membuf buf(reinterpret_cast(data.data()), data.size()); std::istream stream(&buf); while (!stream.eof() && !stream.fail()) { SubRecord rec; bool success = rec.readFrom(stream); if (success) { if (rec.type() != SubRecord::TYPE_UNKNOWN) { switch (rec.type()) { case SubRecord::TYPE_HEDR: onHEDR(rec); break; case SubRecord::TYPE_MAST: onMAST(rec); break; case SubRecord::TYPE_CNAM: onCNAM(rec); break; case SubRecord::TYPE_SNAM: onSNAM(rec); break; } } } } } else { throw ESP::InvalidFileException("invalid file type"); } } void ESP::File::onHEDR(const SubRecord& rec) { if (rec.data().size() != sizeof(m_Header)) { printf("invalid header size\n"); m_Header.version = 0.0f; m_Header.numRecords = 1; // prevent this esp appear like a dummy } else { memcpy(&m_Header, &rec.data()[0], sizeof(m_Header)); } } void ESP::File::onMAST(const SubRecord& rec) { if (rec.data().size() > 0) m_Masters.insert(reinterpret_cast(&rec.data()[0])); } void ESP::File::onCNAM(const SubRecord& rec) { if (rec.data().size() > 0) m_Author = reinterpret_cast(&rec.data()[0]); } void ESP::File::onSNAM(const SubRecord& rec) { if (rec.data().size() > 0) m_Description = reinterpret_cast(&rec.data()[0]); } ESP::Record ESP::File::readRecord() { ESP::Record rec; rec.readFrom(m_File); return rec; } bool ESP::File::isMaster() const { return m_MainRecord.flagSet(Record::FLAG_MASTER); } bool ESP::File::isLight(bool overlaySupport) const { if (overlaySupport) { return m_MainRecord.flagSet(Record::FLAG_LIGHT_ALTERNATE); } else { return m_MainRecord.flagSet(Record::FLAG_LIGHT); } } bool ESP::File::isMedium() const { return m_MainRecord.flagSet(Record::FLAG_MEDIUM); } bool ESP::File::isOverlay() const { return m_MainRecord.flagSet(Record::FLAG_OVERLAY); } bool ESP::File::isBlueprint() const { return m_MainRecord.flagSet(Record::FLAG_BLUEPRINT); } bool ESP::File::isDummy() const { return m_Header.numRecords == 0; }