aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_csharp/src/xml_info_reader.h
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/installer_fomod_csharp/src/xml_info_reader.h
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/installer_fomod_csharp/src/xml_info_reader.h')
-rw-r--r--libs/installer_fomod_csharp/src/xml_info_reader.h140
1 files changed, 140 insertions, 0 deletions
diff --git a/libs/installer_fomod_csharp/src/xml_info_reader.h b/libs/installer_fomod_csharp/src/xml_info_reader.h
new file mode 100644
index 0000000..91d6d9a
--- /dev/null
+++ b/libs/installer_fomod_csharp/src/xml_info_reader.h
@@ -0,0 +1,140 @@
+#ifndef XML_INFO_READER_H
+#define XML_INFO_READER_H
+
+#include <QFile>
+#include <QIODevice>
+#include <QString>
+#include <QStringEncoder>
+#include <QTextStream>
+#include <QXmlStreamReader>
+
+#include <uibase/log.h>
+#include <uibase/utility.h>
+
+// This is from installer_fomod, but should probably not be duplicated here.
+
+struct FomodInfoReader : QObject
+{
+
+ Q_OBJECT
+
+public:
+ struct XmlParseError : MOBase::Exception
+ {
+ XmlParseError(const QString& message) : MOBase::Exception(message) {}
+ };
+
+ static QByteArray skipXmlHeader(QIODevice& file)
+ {
+ static const unsigned char UTF16LE_BOM[] = {0xFF, 0xFE};
+ static const unsigned char UTF16BE_BOM[] = {0xFE, 0xFF};
+ static const unsigned char UTF8_BOM[] = {0xEF, 0xBB, 0xBF};
+ static const unsigned char UTF16LE[] = {0x3C, 0x00, 0x3F, 0x00};
+ static const unsigned char UTF16BE[] = {0x00, 0x3C, 0x00, 0x3F};
+ static const unsigned char UTF8[] = {0x3C, 0x3F, 0x78, 0x6D};
+
+ file.seek(0);
+ QByteArray rawBytes = file.read(4);
+ QTextStream stream(&file);
+ int bom = 0;
+ if (rawBytes.startsWith((const char*)UTF16LE_BOM)) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf16LE);
+ bom = 2;
+ } else if (rawBytes.startsWith((const char*)UTF16BE_BOM)) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf16BE);
+ bom = 2;
+ } else if (rawBytes.startsWith((const char*)UTF8_BOM)) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf8);
+ bom = 3;
+ } else if (rawBytes.startsWith(QByteArray((const char*)UTF16LE, 4))) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf16LE);
+ } else if (rawBytes.startsWith(QByteArray((const char*)UTF16BE, 4))) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf16BE);
+ } else if (rawBytes.startsWith(QByteArray((const char*)UTF8, 4))) {
+ stream.setEncoding(QStringEncoder::Encoding::Utf8);
+ } // otherwise maybe the textstream knows the encoding?
+
+ stream.seek(bom);
+ QString header = stream.readLine();
+ if (!header.startsWith("<?")) {
+ // it was all for nothing, there is no header here...
+ stream.seek(bom);
+ }
+ // this seems to be necessary due to buffering in QTextStream
+ file.seek(stream.pos());
+ return file.readAll();
+ }
+
+ template <class Fn>
+ static auto readXml(QFile& file, Fn&& fn)
+ {
+ // List of encodings to try:
+ static const std::vector<QStringConverter::Encoding> encodings{
+ QStringConverter::Utf16, QStringConverter::Utf8, QStringConverter::Latin1};
+
+ std::string errorMessage;
+ try {
+ QXmlStreamReader reader(&file);
+ return fn(reader);
+ } catch (const XmlParseError& e) {
+ MOBase::log::warn(
+ "The {} in this file is incorrectly encoded ({}). Applying heuristics...",
+ file.fileName(), e.what());
+ }
+
+ // nmm's xml parser is less strict than the one from qt and allows files with
+ // wrong encoding in the header. Being strict here would be bad user experience
+ // this works around bad headers.
+ QByteArray headerlessData = skipXmlHeader(file);
+
+ // try parsing the file with several encodings to support broken files
+ for (auto encoding : encodings) {
+ MOBase::log::debug("Trying encoding {} for {}... ",
+ QStringConverter::nameForEncoding(encoding), file.fileName());
+ try {
+ QStringEncoder encoder(encoding);
+ QXmlStreamReader reader(
+ encoder.encode(QString("<?xml version=\"1.0\" encoding=\"%1\" ?>")
+ .arg(encoder.name())) +
+ headerlessData);
+ MOBase::log::debug("Interpreting {} as {}.", file.fileName(), encoder.name());
+ return fn(reader);
+ } catch (const XmlParseError& e) {
+ MOBase::log::debug("Not {}: {}.", QStringConverter::nameForEncoding(encoding),
+ e.what());
+ }
+ }
+
+ throw XmlParseError(
+ tr("Failed to parse %1. See console for details.").arg(file.fileName()));
+ }
+
+ static std::tuple<QString, int, QString> parseInfo(QXmlStreamReader& reader)
+ {
+ std::tuple<QString, int, QString> info{"", -1, ""};
+ while (!reader.atEnd()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement: {
+ if (reader.name().toString() == "Name") {
+ std::get<0>(info) = reader.readElementText();
+ } else if (reader.name().toString() == "Author") {
+ } else if (reader.name().toString() == "Version") {
+ std::get<2>(info) = reader.readElementText();
+ } else if (reader.name().toString() == "Id") {
+ std::get<1>(info) = reader.readElementText().toInt();
+ } else if (reader.name().toString() == "Website") {
+ }
+ } break;
+ default: {
+ } break;
+ }
+ }
+ if (reader.hasError()) {
+ throw XmlParseError(
+ QString("%1 in line %2").arg(reader.errorString()).arg(reader.lineNumber()));
+ }
+ return info;
+ }
+};
+
+#endif