aboutsummaryrefslogtreecommitdiff
path: root/libs/esptk/src/espfile.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/esptk/src/espfile.cpp
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/esptk/src/espfile.cpp')
-rw-r--r--libs/esptk/src/espfile.cpp188
1 files changed, 188 insertions, 0 deletions
diff --git a/libs/esptk/src/espfile.cpp b/libs/esptk/src/espfile.cpp
new file mode 100644
index 0000000..b97d222
--- /dev/null
+++ b/libs/esptk/src/espfile.cpp
@@ -0,0 +1,188 @@
+#include "espfile.h"
+#include "espexceptions.h"
+#include "subrecord.h"
+#include "tes3subrecord.h"
+#include <bitset>
+#include <cstring>
+#include <sstream>
+
+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: convert wstring to string (UTF-8)
+ std::string narrowName(fileName.begin(), fileName.end());
+ m_File.open(narrowName, std::fstream::in | std::fstream::binary);
+#endif
+ init();
+}
+
+class membuf : public std::basic_streambuf<char>
+{
+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<char*>(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<char*>(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<const char*>(m_TES3Header.author);
+ m_Description = reinterpret_cast<const char*>(m_TES3Header.description);
+ break;
+ case TES3SubRecord::TYPE_MAST:
+ if (subRec.data().size() > 0)
+ m_Masters.insert(reinterpret_cast<const char*>(&subRec.data()[0]));
+ break;
+ }
+ }
+ }
+ }
+ } else if (memcmp(type, "TES4", 4) == 0) {
+ m_File.seekg(0);
+
+ m_MainRecord = readRecord();
+
+ const std::vector<uint8_t>& data = m_MainRecord.data();
+ if (data.empty()) {
+ throw ESP::InvalidRecordException("record has no data");
+ }
+ membuf buf(reinterpret_cast<const char*>(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<const char*>(&rec.data()[0]));
+}
+
+void ESP::File::onCNAM(const SubRecord& rec)
+{
+ if (rec.data().size() > 0)
+ m_Author = reinterpret_cast<const char*>(&rec.data()[0]);
+}
+
+void ESP::File::onSNAM(const SubRecord& rec)
+{
+ if (rec.data().size() > 0)
+ m_Description = reinterpret_cast<const char*>(&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;
+}