aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_omod_native/src/omodinstaller.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_omod_native/src/omodinstaller.cpp')
-rw-r--r--libs/installer_omod_native/src/omodinstaller.cpp472
1 files changed, 0 insertions, 472 deletions
diff --git a/libs/installer_omod_native/src/omodinstaller.cpp b/libs/installer_omod_native/src/omodinstaller.cpp
deleted file mode 100644
index 592111a..0000000
--- a/libs/installer_omod_native/src/omodinstaller.cpp
+++ /dev/null
@@ -1,472 +0,0 @@
-#include "omodinstaller.h"
-
-#include <uibase/guessedvalue.h>
-#include <uibase/iinstallationmanager.h>
-#include <uibase/log.h>
-
-#include <QDir>
-#include <QDirIterator>
-#include <QFile>
-#include <QFileInfo>
-#include <QProcess>
-#include <QTemporaryDir>
-
-#include <zlib.h>
-#include <lzma.h>
-
-#include <cstring>
-#include <stdexcept>
-
-// ============================================================================
-// BinaryReader
-// ============================================================================
-
-OmodInstaller::BinaryReader::BinaryReader(const QByteArray& data) : m_data(data) {}
-
-uint8_t OmodInstaller::BinaryReader::readByte()
-{
- if (m_pos >= m_data.size())
- throw std::runtime_error("BinaryReader: read past end");
- return static_cast<uint8_t>(m_data[m_pos++]);
-}
-
-int32_t OmodInstaller::BinaryReader::readInt32LE()
-{
- if (m_pos + 4 > m_data.size())
- throw std::runtime_error("BinaryReader: read past end");
- int32_t val;
- std::memcpy(&val, m_data.constData() + m_pos, 4);
- m_pos += 4;
- return val;
-}
-
-uint32_t OmodInstaller::BinaryReader::readUInt32LE()
-{
- if (m_pos + 4 > m_data.size())
- throw std::runtime_error("BinaryReader: read past end");
- uint32_t val;
- std::memcpy(&val, m_data.constData() + m_pos, 4);
- m_pos += 4;
- return val;
-}
-
-int64_t OmodInstaller::BinaryReader::readInt64LE()
-{
- if (m_pos + 8 > m_data.size())
- throw std::runtime_error("BinaryReader: read past end");
- int64_t val;
- std::memcpy(&val, m_data.constData() + m_pos, 8);
- m_pos += 8;
- return val;
-}
-
-void OmodInstaller::BinaryReader::skip(int bytes)
-{
- m_pos += bytes;
- if (m_pos > m_data.size())
- throw std::runtime_error("BinaryReader: skip past end");
-}
-
-int OmodInstaller::BinaryReader::read7BitEncodedInt()
-{
- int result = 0;
- int shift = 0;
- while (true) {
- uint8_t b = readByte();
- result |= (b & 0x7F) << shift;
- shift += 7;
- if ((b & 0x80) == 0)
- break;
- }
- return result;
-}
-
-QString OmodInstaller::BinaryReader::readNetString()
-{
- int length = read7BitEncodedInt();
- if (length == 0)
- return {};
- QByteArray raw = readBytes(length);
- return QString::fromUtf8(raw);
-}
-
-QByteArray OmodInstaller::BinaryReader::readBytes(int count)
-{
- if (m_pos + count > m_data.size())
- throw std::runtime_error("BinaryReader: read past end");
- QByteArray result = m_data.mid(m_pos, count);
- m_pos += count;
- return result;
-}
-
-bool OmodInstaller::BinaryReader::atEnd() const
-{
- return m_pos >= m_data.size();
-}
-
-// ============================================================================
-// OmodInstaller
-// ============================================================================
-
-OmodInstaller::OmodInstaller() = default;
-
-bool OmodInstaller::init(MOBase::IOrganizer* organizer)
-{
- m_organizer = organizer;
- return true;
-}
-
-QString OmodInstaller::name() const
-{
- return "OMOD Installer (Native)";
-}
-
-QString OmodInstaller::localizedName() const
-{
- return "OMOD Installer (Native)";
-}
-
-QString OmodInstaller::author() const
-{
- return "Fluorine Manager";
-}
-
-QString OmodInstaller::description() const
-{
- return "Installer for .omod archives (Oblivion Mod Manager format)";
-}
-
-MOBase::VersionInfo OmodInstaller::version() const
-{
- return MOBase::VersionInfo(1, 0, 0);
-}
-
-QList<MOBase::PluginSetting> OmodInstaller::settings() const
-{
- return {};
-}
-
-unsigned int OmodInstaller::priority() const
-{
- return 500;
-}
-
-bool OmodInstaller::isManualInstaller() const
-{
- return false;
-}
-
-bool OmodInstaller::isArchiveSupported(
- std::shared_ptr<const MOBase::IFileTree> tree) const
-{
- if (!tree)
- return false;
-
- // An OMOD zip always contains a "config" entry.
- for (auto it = tree->begin(); it != tree->end(); ++it) {
- auto entry = *it;
- if (entry && !entry->isDir() && entry->name().compare("config", Qt::CaseInsensitive) == 0)
- return true;
- }
- return false;
-}
-
-bool OmodInstaller::isArchiveSupported(const QString& archiveName) const
-{
- return archiveName.toLower().endsWith(".omod");
-}
-
-std::set<QString> OmodInstaller::supportedExtensions() const
-{
- return {"omod"};
-}
-
-OmodInstaller::EInstallResult OmodInstaller::install(
- MOBase::GuessedValue<QString>& modName, QString /*gameName*/,
- const QString& archiveName, const QString& /*version*/, int /*nexusID*/)
-{
- try {
- return doInstall(modName, archiveName);
- } catch (const std::exception& e) {
- MOBase::log::error("OMOD install failed: {}", e.what());
- return RESULT_FAILED;
- }
-}
-
-OmodInstaller::EInstallResult OmodInstaller::doInstall(
- MOBase::GuessedValue<QString>& modName, const QString& archiveName)
-{
- // Extract the .omod (zip) to a temp directory using unzip.
- QTemporaryDir omodTmp;
- if (!omodTmp.isValid()) {
- MOBase::log::error("OMOD: failed to create temp directory");
- return RESULT_FAILED;
- }
-
- {
- QProcess unzip;
- unzip.setWorkingDirectory(omodTmp.path());
- unzip.start("unzip", {"-o", archiveName, "-d", omodTmp.path()});
- unzip.waitForFinished(60000);
- if (unzip.exitCode() != 0) {
- MOBase::log::error("OMOD: unzip failed: {}",
- unzip.readAllStandardError().toStdString());
- return RESULT_FAILED;
- }
- }
-
- // Read and parse the config entry.
- QString configPath = QDir(omodTmp.path()).filePath("config");
- if (!QFile::exists(configPath)) {
- MOBase::log::warn("OMOD: no config entry found");
- return RESULT_NOTATTEMPTED;
- }
-
- QFile configFile(configPath);
- if (!configFile.open(QIODevice::ReadOnly)) {
- MOBase::log::error("OMOD: could not read config");
- return RESULT_FAILED;
- }
- QByteArray configData = configFile.readAll();
- configFile.close();
-
- OmodConfig config = parseConfig(configData);
- if (!config.modName.isEmpty()) {
- modName.update(config.modName, MOBase::GUESS_META);
- }
-
- // Create a separate temp directory for the extracted files.
- QTemporaryDir extractTmp;
- if (!extractTmp.isValid()) {
- MOBase::log::error("OMOD: failed to create extraction temp directory");
- return RESULT_FAILED;
- }
-
- // Extract data and plugins streams.
- extractStream(omodTmp.path(), "data", "data.crc", config.compressionType,
- extractTmp.path());
- extractStream(omodTmp.path(), "plugins", "plugins.crc", config.compressionType,
- extractTmp.path());
-
- // Copy readme files if present.
- QDir omodDir(omodTmp.path());
- QStringList entries = omodDir.entryList(QDir::Files);
- for (const QString& entry : entries) {
- QString lower = entry.toLower();
- if (lower == "readme" || lower.startsWith("readme.")) {
- QFile::copy(omodDir.filePath(entry),
- QDir(extractTmp.path()).filePath(entry));
- }
- }
-
- // Verify we have files to install.
- QDirIterator dirIt(extractTmp.path(), QDir::Files, QDirIterator::Subdirectories);
- if (!dirIt.hasNext()) {
- MOBase::log::warn("OMOD: no files extracted");
- return RESULT_FAILED;
- }
-
- // Repackage extracted files as a standard zip for MO2's installer.
- QString repackPath = QDir(extractTmp.path()).filePath("_repack.zip");
- {
- QProcess zip;
- zip.setWorkingDirectory(extractTmp.path());
- // Collect all files relative to extractTmp, excluding the repack zip itself.
- QStringList zipArgs;
- zipArgs << "-r" << repackPath << ".";
- zipArgs << "-x" << "./_repack.zip";
- zip.start("zip", zipArgs);
- zip.waitForFinished(120000);
- if (zip.exitCode() != 0) {
- MOBase::log::error("OMOD: zip repackaging failed: {}",
- zip.readAllStandardError().toStdString());
- return RESULT_FAILED;
- }
- }
-
- return manager()->installArchive(modName, repackPath);
-}
-
-// ============================================================================
-// OMOD binary format parsing
-// ============================================================================
-
-OmodInstaller::OmodConfig OmodInstaller::parseConfig(const QByteArray& data)
-{
- BinaryReader reader(data);
- OmodConfig config;
-
- config.fileVersion = reader.readByte();
- config.modName = reader.readNetString();
- config.major = reader.readInt32LE();
- config.minor = reader.readInt32LE();
- config.authorName = reader.readNetString();
- config.email = reader.readNetString();
- config.website = reader.readNetString();
- config.desc = reader.readNetString();
-
- // Windows FILETIME (8 bytes) - skip.
- reader.skip(8);
-
- // Compression type: 0 = deflate, 1 = lzma.
- config.compressionType = reader.readByte();
-
- return config;
-}
-
-std::vector<OmodInstaller::CrcEntry>
-OmodInstaller::parseCrcFile(const QByteArray& data)
-{
- BinaryReader reader(data);
- int count = reader.read7BitEncodedInt();
-
- std::vector<CrcEntry> entries;
- entries.reserve(count);
-
- for (int i = 0; i < count; ++i) {
- CrcEntry entry;
- entry.path = reader.readNetString();
- entry.crc = reader.readUInt32LE();
- entry.size = reader.readInt64LE();
- entries.push_back(std::move(entry));
- }
-
- return entries;
-}
-
-QByteArray OmodInstaller::decompressStream(const QByteArray& data,
- uint8_t compressionType)
-{
- if (compressionType == 0) {
- // Raw deflate (zlib with wbits = -15, no header).
- z_stream strm{};
- if (inflateInit2(&strm, -15) != Z_OK) {
- throw std::runtime_error("inflateInit2 failed");
- }
-
- strm.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(data.constData()));
- strm.avail_in = static_cast<uInt>(data.size());
-
- QByteArray result;
- char buffer[65536];
-
- int ret;
- do {
- strm.next_out = reinterpret_cast<Bytef*>(buffer);
- strm.avail_out = sizeof(buffer);
- ret = inflate(&strm, Z_NO_FLUSH);
- if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
- inflateEnd(&strm);
- throw std::runtime_error("zlib inflate failed");
- }
- int have = sizeof(buffer) - strm.avail_out;
- result.append(buffer, have);
- } while (ret != Z_STREAM_END);
-
- inflateEnd(&strm);
- return result;
-
- } else if (compressionType == 1) {
- // LZMA decompression.
- lzma_stream strm = LZMA_STREAM_INIT;
- lzma_ret ret = lzma_alone_decoder(&strm, UINT64_MAX);
- if (ret != LZMA_OK) {
- throw std::runtime_error("lzma_alone_decoder init failed");
- }
-
- strm.next_in = reinterpret_cast<const uint8_t*>(data.constData());
- strm.avail_in = data.size();
-
- QByteArray result;
- uint8_t buffer[65536];
-
- do {
- strm.next_out = buffer;
- strm.avail_out = sizeof(buffer);
- ret = lzma_code(&strm, LZMA_FINISH);
- if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
- lzma_end(&strm);
- throw std::runtime_error("lzma decompression failed");
- }
- size_t have = sizeof(buffer) - strm.avail_out;
- result.append(reinterpret_cast<const char*>(buffer), static_cast<int>(have));
- } while (ret != LZMA_STREAM_END);
-
- lzma_end(&strm);
- return result;
-
- } else {
- throw std::runtime_error(
- std::string("Unknown OMOD compression type: ") +
- std::to_string(compressionType));
- }
-}
-
-void OmodInstaller::extractStream(const QString& omodDir, const QString& streamName,
- const QString& crcName, uint8_t compressionType,
- const QString& outDir)
-{
- QDir dir(omodDir);
- QString streamPath = dir.filePath(streamName);
- QString crcPath = dir.filePath(crcName);
-
- if (!QFile::exists(streamPath))
- return;
-
- if (!QFile::exists(crcPath)) {
- MOBase::log::warn("OMOD: {} present but {} missing", streamName.toStdString(),
- crcName.toStdString());
- return;
- }
-
- // Read CRC file.
- QFile crcFile(crcPath);
- if (!crcFile.open(QIODevice::ReadOnly))
- return;
- QByteArray crcData = crcFile.readAll();
- crcFile.close();
-
- std::vector<CrcEntry> fileList = parseCrcFile(crcData);
- if (fileList.empty())
- return;
-
- // Read and decompress the stream.
- QFile streamFile(streamPath);
- if (!streamFile.open(QIODevice::ReadOnly))
- return;
- QByteArray rawData = streamFile.readAll();
- streamFile.close();
-
- QByteArray decompressed = decompressStream(rawData, compressionType);
-
- // Split the decompressed data into individual files.
- int offset = 0;
- for (const CrcEntry& entry : fileList) {
- if (offset + entry.size > decompressed.size()) {
- MOBase::log::warn(
- "OMOD: truncated stream for {} (need {} bytes at offset {}, have {})",
- entry.path.toStdString(), entry.size, offset, decompressed.size());
- break;
- }
-
- QByteArray fileData = decompressed.mid(offset, static_cast<int>(entry.size));
- offset += static_cast<int>(entry.size);
-
- // Normalise Windows path separators.
- QString normalPath = entry.path;
- normalPath.replace('\\', '/');
-
- QString destPath = QDir(outDir).filePath(normalPath);
-
- // Ensure parent directory exists.
- QDir().mkpath(QFileInfo(destPath).absolutePath());
-
- QFile outFile(destPath);
- if (outFile.open(QIODevice::WriteOnly)) {
- outFile.write(fileData);
- outFile.close();
- } else {
- MOBase::log::warn("OMOD: could not write {}", destPath.toStdString());
- }
- }
-}