diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index c1dbf8e9bf..34e771e3d7 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -38,8 +38,7 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() QString path(QDir::toNativeSeparators(info.absolutePath())); QList items = installationsList->findItems(path, Qt::MatchExactly); - if (items.isEmpty()) - { + if (items.isEmpty()) { // Path is not yet in the list, add it mWizard->addInstallation(path); diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 2c099acecc..f34699742b 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -1,8 +1,10 @@ #include "inisettings.hpp" +#include #include #include +#include #include #include #include @@ -15,25 +17,40 @@ Wizard::IniSettings::~IniSettings() { } +QStringList Wizard::IniSettings::findKeys(const QString &text) +{ + QStringList result; + + foreach (const QString &key, mSettings.keys()) { + + if (key.startsWith(text)) + result << key; + + } + + return result; +} + bool Wizard::IniSettings::readFile(QTextStream &stream) { + qDebug() << "readFile called!"; // Look for a square bracket, "'\\[" // that has one or more "not nothing" in it, "([^]]+)" // and is closed with a square bracket, "\\]" - QRegExp sectionRe("^\\[([^]]+)\\]"); + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); // Find any character(s) that is/are not equal sign(s), "[^=]+" // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" // and one or more periods, "(.+)" - QRegExp keyRe("^([^=]+)\\s*=\\s*(.+)$"); + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); QString currentSection; while (!stream.atEnd()) { - QString line(stream.readLine()); + const QString line(stream.readLine()); - if (line.isEmpty() || line.startsWith(";")) + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) continue; if (sectionRe.exactMatch(line)) @@ -49,6 +66,7 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) if (!currentSection.isEmpty()) key = currentSection + QLatin1Char('/') + key; + qDebug() << "adding: " << key << value; mSettings[key] = QVariant(value); } } @@ -56,13 +74,161 @@ bool Wizard::IniSettings::readFile(QTextStream &stream) return true; } -bool Wizard::IniSettings::writeFile(QTextStream &stream) +bool Wizard::IniSettings::writeFile(const QString &path, QTextStream &stream) { - qDebug() << "test! " << stream.readAll(); + // Look for a square bracket, "'\\[" + // that has one or more "not nothing" in it, "([^]]+)" + // and is closed with a square bracket, "\\]" + QRegExp sectionRe(QLatin1String("^\\[([^]]+)\\]")); + + // Find any character(s) that is/are not equal sign(s), "[^=]+" + // followed by an optional whitespace, an equal sign, and another optional whitespace, "\\s*=\\s*" + // and one or more periods, "(.+)" + QRegExp keyRe(QLatin1String("^([^=]+)\\s*=\\s*(.+)$")); + + const QStringList keys(mSettings.keys()); + + QString currentSection; + QString buffer; + + qDebug() << "Keys! " << keys; while (!stream.atEnd()) { - qDebug() << "test! " << stream.readLine(); + + const QString line(stream.readLine()); + + if (line.isEmpty() || line.startsWith(QLatin1Char(';'))) { + buffer.append(line + QLatin1String("\n")); + continue; + } + + if (sectionRe.exactMatch(line)) { + buffer.append(line + QLatin1String("\n")); + currentSection = sectionRe.cap(1); + } else if (keyRe.indexIn(line) != -1) { + QString key(keyRe.cap(1).trimmed()); + QString lookupKey(key); + + // Append the section, but only if there is one + if (!currentSection.isEmpty()) + lookupKey = currentSection + QLatin1Char('/') + key; + + buffer.append(key + QLatin1Char('=') + mSettings[lookupKey].toString() + QLatin1String("\n")); + mSettings.remove(lookupKey); + } + } + + // Add the new settings to the buffer + QHashIterator i(mSettings); + while (i.hasNext()) { + i.next(); + + QStringList fullKey(i.key().split(QLatin1Char('/'))); + QString section(fullKey.at(0)); + section.prepend(QLatin1Char('[')); + section.append(QLatin1Char(']')); + QString key(fullKey.at(1)); + + int index = buffer.lastIndexOf(section); + if (index != -1) { + // Append the new keys to the bottom of the section + index = buffer.indexOf(QLatin1Char('['), index + 1); + + if (index == -1 ) + { + // Beginning of next section not found, we are at the last section + if (buffer.lastIndexOf(QLatin1String("\n")) > (buffer.lastIndexOf(section) + section.length())) { + // There is a newline after the section + index = buffer.lastIndexOf(QLatin1String("\n")) - 1; + buffer.insert(index - 2, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } else { + // No newline found, or the last newline is before the last section + // Append the key to the bottom of the file + buffer.append(QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + continue; + } + } + + // Add the key at the index + buffer.insert(index - 1, QString("\n%1=%2").arg(key, i.value().toString())); + mSettings.remove(i.key()); + } else { + // Add the section to the end of the file, because it's not found + buffer.append(QString("\n%1\n").arg(section)); + i.previous(); + } } + // Now we reopen the file, this time we write + QFile file(path); + + if (file.open(QIODevice::ReadWrite | QIODevice::Truncate | QIODevice::Text)) { + QTextStream in(&file); + in.setCodec(stream.codec()); + + // Write the updated buffer to an empty file + in << buffer; + file.flush(); + file.close(); + } else { + return false; + } + + return true; +} + +bool Wizard::IniSettings::parseInx(const QString &path) +{ + QFile file(path); + + if (file.open(QIODevice::ReadOnly)) + { + + const QByteArray data(file.readAll()); + const QByteArray pattern("\x21\x00\x1A\x01\x04\x00\x04\x97\xFF\x06", 10); + + int i = 0; + while ((i = data.indexOf(pattern, i)) != -1) { + + int next = data.indexOf(pattern, i + 1); + if (next == -1) + break; + + QByteArray array(data.mid(i, (next - i))); + + // Skip some invalid entries + if (array.contains("\x04\x96\xFF")) { + ++i; + continue; + } + + // Remove the pattern from the beginning + array.remove(0, 12); + + int index = array.indexOf("\x06"); + const QString section(array.left(index)); + + // Figure how many characters to read for the key + int lenght = array.indexOf("\x06", section.length() + 3) - (section.length() + 3); + const QString key(array.mid(section.length() + 3, lenght)); + + QString value(array.mid(section.length() + key.length() + 6)); + //qDebug() << section << key << value; + + // Add the value + setValue(section + QLatin1Char('/') + key, QVariant(value)); + + ++i; + } + + file.close(); + } else { + qDebug() << "Failed to open INX file: " << path; + return false; + } + return true; } diff --git a/apps/wizard/inisettings.hpp b/apps/wizard/inisettings.hpp index 66fe96ec1f..d425a9b1b3 100644 --- a/apps/wizard/inisettings.hpp +++ b/apps/wizard/inisettings.hpp @@ -22,6 +22,11 @@ namespace Wizard return mSettings.value(key, defaultValue); } + inline QList values() const + { + return mSettings.values(); + } + inline void setValue(const QString &key, const QVariant &value) { mSettings.insert(key, value); @@ -32,11 +37,17 @@ namespace Wizard mSettings.remove(key); } + QStringList findKeys(const QString &text); + bool readFile(QTextStream &stream); - bool writeFile(QTextStream &stream); + bool writeFile(const QString &path, QTextStream &stream); + + bool parseInx(const QString &path); private: + int getLastNewline(const QString &buffer, int from); + SettingsMap mSettings; }; diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index efa6880be1..05d1b4af84 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -110,6 +110,7 @@ void Wizard::InstallationPage::startInstallation() // Set the location of the Morrowind.ini to update mUnshield->setIniPath(mWizard->mInstallations[path]->iniPath); + mUnshield->setupSettings(); } // Set the installation target path diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index 306e7221c7..daf2c08467 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -21,11 +21,6 @@ void Wizard::InstallationTargetPage::initializePage() QString path(QFile::decodeName(mCfgMgr.getUserDataPath().string().c_str())); path.append(QDir::separator() + QLatin1String("data")); - if (!QFile::exists(path)) { - QDir dir; - dir.mkpath(path); - } - QDir dir(path); targetLineEdit->setText(QDir::toNativeSeparators(dir.absolutePath())); } @@ -36,6 +31,13 @@ bool Wizard::InstallationTargetPage::validatePage() qDebug() << "Validating path: " << path; + // TODO: Check writeability + if (!QFile::exists(path)) { + QDir dir; + dir.mkpath(path); + return true; + } + if (mWizard->findFiles(QLatin1String("Morrowind"), path)) { QMessageBox msgBox; msgBox.setWindowTitle(tr("Destination not empty")); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 0913cf9e4d..d9b2c3ba8f 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -127,6 +127,7 @@ void Wizard::MainWizard::setupPages() setPage(Page_Conclusion, new ConclusionPage(this)); setStartId(Page_Intro); } + void Wizard::MainWizard::accept() { writeSettings(); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 02e50a7e33..47d3c1be7c 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -43,7 +43,6 @@ Wizard::UnshieldWorker::UnshieldWorker(QObject *parent) : Wizard::UnshieldWorker::~UnshieldWorker() { - } void Wizard::UnshieldWorker::setInstallComponent(Wizard::Component component, bool install) @@ -198,6 +197,29 @@ void Wizard::UnshieldWorker::setupSettings() mIniSettings.readFile(stream); } +void Wizard::UnshieldWorker::writeSettings() +{ + if (getIniPath().isEmpty()) + return; + + QFile file(getIniPath()); + + if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) { + qDebug() << "Error opening .ini file!"; + emit error(tr("Failed to open Morrowind configuration file!"), + tr("Opening %1 failed: %2.").arg(getIniPath(), file.errorString())); + return; + } + + QTextStream stream(&file); + stream.setCodec(mIniCodec); + + if (!mIniSettings.writeFile(getIniPath(), stream)) { + emit error(tr("Failed to write Morrowind configuration file!"), + tr("Writing to %1 failed: %2.").arg(getIniPath(), file.errorString())); + } +} + bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) { bool result = true; @@ -383,6 +405,32 @@ void Wizard::UnshieldWorker::extract() setupAddon(Wizard::Component_Bloodmoon); } + // Update Morrowind configuration + if (getInstallComponent(Wizard::Component_Tribunal)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + } + + if (getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Bloodmoon.esm"))); + } + + if (getInstallComponent(Wizard::Component_Tribunal) && + getInstallComponent(Wizard::Component_Bloodmoon)) + { + mIniSettings.setValue(QLatin1String("Archives/Archive0"), QVariant(QString("Tribunal.bsa"))); + mIniSettings.setValue(QLatin1String("Archives/Archive1"), QVariant(QString("Bloodmoon.bsa"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File1"), QVariant(QString("Tribunal.esm"))); + mIniSettings.setValue(QLatin1String("Game Files/Game File2"), QVariant(QString("Bloodmoon.esm"))); + } + + + // Write the settings to the Morrowind config file + writeSettings(); + // Remove the temporary directory removeDirectory(getPath() + QDir::separator() + QLatin1String("extract-temp")); @@ -554,6 +602,10 @@ bool Wizard::UnshieldWorker::installComponent(Component component) emit error(tr("Could not find Morrowind configuration file!"), tr("Failed to find %0.").arg(iniPath)); return false; } + + // Setup Morrowind configuration + setIniPath(getPath() + QDir::separator() + QLatin1String("Morrowind.ini")); + setupSettings(); } if (component == Wizard::Component_Tribunal) @@ -564,7 +616,6 @@ bool Wizard::UnshieldWorker::installComponent(Component component) emit textChanged(tr("Extracting: Sound directory")); copyDirectory(sounds.absoluteFilePath(), getPath() + QDir::separator() + QLatin1String("Sound")); } - } if (component == Wizard::Component_Bloodmoon) @@ -577,6 +628,15 @@ bool Wizard::UnshieldWorker::installComponent(Component component) copyFile(patch.absoluteFilePath(), original.absoluteFilePath()); } + // Load Morrowind configuration settings from the setup script + QFileInfo inx(disk.absoluteFilePath(QLatin1String("setup.inx"))); + + if (inx.exists()) { + emit textChanged(tr("Updating Morrowind configuration file")); + mIniSettings.parseInx(inx.absoluteFilePath()); + } else { + qDebug() << "setup.inx not found!"; + } } emit textChanged(tr("%0 installation finished!").arg(name)); @@ -649,7 +709,7 @@ bool Wizard::UnshieldWorker::findFile(const QString &cabFile, const QString &fil { QString current(QString::fromLatin1(unshield_file_name(unshield, j))); - qDebug() << "File is: " << unshield_file_name(unshield, j); + qDebug() << "File is: " << current; if (current == fileName) return true; // File is found! } diff --git a/apps/wizard/unshield/unshieldworker.hpp b/apps/wizard/unshield/unshieldworker.hpp index d110851f4f..8aa54c9c06 100644 --- a/apps/wizard/unshield/unshieldworker.hpp +++ b/apps/wizard/unshield/unshieldworker.hpp @@ -41,8 +41,12 @@ namespace Wizard void setIniCodec(QTextCodec *codec); + void setupSettings(); + private: + void writeSettings(); + bool getInstallComponent(Component component); QString getComponentPath(Component component); @@ -58,8 +62,6 @@ namespace Wizard bool moveFile(const QString &source, const QString &destination); bool moveDirectory(const QString &source, const QString &destination); - void setupSettings(); - bool extractCab(const QString &cabFile, const QString &outputDir); bool extractFile(Unshield *unshield, const QString &outputDir, const QString &prefix, int index, int counter); bool findFile(const QString &cabFile, const QString &fileName);