summaryrefslogtreecommitdiffstats
path: root/heimdall-frontend/source
diff options
context:
space:
mode:
Diffstat (limited to 'heimdall-frontend/source')
-rw-r--r--heimdall-frontend/source/Alerts.cpp45
-rw-r--r--heimdall-frontend/source/Alerts.h38
-rw-r--r--heimdall-frontend/source/FirmwareInfo.cpp783
-rw-r--r--heimdall-frontend/source/FirmwareInfo.h302
-rw-r--r--heimdall-frontend/source/PackageData.cpp66
-rw-r--r--heimdall-frontend/source/PackageData.h77
-rw-r--r--heimdall-frontend/source/Packaging.cpp934
-rw-r--r--heimdall-frontend/source/Packaging.h121
-rw-r--r--heimdall-frontend/source/aboutform.cpp126
-rw-r--r--heimdall-frontend/source/aboutform.h58
-rw-r--r--heimdall-frontend/source/main.cpp40
-rw-r--r--heimdall-frontend/source/mainwindow.cpp1375
-rw-r--r--heimdall-frontend/source/mainwindow.h183
13 files changed, 4148 insertions, 0 deletions
diff --git a/heimdall-frontend/source/Alerts.cpp b/heimdall-frontend/source/Alerts.cpp
new file mode 100644
index 0000000..0dfe19c
--- /dev/null
+++ b/heimdall-frontend/source/Alerts.cpp
@@ -0,0 +1,45 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Qt
+#include <QMessageBox>
+
+// Heimdall Frontend
+#include "Alerts.h"
+
+using namespace HeimdallFrontend;
+
+void Alerts::DisplayError(const QString& errorMessage)
+{
+ QMessageBox messageBox;
+ messageBox.setModal(true);
+ messageBox.setText(errorMessage);
+ messageBox.setIcon(QMessageBox::Critical);
+ messageBox.exec();
+}
+
+void Alerts::DisplayWarning(const QString& warningMessage)
+{
+ QMessageBox messageBox;
+ messageBox.setModal(true);
+ messageBox.setText(warningMessage);
+ messageBox.setIcon(QMessageBox::Warning);
+ messageBox.exec();
+}
diff --git a/heimdall-frontend/source/Alerts.h b/heimdall-frontend/source/Alerts.h
new file mode 100644
index 0000000..68b9c91
--- /dev/null
+++ b/heimdall-frontend/source/Alerts.h
@@ -0,0 +1,38 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef ALERTS_H
+#define ALERTS_H
+
+// Qt
+#include <QString>
+
+namespace HeimdallFrontend
+{
+ class Alerts
+ {
+ public:
+
+ static void DisplayError(const QString& errorMessage);
+ static void DisplayWarning(const QString& warningMessage);
+ };
+}
+
+#endif
diff --git a/heimdall-frontend/source/FirmwareInfo.cpp b/heimdall-frontend/source/FirmwareInfo.cpp
new file mode 100644
index 0000000..c01d636
--- /dev/null
+++ b/heimdall-frontend/source/FirmwareInfo.cpp
@@ -0,0 +1,783 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Qt
+#include "QRegExp"
+
+// Heimdall Frontend
+#include "Alerts.h"
+#include "FirmwareInfo.h"
+#include "Packaging.h"
+
+using namespace HeimdallFrontend;
+
+DeviceInfo::DeviceInfo()
+{
+}
+
+DeviceInfo::DeviceInfo(const QString& manufacturer, const QString& product, const QString& name)
+{
+ this->manufacturer = manufacturer;
+ this->product = product;
+ this->name = name;
+}
+
+bool DeviceInfo::ParseXml(QXmlStreamReader& xml)
+{
+ bool foundManufacturer = false;
+ bool foundProduct = false;
+ bool foundName = false;
+
+ while (!xml.atEnd())
+ {
+ QXmlStreamReader::TokenType nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "manufacturer")
+ {
+ if (foundManufacturer)
+ {
+ Alerts::DisplayError("Found multiple <manufacturer> elements in <device>.");
+ return (false);
+ }
+
+ foundManufacturer = true;
+
+ manufacturer = xml.readElementText();
+ }
+ else if (xml.name() == "product")
+ {
+ if (foundProduct)
+ {
+ Alerts::DisplayError("Found multiple <product> elements in <device>.");
+ return (false);
+ }
+
+ foundProduct = true;
+
+ product = xml.readElementText();
+ }
+ else if (xml.name() == "name")
+ {
+ if (foundName)
+ {
+ Alerts::DisplayError("Found multiple <name> elements in <device>.");
+ return (false);
+ }
+
+ foundName = true;
+
+ name = xml.readElementText();
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <device>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "device")
+ {
+ if (foundManufacturer && foundProduct && foundName)
+ {
+ return (true);
+ }
+ else
+ {
+ Alerts::DisplayError("Required elements are missing from <device>.");
+ return (false);
+ }
+ }
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <device>.");
+ return (false);
+ }
+ }
+ }
+
+ return (false);
+}
+
+void DeviceInfo::WriteXml(QXmlStreamWriter& xml) const
+{
+ xml.writeStartElement("device");
+
+ xml.writeStartElement("manufacturer");
+ xml.writeCharacters(manufacturer);
+ xml.writeEndElement();
+
+ xml.writeStartElement("product");
+ xml.writeCharacters(product);
+ xml.writeEndElement();
+
+ xml.writeStartElement("name");
+ xml.writeCharacters(name);
+ xml.writeEndElement();
+
+ xml.writeEndElement();
+}
+
+
+
+PlatformInfo::PlatformInfo()
+{
+}
+
+void PlatformInfo::Clear(void)
+{
+ name.clear();
+ version.clear();
+}
+
+bool PlatformInfo::IsCleared(void) const
+{
+ return (name.isEmpty() && version.isEmpty());
+}
+
+bool PlatformInfo::ParseXml(QXmlStreamReader& xml)
+{
+ Clear();
+
+ bool foundName = false;
+ bool foundVersion = false;
+
+ while (!xml.atEnd())
+ {
+ QXmlStreamReader::TokenType nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "name")
+ {
+ if (foundName)
+ {
+ Alerts::DisplayError("Found multiple <name> elements in <platform>.");
+ return (false);
+ }
+
+ foundName = true;
+
+ name = xml.readElementText();
+ }
+ else if (xml.name() == "version")
+ {
+ if (foundVersion)
+ {
+ Alerts::DisplayError("Found multiple <version> elements in <platform>.");
+ return (false);
+ }
+
+ foundVersion = true;
+
+ version = xml.readElementText();
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <platform>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "platform")
+ {
+ if (foundName && foundVersion)
+ {
+ return (true);
+ }
+ else
+ {
+ Alerts::DisplayError("Required elements are missing from <platform>.");
+ return (false);
+ }
+ }
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <platform>.");
+ return (false);
+ }
+ }
+ }
+
+ return (false);
+}
+
+void PlatformInfo::WriteXml(QXmlStreamWriter& xml) const
+{
+ xml.writeStartElement("platform");
+
+ xml.writeStartElement("name");
+ xml.writeCharacters(name);
+ xml.writeEndElement();
+
+ xml.writeStartElement("version");
+ xml.writeCharacters(version);
+ xml.writeEndElement();
+
+ xml.writeEndElement();
+}
+
+
+
+FileInfo::FileInfo()
+{
+}
+
+FileInfo::FileInfo(unsigned int partitionId, const QString& filename)
+{
+ this->partitionId = partitionId;
+ this->filename = filename;
+}
+
+bool FileInfo::ParseXml(QXmlStreamReader& xml)
+{
+ bool foundId = false;
+ bool foundFilename = false;
+
+ while (!xml.atEnd())
+ {
+ QXmlStreamReader::TokenType nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "id")
+ {
+ if (foundId)
+ {
+ Alerts::DisplayError("Found multiple <id> elements in <file>.");
+ return (false);
+ }
+
+ foundId = true;
+
+ partitionId = xml.readElementText().toInt();
+ }
+ else if (xml.name() == "filename")
+ {
+ if (foundFilename)
+ {
+ Alerts::DisplayError("Found multiple <filename> elements in <file>.");
+ return (false);
+ }
+
+ foundFilename = true;
+
+ filename = xml.readElementText();
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <file>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "file")
+ {
+ if (foundId && foundFilename)
+ {
+ return (true);
+ }
+ else
+ {
+ Alerts::DisplayError("Required elements are missing from <file>.");
+ return (false);
+ }
+ }
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <file>.");
+ return (false);
+ }
+ }
+ }
+
+ return (false);
+}
+
+void FileInfo::WriteXml(QXmlStreamWriter& xml, const QString& filename) const
+{
+ xml.writeStartElement("file");
+
+ xml.writeStartElement("id");
+ xml.writeCharacters(QString::number(partitionId));
+ xml.writeEndElement();
+
+ xml.writeStartElement("filename");
+ xml.writeCharacters(filename);
+ xml.writeEndElement();
+
+ xml.writeEndElement();
+}
+
+
+
+FirmwareInfo::FirmwareInfo()
+{
+ repartition = false;
+ noReboot = false;
+}
+
+void FirmwareInfo::Clear(void)
+{
+ name = "";
+ version = "";
+ platformInfo.Clear();
+
+ developers.clear();
+ url.clear();
+ donateUrl.clear();
+
+ deviceInfos.clear();
+
+ pitFilename.clear();
+ repartition = false;
+
+ noReboot = false;
+
+ fileInfos.clear();
+}
+
+bool FirmwareInfo::IsCleared(void) const
+{
+ return (name.isEmpty() && version.isEmpty() && platformInfo.IsCleared() && developers.isEmpty() && url.isEmpty() && url.isEmpty() && donateUrl.isEmpty()
+ && deviceInfos.isEmpty() && pitFilename.isEmpty() && !repartition && !noReboot && fileInfos.isEmpty());
+}
+
+bool FirmwareInfo::ParseXml(QXmlStreamReader& xml)
+{
+ Clear();
+
+ bool foundName = false;
+ bool foundVersion = false;
+ bool foundPlatform = false;
+ bool foundDevelopers = false;
+ bool foundUrl = false;
+ bool foundDonateUrl = false;
+ bool foundDevices = false;
+ bool foundPit = false;
+ bool foundRepartition = false;
+ bool foundNoReboot = false;
+ bool foundFiles = false;
+
+ if (!xml.readNextStartElement())
+ {
+ Alerts::DisplayError("Failed to find <firmware> element.");
+ return (false);
+ }
+
+ if (xml.name() != "firmware")
+ {
+ Alerts::DisplayError(QString("Expected <firmware> element but found <%1>.").arg(xml.name().toString()));
+ return (false);
+ }
+
+ QString formatVersionString;
+ formatVersionString += xml.attributes().value("version");
+
+ if (formatVersionString.isEmpty())
+ {
+ Alerts::DisplayError("<firmware> is missing the version attribute.");
+ return (false);
+ }
+
+ bool parsedVersion = false;
+ int formatVersion = formatVersionString.toInt(&parsedVersion);
+
+ if (!parsedVersion)
+ {
+ Alerts::DisplayError("<firmware> contains a malformed version.");
+ return (false);
+ }
+
+ if (formatVersion > kVersion)
+ {
+ Alerts::DisplayError("Package is for a newer version of Heimdall Frontend.\nPlease download the latest version of Heimdall Frontend.");
+ return (false);
+ }
+
+ while (!xml.atEnd())
+ {
+ QXmlStreamReader::TokenType nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "name")
+ {
+ if (foundName)
+ {
+ Alerts::DisplayError("Found multiple <name> elements in <firmware>.");
+ return (false);
+ }
+
+ foundName = true;
+ name = xml.readElementText();
+ }
+ else if (xml.name() == "version")
+ {
+ if (foundVersion)
+ {
+ Alerts::DisplayError("Found multiple <version> elements in <firmware>.");
+ return (false);
+ }
+
+ foundVersion = true;
+ version = xml.readElementText();
+ }
+ else if (xml.name() == "platform")
+ {
+ if (foundPlatform)
+ {
+ Alerts::DisplayError("Found multiple <platform> elements in <firmware>.");
+ return (false);
+ }
+
+ foundPlatform = true;
+
+ if (!platformInfo.ParseXml(xml))
+ return (false);
+ }
+ else if (xml.name() == "developers")
+ {
+ if (foundDevelopers)
+ {
+ Alerts::DisplayError("Found multiple <developers> elements in <firmware>.");
+ return (false);
+ }
+
+ foundDevelopers = true;
+
+ while (!xml.atEnd())
+ {
+ nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "name")
+ {
+ developers.append(xml.readElementText());
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <developers>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "developers")
+ break;
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <developers>.");
+ return (false);
+ }
+ }
+ }
+ }
+ else if (xml.name() == "url")
+ {
+ if (foundUrl)
+ {
+ Alerts::DisplayError("Found multiple <url> elements in <firmware>.");
+ return (false);
+ }
+
+ foundUrl = true;
+
+ url = xml.readElementText();
+ }
+ else if (xml.name() == "donateurl")
+ {
+ if (foundDonateUrl)
+ {
+ Alerts::DisplayError("Found multiple <donateurl> elements in <firmware>.");
+ return (false);
+ }
+
+ foundDonateUrl = true;
+
+ donateUrl = xml.readElementText();
+ }
+ else if (xml.name() == "devices")
+ {
+ if (foundDevices)
+ {
+ Alerts::DisplayError("Found multiple <devices> elements in <firmware>.");
+ return (false);
+ }
+
+ foundDevices = true;
+
+ while (!xml.atEnd())
+ {
+ nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "device")
+ {
+ DeviceInfo deviceInfo;
+
+ if (!deviceInfo.ParseXml(xml))
+ return (false);
+
+ deviceInfos.append(deviceInfo);
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <devices>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "devices")
+ break;
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <devices>.");
+ return (false);
+ }
+ }
+ }
+ }
+ else if (xml.name() == "pit")
+ {
+ if (foundPit)
+ {
+ Alerts::DisplayError("Found multiple <pit> elements in <firmware>.");
+ return (false);
+ }
+
+ foundPit = true;
+
+ pitFilename = xml.readElementText();
+ }
+ else if (xml.name() == "repartition")
+ {
+ if (foundRepartition)
+ {
+ Alerts::DisplayError("Found multiple <repartition> elements in <firmware>.");
+ return (false);
+ }
+
+ foundRepartition = true;
+
+ repartition = (xml.readElementText().toInt() != 0);
+ }
+ else if (xml.name() == "noreboot")
+ {
+ if (foundNoReboot)
+ {
+ Alerts::DisplayError("Found multiple <noreboot> elements in <firmware>.");
+ return (false);
+ }
+
+ foundNoReboot = true;
+
+ noReboot = (xml.readElementText().toInt() != 0);
+ }
+ else if (xml.name() == "files")
+ {
+ if (foundFiles)
+ {
+ Alerts::DisplayError("Found multiple <files> elements in <firmware>.");
+ return (false);
+ }
+
+ foundFiles = true;
+
+ while (!xml.atEnd())
+ {
+ nextToken = xml.readNext();
+
+ if (nextToken == QXmlStreamReader::StartElement)
+ {
+ if (xml.name() == "file")
+ {
+ FileInfo fileInfo;
+
+ if (!fileInfo.ParseXml(xml))
+ return (false);
+
+ fileInfos.append(fileInfo);
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <files>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "files")
+ break;
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <devices>.");
+ return (false);
+ }
+ }
+ }
+ }
+ else
+ {
+ Alerts::DisplayError(QString("<%1> is not a valid child of <firmware>.").arg(xml.name().toString()));
+ return (false);
+ }
+ }
+ else if (nextToken == QXmlStreamReader::EndElement)
+ {
+ if (xml.name() == "firmware")
+ {
+ if (!(foundName && foundVersion && foundPlatform && foundDevelopers && foundDevices && foundPit && foundRepartition && foundNoReboot && foundFiles))
+ {
+ Alerts::DisplayError("Required elements are missing from <firmware>.");
+ return (false);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (!(nextToken == QXmlStreamReader::Characters && xml.isWhitespace()))
+ {
+ Alerts::DisplayError("Unexpected token found in <firmware>.");
+ return (false);
+ }
+ }
+ }
+
+ // Read whitespaces at the end of the file (if there are any)
+ xml.readNext();
+
+ if (!xml.atEnd())
+ {
+ Alerts::DisplayError("Found data after </firmware>.");
+ return (false);
+ }
+
+ return (true);
+}
+
+void FirmwareInfo::WriteXml(QXmlStreamWriter& xml) const
+{
+ xml.writeStartDocument();
+ xml.writeStartElement("firmware");
+ xml.writeAttribute("version", QString::number(FirmwareInfo::kVersion));
+
+ xml.writeStartElement("name");
+ xml.writeCharacters(name);
+ xml.writeEndElement();
+
+ xml.writeStartElement("version");
+ xml.writeCharacters(version);
+ xml.writeEndElement();
+
+ platformInfo.WriteXml(xml);
+
+ xml.writeStartElement("developers");
+
+ for (int i = 0; i < developers.length(); i++)
+ {
+ xml.writeStartElement("name");
+ xml.writeCharacters(developers[i]);
+ xml.writeEndElement();
+ }
+
+ xml.writeEndElement();
+
+ if (!url.isEmpty())
+ {
+ xml.writeStartElement("url");
+ xml.writeCharacters(url);
+ xml.writeEndElement();
+ }
+
+ if (!donateUrl.isEmpty())
+ {
+ xml.writeStartElement("donateurl");
+ xml.writeCharacters(donateUrl);
+ xml.writeEndElement();
+ }
+
+ xml.writeStartElement("devices");
+
+ for (int i = 0; i < deviceInfos.length(); i++)
+ deviceInfos[i].WriteXml(xml);
+
+ xml.writeEndElement();
+
+ xml.writeStartElement("pit");
+
+ int lastSlash = pitFilename.lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = pitFilename.lastIndexOf('\\');
+
+ xml.writeCharacters(pitFilename.mid(lastSlash + 1));
+
+ xml.writeEndElement();
+
+ xml.writeStartElement("repartition");
+ xml.writeCharacters((repartition) ? "1" : "0");
+ xml.writeEndElement();
+
+ xml.writeStartElement("noreboot");
+ xml.writeCharacters((noReboot) ? "1" : "0");
+ xml.writeEndElement();
+
+ xml.writeStartElement("files");
+
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ fileInfos[i].WriteXml(xml, Packaging::ClashlessFilename(fileInfos, i));
+ }
+
+ xml.writeEndElement();
+
+ xml.writeEndElement();
+ xml.writeEndDocument();
+}
diff --git a/heimdall-frontend/source/FirmwareInfo.h b/heimdall-frontend/source/FirmwareInfo.h
new file mode 100644
index 0000000..8e6713e
--- /dev/null
+++ b/heimdall-frontend/source/FirmwareInfo.h
@@ -0,0 +1,302 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef FIRMWAREINFO_H
+#define FIRMWAREINFO_H
+
+// Qt
+#include <QFile>
+#include <QString>
+#include <QXmlStreamReader>
+
+namespace HeimdallFrontend
+{
+ class DeviceInfo
+ {
+ private:
+
+ QString manufacturer;
+ QString product;
+ QString name;
+
+ public:
+
+ DeviceInfo();
+ DeviceInfo(const QString& manufacturer, const QString& product, const QString& name);
+
+ bool ParseXml(QXmlStreamReader& xml);
+ void WriteXml(QXmlStreamWriter& xml) const;
+
+ const QString& GetManufacturer(void) const
+ {
+ return (manufacturer);
+ }
+
+ void SetManufacturer(const QString& manufacturer)
+ {
+ this->manufacturer = manufacturer;
+ }
+
+ const QString& GetProduct(void) const
+ {
+ return (product);
+ }
+
+ void SetProduct(const QString& product)
+ {
+ this->product = product;
+ }
+
+ const QString& GetName(void) const
+ {
+ return (name);
+ }
+
+ void SetName(const QString& name)
+ {
+ this->name = name;
+ }
+ };
+
+ class PlatformInfo
+ {
+ private:
+
+ QString name;
+ QString version;
+
+ public:
+
+ PlatformInfo();
+
+ void Clear(void);
+ bool IsCleared(void) const;
+
+ bool ParseXml(QXmlStreamReader& xml);
+ void WriteXml(QXmlStreamWriter& xml) const;
+
+ const QString& GetName(void) const
+ {
+ return (name);
+ }
+
+ void SetName(const QString& name)
+ {
+ this->name = name;
+ }
+
+ const QString& GetVersion(void) const
+ {
+ return (version);
+ }
+
+ void SetVersion(const QString& version)
+ {
+ this->version = version;
+ }
+ };
+
+ class FileInfo
+ {
+ private:
+
+ unsigned int partitionId;
+ QString filename;
+
+ public:
+
+ FileInfo();
+ FileInfo(unsigned int partitionId, const QString& filename);
+
+ bool ParseXml(QXmlStreamReader& xml);
+ void WriteXml(QXmlStreamWriter& xml, const QString& filename) const;
+
+ unsigned int GetPartitionId(void) const
+ {
+ return (partitionId);
+ }
+
+ void SetPartitionId(unsigned int partitionId)
+ {
+ this->partitionId = partitionId;
+ }
+
+ const QString& GetFilename(void) const
+ {
+ return (filename);
+ }
+
+ void SetFilename(const QString& filename)
+ {
+ this->filename = filename;
+ }
+ };
+
+ class FirmwareInfo
+ {
+ public:
+
+ enum
+ {
+ kVersion = 1
+ };
+
+ private:
+
+ QString name;
+ QString version;
+ PlatformInfo platformInfo;
+
+ QList<QString> developers;
+ QString url;
+ QString donateUrl;
+
+ QList<DeviceInfo> deviceInfos;
+
+ QString pitFilename;
+ bool repartition;
+
+ bool noReboot;
+
+ QList<FileInfo> fileInfos;
+
+ public:
+
+ FirmwareInfo();
+
+ void Clear(void);
+ bool IsCleared(void) const;
+
+ bool ParseXml(QXmlStreamReader& xml);
+ void WriteXml(QXmlStreamWriter& xml) const;
+
+ const QString& GetName(void) const
+ {
+ return (name);
+ }
+
+ void SetName(const QString& name)
+ {
+ this->name = name;
+ }
+
+ const QString& GetVersion(void) const
+ {
+ return (version);
+ }
+
+ void SetVersion(const QString& version)
+ {
+ this->version = version;
+ }
+
+ const PlatformInfo& GetPlatformInfo(void) const
+ {
+ return (platformInfo);
+ }
+
+ PlatformInfo& GetPlatformInfo(void)
+ {
+ return (platformInfo);
+ }
+
+ const QList<QString>& GetDevelopers(void) const
+ {
+ return (developers);
+ }
+
+ QList<QString>& GetDevelopers(void)
+ {
+ return (developers);
+ }
+
+ const QString& GetUrl(void) const
+ {
+ return (url);
+ }
+
+ void SetUrl(const QString& url)
+ {
+ this->url = url;
+ }
+
+ const QString& GetDonateUrl(void) const
+ {
+ return (donateUrl);
+ }
+
+ void SetDonateUrl(const QString& donateUrl)
+ {
+ this->donateUrl = donateUrl;
+ }
+
+ const QList<DeviceInfo>& GetDeviceInfos(void) const
+ {
+ return (deviceInfos);
+ }
+
+ QList<DeviceInfo>& GetDeviceInfos(void)
+ {
+ return (deviceInfos);
+ }
+
+ const QString& GetPitFilename(void) const
+ {
+ return (pitFilename);
+ }
+
+ void SetPitFilename(const QString& pitFilename)
+ {
+ this->pitFilename = pitFilename;
+ }
+
+ bool GetRepartition(void) const
+ {
+ return (repartition);
+ }
+
+ void SetRepartition(bool repartition)
+ {
+ this->repartition = repartition;
+ }
+
+ bool GetNoReboot(void) const
+ {
+ return (noReboot);
+ }
+
+ void SetNoReboot(bool noReboot)
+ {
+ this->noReboot = noReboot;
+ }
+
+ const QList<FileInfo>& GetFileInfos(void) const
+ {
+ return (fileInfos);
+ }
+
+ QList<FileInfo>& GetFileInfos(void)
+ {
+ return (fileInfos);
+ }
+ };
+}
+
+#endif
diff --git a/heimdall-frontend/source/PackageData.cpp b/heimdall-frontend/source/PackageData.cpp
new file mode 100644
index 0000000..c3689b3
--- /dev/null
+++ b/heimdall-frontend/source/PackageData.cpp
@@ -0,0 +1,66 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Heimdall Frontend
+#include "Alerts.h"
+#include "PackageData.h"
+
+using namespace HeimdallFrontend;
+
+PackageData::PackageData()
+{
+}
+
+PackageData::~PackageData()
+{
+ for (int i = 0; i < files.length(); i++)
+ delete files[i];
+}
+
+void PackageData::Clear(void)
+{
+ firmwareInfo.Clear();
+
+ for (int i = 0; i < files.length(); i++)
+ delete files[i];
+
+ files.clear();
+}
+
+bool PackageData::ReadFirmwareInfo(QFile *file)
+{
+ if (!file->open(QFile::ReadOnly))
+ {
+ Alerts::DisplayError(QString("Failed to open file: \1%s").arg(file->fileName()));
+ return (false);
+ }
+
+ QXmlStreamReader xml(file);
+ bool success = firmwareInfo.ParseXml(xml);
+
+ file->close();
+
+ return (success);
+}
+
+bool PackageData::IsCleared(void) const
+{
+ return (firmwareInfo.IsCleared() && files.isEmpty());
+}
diff --git a/heimdall-frontend/source/PackageData.h b/heimdall-frontend/source/PackageData.h
new file mode 100644
index 0000000..84f081c
--- /dev/null
+++ b/heimdall-frontend/source/PackageData.h
@@ -0,0 +1,77 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef PACKAGEDATA_H
+#define PACKAGEDATA_H
+
+// Qt
+#include <QTemporaryFile>
+
+// Heimdall Frontend
+#include "FirmwareInfo.h"
+
+namespace HeimdallFrontend
+{
+ class PackageData
+ {
+ private:
+
+ FirmwareInfo firmwareInfo;
+ QList<QTemporaryFile *> files;
+
+ public:
+
+ PackageData();
+ ~PackageData();
+
+ void Clear(void);
+ bool ReadFirmwareInfo(QFile *file);
+
+ bool IsCleared(void) const;
+
+ const FirmwareInfo& GetFirmwareInfo(void) const
+ {
+ return (firmwareInfo);
+ }
+
+ FirmwareInfo& GetFirmwareInfo(void)
+ {
+ return (firmwareInfo);
+ }
+
+ const QList<QTemporaryFile *>& GetFiles(void) const
+ {
+ return (files);
+ }
+
+ QList<QTemporaryFile *>& GetFiles(void)
+ {
+ return (files);
+ }
+
+ // Simply clears the files list, it does delete/close any files.
+ void RemoveAllFiles(void)
+ {
+ files.clear();
+ }
+ };
+}
+
+#endif
diff --git a/heimdall-frontend/source/Packaging.cpp b/heimdall-frontend/source/Packaging.cpp
new file mode 100644
index 0000000..7a27b3c
--- /dev/null
+++ b/heimdall-frontend/source/Packaging.cpp
@@ -0,0 +1,934 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifdef WIN32
+#pragma warning(disable : 4996)
+#endif
+
+// C/C++ Standard Library
+#include <stdio.h>
+
+// zlib
+#include "zlib.h"
+
+// Qt
+#include <QDateTime>
+#include <QDir>
+#include <QProgressDialog>
+
+// Heimdall Frontend
+#include "Alerts.h"
+#include "Packaging.h"
+
+using namespace HeimdallFrontend;
+
+const qint64 Packaging::kMaxFileSize = 8589934592ll;
+const char *Packaging::ustarMagic = "ustar";
+
+bool Packaging::ExtractTar(QTemporaryFile& tarFile, PackageData *packageData)
+{
+ TarHeader tarHeader;
+
+ if (!tarFile.open())
+ {
+ Alerts::DisplayError(QString("Error opening temporary TAR archive:\n%1").arg(tarFile.fileName()));
+ return (false);
+ }
+
+ bool previousEmpty = false;
+
+ QProgressDialog progressDialog("Extracting files...", "Cancel", 0, tarFile.size());
+ progressDialog.setWindowModality(Qt::ApplicationModal);
+ progressDialog.setWindowTitle("Heimdall Frontend");
+
+ while (!tarFile.atEnd())
+ {
+ qint64 dataRead = tarFile.read(tarHeader.buffer, TarHeader::kBlockLength);
+
+ if (dataRead != TarHeader::kBlockLength)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Package's TAR archive is malformed.");
+
+ tarFile.close();
+
+ return (false);
+ }
+
+ progressDialog.setValue(tarFile.pos());
+
+ if (progressDialog.wasCanceled())
+ {
+ tarFile.close();
+ progressDialog.close();
+
+ return (false);
+ }
+
+ //bool ustarFormat = strcmp(tarHeader.fields.magic, ustarMagic) == 0;
+ bool empty = true;
+
+ for (int i = 0; i < TarHeader::kBlockLength; i++)
+ {
+ if (tarHeader.buffer[i] != 0)
+ {
+ empty = false;
+ break;
+ }
+ }
+
+ if (empty)
+ {
+ if (previousEmpty)
+ {
+ // Two empty blocks in a row means we've reached the end of the archive.
+ break;
+ }
+ }
+ else
+ {
+ int checksum = 0;
+
+ for (char *bufferIndex = tarHeader.buffer; bufferIndex < tarHeader.fields.checksum; bufferIndex++)
+ checksum += static_cast<unsigned char>(*bufferIndex);
+
+ checksum += 8 * ' ';
+ checksum += static_cast<unsigned char>(tarHeader.fields.typeFlag);
+
+ // Both the TAR and USTAR formats have terrible documentation, it's not clear if the following code is required.
+ /*if (ustarFormat)
+ {
+ for (char *bufferIndex = tarHeader.fields.linkName; bufferIndex < tarHeader.fields.prefix + 155; bufferIndex++)
+ checksum += static_cast<unsigned char>(*bufferIndex);
+ }*/
+
+ bool parsed = false;
+
+ // The size field is not always null terminated, so we must create a copy and null terminate it for parsing.
+ char fileSizeString[13];
+ memcpy(fileSizeString, tarHeader.fields.size, 12);
+ fileSizeString[12] = '\0';
+
+ qulonglong fileSize = QString(fileSizeString).toULongLong(&parsed, 8);
+
+ if (!parsed)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Tar header contained an invalid file size.");
+
+ tarFile.close();
+
+ return (false);
+ }
+
+ if (fileSize > 0 && tarHeader.fields.typeFlag == '0')
+ {
+ // We're working with a file.
+ QString filename = QString::fromUtf8(tarHeader.fields.name);
+
+ QTemporaryFile *outputFile = new QTemporaryFile("XXXXXX-" + filename);
+ packageData->GetFiles().append(outputFile);
+
+ if (!outputFile->open())
+ {
+ progressDialog.close();
+ Alerts::DisplayError(QString("Failed to open output file: \n%1").arg(outputFile->fileName()));
+
+ tarFile.close();
+
+ return (false);
+ }
+
+ qulonglong dataRemaining = fileSize;
+ char readBuffer[TarHeader::kBlockReadCount * TarHeader::kBlockLength];
+
+ // Copy the file contents from tarFile to outputFile
+ while (dataRemaining > 0)
+ {
+ qint64 fileDataToRead = (dataRemaining < TarHeader::kBlockReadCount * TarHeader::kBlockLength)
+ ? dataRemaining : TarHeader::kBlockReadCount * TarHeader::kBlockLength;
+
+ qint64 dataRead = tarFile.read(readBuffer, fileDataToRead + (TarHeader::kBlockLength - fileDataToRead % TarHeader::kBlockLength) % TarHeader::kBlockLength);
+
+ if (dataRead < fileDataToRead || dataRead % TarHeader::kBlockLength != 0)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Unexpected read error whilst extracting package files.");
+
+ tarFile.close();
+ outputFile->close();
+
+ remove(outputFile->fileName().toStdString().c_str());
+
+ return (false);
+ }
+
+ outputFile->write(readBuffer, fileDataToRead);
+
+ dataRemaining -= fileDataToRead;
+
+ progressDialog.setValue(tarFile.pos());
+
+ if (progressDialog.wasCanceled())
+ {
+ tarFile.close();
+ outputFile->close();
+
+ remove(outputFile->fileName().toStdString().c_str());
+
+ progressDialog.close();
+
+ return (false);
+ }
+ }
+
+ outputFile->close();
+ }
+ else
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Heimdall packages shouldn't contain links or directories.");
+
+ tarFile.close();
+
+ return (false);
+ }
+ }
+
+ previousEmpty = empty;
+ }
+
+ progressDialog.close();
+ tarFile.close();
+
+ return (true);
+}
+
+bool Packaging::WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, const QString& entryFilename)
+{
+ TarHeader tarHeader;
+ memset(tarHeader.buffer, 0, TarHeader::kBlockLength);
+
+ QFile file(filePath);
+
+ if (!file.open(QFile::ReadOnly))
+ {
+ Alerts::DisplayError(QString("Failed to open file: \n%1").arg(file.fileName()));
+ return (false);
+ }
+
+ if (file.size() > Packaging::kMaxFileSize)
+ {
+ Alerts::DisplayError(QString("File is too large to be packaged:\n%1").arg(file.fileName()));
+ return (false);
+ }
+
+ QFileInfo qtFileInfo(file);
+ QByteArray utfFilename;
+
+ utfFilename = entryFilename.toUtf8();
+
+ if (utfFilename.length() > 100)
+ {
+ Alerts::DisplayError(QString("File name is too long:\n%1").arg(qtFileInfo.fileName()));
+ return (false);
+ }
+
+ strcpy(tarHeader.fields.name, utfFilename.constData());
+
+ unsigned int mode = 0;
+
+ QFile::Permissions permissions = file.permissions();
+
+ // Other
+ if (permissions.testFlag(QFile::ExeOther))
+ mode |= TarHeader::kModeOtherExecute;
+ if (permissions.testFlag(QFile::WriteOther))
+ mode |= TarHeader::kModeOtherWrite;
+ if (permissions.testFlag(QFile::ReadOther))
+ mode |= TarHeader::kModeOtherRead;
+
+ // Group
+ if (permissions.testFlag(QFile::ExeGroup))
+ mode |= TarHeader::kModeGroupExecute;
+ if (permissions.testFlag(QFile::WriteGroup))
+ mode |= TarHeader::kModeGroupWrite;
+ if (permissions.testFlag(QFile::ReadGroup))
+ mode |= TarHeader::kModeGroupRead;
+
+ // Owner
+ if (permissions.testFlag(QFile::ExeOwner))
+ mode |= TarHeader::kModeOwnerExecute;
+ if (permissions.testFlag(QFile::WriteOwner))
+ mode |= TarHeader::kModeOwnerWrite;
+ if (permissions.testFlag(QFile::ReadOwner))
+ mode |= TarHeader::kModeOwnerRead;
+
+ sprintf(tarHeader.fields.mode, "%07o", mode);
+
+ // Owner id
+ uint id = qtFileInfo.ownerId();
+
+ if (id < 2097151)
+ sprintf(tarHeader.fields.userId, "%07o", id);
+ else
+ sprintf(tarHeader.fields.userId, "%07o", 0);
+
+ // Group id
+ id = qtFileInfo.groupId();
+
+ if (id < 2097151)
+ sprintf(tarHeader.fields.groupId, "%07o", id);
+ else
+ sprintf(tarHeader.fields.groupId, "%07o", 0);
+
+ // Note: We don't support base-256 encoding. Support could be added later.
+ sprintf(tarHeader.fields.size, "%011llo", file.size());
+ sprintf(tarHeader.fields.modifiedTime, "%u", qtFileInfo.lastModified().toTime_t());
+
+ // Regular File
+ tarHeader.fields.typeFlag = '0';
+
+ // Calculate checksum
+ int checksum = 0;
+ memset(tarHeader.fields.checksum, ' ', 8);
+
+ for (int i = 0; i < TarHeader::kTarHeaderLength; i++)
+ checksum += static_cast<unsigned char>(tarHeader.buffer[i]);
+
+ sprintf(tarHeader.fields.checksum, "%07o", checksum);
+
+ // Write the header to the TAR file.
+ tarFile->write(tarHeader.buffer, TarHeader::kBlockLength);
+
+ char buffer[TarHeader::kBlockWriteCount * TarHeader::kBlockLength];
+ qint64 offset = 0;
+
+ while (offset < file.size())
+ {
+ qint64 dataRead = file.read(buffer, TarHeader::kBlockWriteCount * TarHeader::kBlockLength);
+
+ if (tarFile->write(buffer, dataRead) != dataRead)
+ {
+ Alerts::DisplayError("Failed to write data to the temporary TAR file.");
+ return (false);
+ }
+
+ if (dataRead % TarHeader::kBlockLength != 0)
+ {
+ int remainingBlockLength = TarHeader::kBlockLength - dataRead % TarHeader::kBlockLength;
+ memset(buffer, 0, remainingBlockLength);
+
+ if (tarFile->write(buffer, remainingBlockLength) != remainingBlockLength)
+ {
+ Alerts::DisplayError("Failed to write data to the temporary TAR file.");
+ return (false);
+ }
+ }
+
+ offset += dataRead;
+ }
+
+ return (true);
+}
+
+bool Packaging::CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarFile)
+{
+ const QList<FileInfo>& fileInfos = firmwareInfo.GetFileInfos();
+
+ QProgressDialog progressDialog("Packaging files...", "Cancel", 0, fileInfos.length() + 2);
+ progressDialog.setWindowModality(Qt::ApplicationModal);
+ progressDialog.setWindowTitle("Heimdall Frontend");
+
+ QTemporaryFile firmwareXmlFile("XXXXXX-firmware.xml");
+
+ if (!firmwareXmlFile.open())
+ {
+ progressDialog.close();
+ Alerts::DisplayError(QString("Failed to create temporary file: \n%1").arg(firmwareXmlFile.fileName()));
+
+ return (false);
+ }
+
+ QXmlStreamWriter xml(&firmwareXmlFile);
+ firmwareInfo.WriteXml(xml);
+ firmwareXmlFile.close();
+
+ if (!tarFile->open())
+ {
+ progressDialog.close();
+ Alerts::DisplayError(QString("Failed to open file: \n%1").arg(tarFile->fileName()));
+
+ return (false);
+ }
+
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ // If the file was already compressed we don't compress it again.
+ bool skip = false;
+
+ for (int j = 0; j < i; j++)
+ {
+ if (fileInfos[i].GetFilename() == fileInfos[j].GetFilename())
+ {
+ skip = true;
+ break;
+ }
+ }
+
+ if (skip)
+ {
+ progressDialog.setValue(i);
+ continue;
+ }
+
+ QString filename = ClashlessFilename(fileInfos, i);
+
+ if (filename == "firmware.xml")
+ {
+ Alerts::DisplayError("You cannot name your partition files \"firmware.xml\".\nIt is a reserved name.");
+ return (false);
+ }
+
+ if (!WriteTarEntry(fileInfos[i].GetFilename(), tarFile, filename))
+ {
+ tarFile->resize(0);
+ tarFile->close();
+
+ progressDialog.close();
+
+ return (false);
+ }
+
+ progressDialog.setValue(i);
+
+ if (progressDialog.wasCanceled())
+ {
+ tarFile->resize(0);
+ tarFile->close();
+
+ progressDialog.close();
+
+ return (false);
+ }
+ }
+
+ int lastSlash = firmwareInfo.GetPitFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = firmwareInfo.GetPitFilename().lastIndexOf('\\');
+
+ QString pitFilename = ClashlessFilename(fileInfos, firmwareInfo.GetPitFilename().mid(lastSlash + 1));
+
+ if (pitFilename == "firmware.xml")
+ {
+ Alerts::DisplayError("You cannot name your PIT file \"firmware.xml\".\nIt is a reserved name.");
+ return (false);
+ }
+
+ if (!WriteTarEntry(firmwareInfo.GetPitFilename(), tarFile, pitFilename))
+ {
+ tarFile->resize(0);
+ tarFile->close();
+
+ return (false);
+ }
+
+ progressDialog.setValue(progressDialog.value() + 1);
+
+ if (progressDialog.wasCanceled())
+ {
+ tarFile->resize(0);
+ tarFile->close();
+
+ progressDialog.close();
+
+ return (false);
+ }
+
+ if (!WriteTarEntry(firmwareXmlFile.fileName(), tarFile, "firmware.xml"))
+ {
+ tarFile->resize(0);
+ tarFile->close();
+
+ return (false);
+ }
+
+ progressDialog.setValue(progressDialog.value() + 1);
+ progressDialog.close();
+
+ // Write two empty blocks to signify the end of the archive.
+ char emptyEntry[TarHeader::kBlockLength];
+ memset(emptyEntry, 0, TarHeader::kBlockLength);
+
+ tarFile->write(emptyEntry, TarHeader::kBlockLength);
+ tarFile->write(emptyEntry, TarHeader::kBlockLength);
+
+ tarFile->close();
+
+ return (true);
+}
+
+bool Packaging::ExtractPackage(const QString& packagePath, PackageData *packageData)
+{
+ FILE *compressedPackageFile = fopen(packagePath.toStdString().c_str(), "rb");
+
+ if (!compressedPackageFile)
+ {
+ Alerts::DisplayError(QString("Failed to open package:\n%1").arg(packagePath));
+ return (false);
+ }
+
+ fseek(compressedPackageFile, 0, SEEK_END);
+ quint64 compressedFileSize = ftell(compressedPackageFile);
+ rewind(compressedPackageFile);
+
+ gzFile packageFile = gzdopen(fileno(compressedPackageFile), "rb");
+
+ QTemporaryFile outputTar("XXXXXX.tar");
+
+ if (!outputTar.open())
+ {
+ Alerts::DisplayError("Failed to open temporary TAR archive.");
+ gzclose(packageFile);
+
+ return (false);
+ }
+
+ char buffer[kExtractBufferLength];
+ int bytesRead;
+ quint64 totalBytesRead = 0;
+
+ QProgressDialog progressDialog("Decompressing package...", "Cancel", 0, compressedFileSize);
+ progressDialog.setWindowModality(Qt::ApplicationModal);
+ progressDialog.setWindowTitle("Heimdall Frontend");
+
+ do
+ {
+ bytesRead = gzread(packageFile, buffer, kExtractBufferLength);
+
+ if (bytesRead == -1)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Error decompressing archive.");
+
+ gzclose(packageFile);
+
+ return (false);
+ }
+
+ outputTar.write(buffer, bytesRead);
+
+ totalBytesRead += bytesRead;
+ progressDialog.setValue(totalBytesRead);
+
+ if (progressDialog.wasCanceled())
+ {
+ gzclose(packageFile);
+ progressDialog.close();
+
+ return (false);
+ }
+ } while (bytesRead > 0);
+
+ progressDialog.close();
+
+ outputTar.close();
+ gzclose(packageFile); // Closes packageFile and compressedPackageFile
+
+ if (!ExtractTar(outputTar, packageData))
+ return (false);
+
+ // Find and read firmware.xml
+ for (int i = 0; i < packageData->GetFiles().length(); i++)
+ {
+ QTemporaryFile *file = packageData->GetFiles()[i];
+
+ if (file->fileTemplate() == "XXXXXX-firmware.xml")
+ {
+ if (!packageData->ReadFirmwareInfo(file))
+ {
+ packageData->Clear();
+ return (false);
+ }
+
+ return (true);
+ }
+ }
+
+ Alerts::DisplayError("firmware.xml is missing from the package.");
+ return (false);
+}
+
+bool Packaging::BuildPackage(const QString& packagePath, const FirmwareInfo& firmwareInfo)
+{
+ FILE *compressedPackageFile = fopen(packagePath.toStdString().c_str(), "wb");
+
+ if (!compressedPackageFile)
+ {
+ Alerts::DisplayError(QString("Failed to create package:\n%1").arg(packagePath));
+ return (false);
+ }
+
+ QTemporaryFile tar("XXXXXX.tar");
+
+ if (!CreateTar(firmwareInfo, &tar))
+ {
+ fclose(compressedPackageFile);
+ remove(packagePath.toStdString().c_str());
+
+ return (false);
+ }
+
+ if (!tar.open())
+ {
+ Alerts::DisplayError(QString("Failed to open temporary file: \n%1").arg(tar.fileName()));
+
+ fclose(compressedPackageFile);
+ remove(packagePath.toStdString().c_str());
+
+ return (false);
+ }
+
+ gzFile packageFile = gzdopen(fileno(compressedPackageFile), "wb");
+
+ char buffer[kCompressBufferLength];
+ qint64 totalBytesRead = 0;
+ int bytesRead;
+
+ QProgressDialog progressDialog("Compressing package...", "Cancel", 0, tar.size());
+ progressDialog.setWindowModality(Qt::ApplicationModal);
+ progressDialog.setWindowTitle("Heimdall Frontend");
+
+ do
+ {
+ bytesRead = tar.read(buffer, kCompressBufferLength);
+
+ if (bytesRead == -1)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Error reading temporary TAR file.");
+
+ gzclose(packageFile);
+ remove(packagePath.toStdString().c_str());
+
+ return (false);
+ }
+
+ if (gzwrite(packageFile, buffer, bytesRead) != bytesRead)
+ {
+ progressDialog.close();
+ Alerts::DisplayError("Error compressing package.");
+
+ gzclose(packageFile);
+ remove(packagePath.toStdString().c_str());
+
+ return (false);
+ }
+
+ totalBytesRead += bytesRead;
+ progressDialog.setValue(totalBytesRead);
+
+ if (progressDialog.wasCanceled())
+ {
+ gzclose(packageFile);
+ remove(packagePath.toStdString().c_str());
+
+ progressDialog.close();
+
+ return (false);
+ }
+ } while (bytesRead > 0);
+
+ progressDialog.close();
+
+ gzclose(packageFile); // Closes packageFile and compressedPackageFile
+
+ return (true);
+}
+
+QString Packaging::ClashlessFilename(const QList<FileInfo>& fileInfos, int fileInfoIndex)
+{
+ int lastSlash = fileInfos[fileInfoIndex].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[fileInfoIndex].GetFilename().lastIndexOf('\\');
+
+ QString filename = fileInfos[fileInfoIndex].GetFilename().mid(lastSlash + 1);
+ unsigned int renameIndex = 0;
+
+ // Check for name clashes
+ for (int i = 0; i < fileInfoIndex; i++)
+ {
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1);
+
+ // If the filenames are the same, but the files themselves aren't the same (i.e. not the same path), then rename.
+ if (filename == otherFilename && fileInfos[i].GetFilename() != fileInfos[fileInfoIndex].GetFilename())
+ renameIndex++;
+ }
+
+ if (renameIndex > 0)
+ {
+ int lastPeriodIndex = filename.lastIndexOf(QChar('.'));
+ QString shortFilename;
+ QString fileType;
+
+ if (lastPeriodIndex >= 0)
+ {
+ shortFilename = filename.left(lastPeriodIndex);
+ fileType = filename.mid(lastPeriodIndex);
+ }
+ else
+ {
+ shortFilename = filename;
+ }
+
+ unsigned int renameIndexOffset = 0;
+ bool validIndexOffset = true;
+
+ // Before we append a rename index we must ensure it doesn't produce further collisions.
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1);
+
+ if (otherFilename.length() > filename.length() + 1)
+ {
+ QString trimmedOtherFilename = otherFilename.left(shortFilename.length());
+
+ if (shortFilename == trimmedOtherFilename)
+ {
+ lastPeriodIndex = otherFilename.lastIndexOf(QChar('.'));
+ QString shortOtherFilename;
+
+ if (lastPeriodIndex >= 0)
+ shortOtherFilename = otherFilename.left(lastPeriodIndex);
+ else
+ shortOtherFilename = otherFilename;
+
+ QRegExp renameExp("-[0-9]+");
+
+ if (renameExp.lastIndexIn(shortOtherFilename) == shortFilename.length())
+ {
+ unsigned int trailingInteger = shortOtherFilename.mid(shortFilename.length() + 1).toUInt(&validIndexOffset);
+
+ if (!validIndexOffset)
+ break;
+
+ if (trailingInteger > renameIndexOffset)
+ renameIndexOffset = trailingInteger;
+ }
+ }
+ }
+ }
+
+ if (validIndexOffset)
+ {
+ // Ensure renaming won't involve integer overflow!
+ if (renameIndex > static_cast<unsigned int>(-1) - renameIndexOffset)
+ validIndexOffset = false;
+ }
+
+ if (validIndexOffset)
+ {
+ shortFilename.append(QChar('-'));
+ shortFilename.append(QString::number(renameIndexOffset + renameIndex));
+
+ return (shortFilename + fileType);
+ }
+ else
+ {
+ // Fallback behaviour... an absolutely terrible brute force implementation!
+ QString filename;
+ QString renamePrefix;
+
+ for (;;)
+ {
+ renamePrefix.append(QChar('+'));
+
+ for (unsigned int i = 0; i < static_cast<unsigned int>(-1); i++)
+ {
+ filename = shortFilename + renamePrefix + QString::number(i) + fileType;
+
+ bool valid = true;
+
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ if (filename == fileInfos[i].GetFilename().mid(lastSlash + 1))
+ {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid)
+ return (filename);
+ }
+ }
+ }
+ }
+ else
+ {
+ return (filename);
+ }
+}
+
+QString Packaging::ClashlessFilename(const QList<FileInfo>& fileInfos, const QString& filename)
+{
+ unsigned int renameIndex = 0;
+
+ // Check for name clashes
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1);
+
+ if (filename == otherFilename)
+ renameIndex++;
+ }
+
+ if (renameIndex > 0)
+ {
+ int lastPeriodIndex = filename.lastIndexOf(QChar('.'));
+ QString shortFilename;
+ QString fileType;
+
+ if (lastPeriodIndex >= 0)
+ {
+ shortFilename = filename.left(lastPeriodIndex);
+ fileType = filename.mid(lastPeriodIndex);
+ }
+ else
+ {
+ shortFilename = filename;
+ }
+
+ unsigned int renameIndexOffset = 0;
+ bool validIndexOffset = true;
+
+ // Before we append a rename index we must ensure it doesn't produce further collisions.
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ QString otherFilename = fileInfos[i].GetFilename().mid(lastSlash + 1);
+
+ if (otherFilename.length() > filename.length() + 1)
+ {
+ QString trimmedOtherFilename = otherFilename.left(filename.length());
+
+ if (filename == trimmedOtherFilename)
+ {
+ lastPeriodIndex = otherFilename.lastIndexOf(QChar('.'));
+ QString shortOtherFilename;
+
+ if (lastPeriodIndex >= 0)
+ shortOtherFilename = otherFilename.left(lastPeriodIndex);
+ else
+ shortOtherFilename = otherFilename;
+
+ QRegExp renameExp("-[0-9]+");
+
+ if (renameExp.lastIndexIn(shortOtherFilename) == shortFilename.length())
+ {
+ unsigned int trailingInteger = shortOtherFilename.mid(shortFilename.length() + 1).toUInt(&validIndexOffset);
+
+ if (!validIndexOffset)
+ break;
+
+ if (trailingInteger > renameIndexOffset)
+ renameIndexOffset = trailingInteger;
+ }
+ }
+ }
+ }
+
+ if (validIndexOffset)
+ {
+ // Ensure renaming won't involve integer overflow!
+ if (renameIndex > static_cast<unsigned int>(-1) - renameIndexOffset)
+ validIndexOffset = false;
+ }
+
+ if (validIndexOffset)
+ {
+ shortFilename.append(QChar('-'));
+ shortFilename.append(QString::number(renameIndexOffset + renameIndex));
+
+ return (shortFilename + fileType);
+ }
+ else
+ {
+ // Fallback behaviour, brute-force/semi-random.
+ bool valid;
+ QString filename;
+
+ do
+ {
+ valid = true;
+
+ filename = shortFilename + "-";
+ for (int i = 0; i < 8; i++)
+ filename.append(QChar(qrand() % ('Z' - 'A' + 1) + 'A'));
+
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ int lastSlash = fileInfos[i].GetFilename().lastIndexOf('/');
+
+ if (lastSlash < 0)
+ lastSlash = fileInfos[i].GetFilename().lastIndexOf('\\');
+
+ if (filename == fileInfos[i].GetFilename().mid(lastSlash + 1))
+ {
+ valid = false;
+ break;
+ }
+ }
+ } while (!valid);
+
+ return (filename);
+ }
+ }
+ else
+ {
+ return (filename);
+ }
+}
diff --git a/heimdall-frontend/source/Packaging.h b/heimdall-frontend/source/Packaging.h
new file mode 100644
index 0000000..8ba83f7
--- /dev/null
+++ b/heimdall-frontend/source/Packaging.h
@@ -0,0 +1,121 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef PACKAGING_H
+#define PACKAGING_H
+
+// Qt
+#include <QList>
+#include <QString>
+#include <QTemporaryFile>
+
+// Heimdall Frontend
+#include "PackageData.h"
+
+namespace HeimdallFrontend
+{
+ union TarHeader
+ {
+ enum
+ {
+ kBlockLength = 512,
+ kBlockReadCount = 8,
+ kBlockWriteCount = 8,
+
+ kTarHeaderLength = 257,
+ kUstarHeaderLength = 500,
+ };
+
+ enum
+ {
+ kModeOtherExecute = 1,
+ kModeOtherWrite = 1 << 1,
+ kModeOtherRead = 1 << 2,
+
+ kModeGroupExecute = 1 << 3,
+ kModeGroupWrite = 1 << 4,
+ kModeGroupRead = 1 << 5,
+
+ kModeOwnerExecute = 1 << 6,
+ kModeOwnerWrite = 1 << 7,
+ kModeOwnerRead = 1 << 8,
+
+ kModeReserved = 2 << 9,
+ kModeSetGid = 2 << 10,
+ kModeSetUid = 2 << 11
+ };
+
+ struct
+ {
+ char name[100];
+ char mode[8];
+ char userId[8];
+ char groupId[8];
+ char size[12];
+ char modifiedTime[12];
+ char checksum[8];
+ char typeFlag;
+ char linkName[100];
+ char magic[6];
+ char version[2];
+ char userName[32];
+ char groupName[32];
+ char devMajor[8];
+ char devMinor[8];
+ char prefix[155];
+ } fields;
+
+ char buffer[kBlockLength];
+ };
+
+ class Packaging
+ {
+ public:
+
+ // Would definitely prefer to use an enum but VC++ and GCC give conflicting warnings about C++0x or type overflow.
+ static const qint64 kMaxFileSize;
+
+ private:
+
+ enum
+ {
+ kExtractBufferLength = 262144,
+ kCompressBufferLength = 262144
+ };
+
+ // TODO: Add support for sparse files to both methods?
+ static bool ExtractTar(QTemporaryFile& tarFile, PackageData *packageData);
+
+ static bool WriteTarEntry(const QString& filePath, QTemporaryFile *tarFile, const QString& entryFilename);
+ static bool CreateTar(const FirmwareInfo& firmwareInfo, QTemporaryFile *tarFile); // Uses original TAR format.
+
+ public:
+
+ static const char *ustarMagic;
+
+ static bool ExtractPackage(const QString& packagePath, PackageData *packageData);
+ static bool BuildPackage(const QString& packagePath, const FirmwareInfo& firmwareInfo);
+
+ static QString ClashlessFilename(const QList<FileInfo>& fileInfos, int fileInfoIndex);
+ static QString ClashlessFilename(const QList<FileInfo>& fileInfos, const QString& filename);
+ };
+}
+
+#endif
diff --git a/heimdall-frontend/source/aboutform.cpp b/heimdall-frontend/source/aboutform.cpp
new file mode 100644
index 0000000..93e56fb
--- /dev/null
+++ b/heimdall-frontend/source/aboutform.cpp
@@ -0,0 +1,126 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Qt
+#include <QDir>
+#include <QProcess>
+
+// Heimdall Frontend
+#include "aboutform.h"
+
+#include <QStringList>
+
+#define UNUSED(x) (void)(x)
+
+using namespace HeimdallFrontend;
+
+AboutForm::AboutForm(QWidget *parent) : QWidget(parent)
+{
+ setupUi(this);
+
+ // Heimdall Command Line
+ QObject::connect(&heimdallProcess, SIGNAL(readyRead()), this, SLOT(HandleHeimdallStdout()));
+ QObject::connect(&heimdallProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(HandleHeimdallReturned(int, QProcess::ExitStatus)));
+ QObject::connect(&heimdallProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(HandleHeimdallError(QProcess::ProcessError)));
+
+ heimdallFailed = false;
+
+ RetrieveHeimdallVersion();
+}
+
+void AboutForm::RetrieveHeimdallVersion(void)
+{
+ heimdallProcess.setReadChannel(QProcess::StandardOutput);
+
+ heimdallProcess.start("heimdall", QStringList("version"));
+ heimdallProcess.waitForFinished(350);
+
+ // OS X was playing up and not finding heimdall, so we're manually checking the PATH.
+ if (heimdallFailed)
+ {
+ QStringList environment = QProcess::systemEnvironment();
+
+ QStringList paths;
+
+ // Ensure /usr/bin is in PATH
+ for (int i = 0; i < environment.length(); i++)
+ {
+ if (environment[i].left(5) == "PATH=")
+ {
+ paths = environment[i].mid(5).split(':');
+ paths.prepend("/usr/bin");
+ break;
+ }
+ }
+
+ int pathIndex = -1;
+
+ while (heimdallFailed && ++pathIndex < paths.length())
+ {
+ QString heimdallPath = paths[pathIndex];
+
+ if (heimdallPath.length() > 0)
+ {
+ heimdallFailed = false;
+
+ if (heimdallPath[heimdallPath.length() - 1] != QDir::separator())
+ heimdallPath += QDir::separator();
+
+ heimdallPath += "heimdall";
+
+ heimdallProcess.start(heimdallPath, QStringList("version"));
+ heimdallProcess.waitForFinished(350);
+ }
+ }
+
+ if (heimdallFailed)
+ versionCopyrightLabel->setText(versionCopyrightLabel->text().replace("%HEIMDALL-VERSION%", ""));
+ }
+}
+
+void AboutForm::HandleHeimdallStdout(void)
+{
+ QString version = heimdallProcess.readAll();
+
+ if (version.length() > 0)
+ {
+ if (version.at(0) == QChar('v'))
+ version = version.mid(1);
+
+ versionCopyrightLabel->setText(versionCopyrightLabel->text().replace("%HEIMDALL-VERSION%", "Version " + version + "<br />"));
+ }
+}
+
+void AboutForm::HandleHeimdallReturned(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ UNUSED(exitCode);
+ UNUSED(exitStatus);
+
+ // If for some reason %HEIMDALL-VERSION% hasn't been replaced yet, we'll replace it with an empty string.
+ versionCopyrightLabel->setText(versionCopyrightLabel->text().replace("%HEIMDALL-VERSION%", ""));
+}
+
+void AboutForm::HandleHeimdallError(QProcess::ProcessError error)
+{
+ UNUSED(error);
+
+ heimdallFailed = true;
+}
+
diff --git a/heimdall-frontend/source/aboutform.h b/heimdall-frontend/source/aboutform.h
new file mode 100644
index 0000000..3f5d715
--- /dev/null
+++ b/heimdall-frontend/source/aboutform.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef ABOUTFORM_H
+#define ABOUTFORM_H
+
+// Qt
+#include <QProcess>
+#include <QWidget>
+
+// Heimdall Frontend
+#include "ui_aboutform.h"
+
+namespace HeimdallFrontend
+{
+ class AboutForm : public QWidget, public Ui::AboutForm
+ {
+ Q_OBJECT
+
+ private:
+
+ bool heimdallFailed;
+ QProcess heimdallProcess;
+
+ void RetrieveHeimdallVersion(void);
+
+ public:
+
+ explicit AboutForm(QWidget *parent = 0);
+
+ public slots:
+
+ // Heimdall Command Line
+ void HandleHeimdallStdout(void);
+ void HandleHeimdallReturned(int exitCode, QProcess::ExitStatus exitStatus);
+ void HandleHeimdallError(QProcess::ProcessError error);
+ };
+}
+
+#endif
+
diff --git a/heimdall-frontend/source/main.cpp b/heimdall-frontend/source/main.cpp
new file mode 100644
index 0000000..c327b63
--- /dev/null
+++ b/heimdall-frontend/source/main.cpp
@@ -0,0 +1,40 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Qt
+#include <QApplication>
+#include <QtPlugin>
+
+// Heimdall Frontend
+#include "mainwindow.h"
+
+Q_IMPORT_PLUGIN (QWindowsIntegrationPlugin);
+
+using namespace HeimdallFrontend;
+
+int main(int argc, char *argv[])
+{
+ QApplication application(argc, argv);
+
+ MainWindow window;
+ window.show();
+
+ return (application.exec());
+}
diff --git a/heimdall-frontend/source/mainwindow.cpp b/heimdall-frontend/source/mainwindow.cpp
new file mode 100644
index 0000000..94321b3
--- /dev/null
+++ b/heimdall-frontend/source/mainwindow.cpp
@@ -0,0 +1,1375 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+// Qt
+#include <QCoreApplication>
+#include <QDesktopServices>
+#include <QDir>
+#include <QFileDialog>
+#include <QProcess>
+#include <QRegExp>
+#include <QUrl>
+
+// Heimdall Frontend
+#include "Alerts.h"
+#include "mainwindow.h"
+#include "Packaging.h"
+
+#define UNUSED(x) (void)(x)
+
+using namespace HeimdallFrontend;
+
+void MainWindow::StartHeimdall(const QStringList& arguments)
+{
+ UpdateInterfaceAvailability();
+
+ heimdallProcess.setReadChannel(QProcess::StandardOutput);
+
+ heimdallProcess.start("heimdall", arguments);
+ heimdallProcess.waitForStarted(3000);
+
+ // OS X was playing up and not finding heimdall, so we're manually checking the PATH.
+ if (heimdallFailed)
+ {
+ QStringList environment = QProcess::systemEnvironment();
+
+ QStringList paths;
+
+ // Ensure /usr/local/bin and /usr/bin are in PATH.
+ for (int i = 0; i < environment.length(); i++)
+ {
+ if (environment[i].left(5) == "PATH=")
+ {
+ paths = environment[i].mid(5).split(':');
+
+ if (!paths.contains("/usr/local/bin"))
+ paths.prepend("/usr/local/bin");
+
+ if (!paths.contains("/usr/bin"))
+ paths.prepend("/usr/bin");
+
+ break;
+ }
+ }
+
+ int pathIndex = -1;
+
+ while (heimdallFailed && ++pathIndex < paths.length())
+ {
+ QString heimdallPath = paths[pathIndex];
+
+ if (heimdallPath.length() > 0)
+ {
+ utilityOutputPlainTextEdit->clear();
+ heimdallFailed = false;
+
+ if (heimdallPath[heimdallPath.length() - 1] != QDir::separator())
+ heimdallPath += QDir::separator();
+
+ heimdallPath += "heimdall";
+
+ heimdallProcess.start(heimdallPath, arguments);
+ heimdallProcess.waitForStarted(3000);
+ }
+ }
+
+ if (heimdallFailed)
+ {
+ flashLabel->setText("Failed to start Heimdall!");
+
+ heimdallState = HeimdallState::Stopped;
+ UpdateInterfaceAvailability();
+ }
+ }
+}
+
+void MainWindow::UpdateUnusedPartitionIds(void)
+{
+ unusedPartitionIds.clear();
+
+ // Initially populate unusedPartitionIds with all possible partition IDs.
+ for (unsigned int i = 0; i < currentPitData.GetEntryCount(); i++)
+ {
+ const PitEntry *pitEntry = currentPitData.GetEntry(i);
+
+ if (pitEntry->IsFlashable() && strcmp(pitEntry->GetPartitionName(), "PIT") != 0 && strcmp(pitEntry->GetPartitionName(), "PT") != 0)
+ unusedPartitionIds.append(pitEntry->GetIdentifier());
+ }
+
+ // Remove any used partition IDs from unusedPartitionIds
+ QList<FileInfo>& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos();
+
+ for (int i = 0; i < fileList.length(); i++)
+ unusedPartitionIds.removeOne(fileList[i].GetPartitionId());
+}
+
+bool MainWindow::ReadPit(QFile *file)
+{
+ if(!file->open(QIODevice::ReadOnly))
+ return (false);
+
+ unsigned char *buffer = new unsigned char[file->size()];
+
+ file->read(reinterpret_cast<char *>(buffer), file->size());
+ file->close();
+
+ bool success = currentPitData.Unpack(buffer);
+ delete buffer;
+
+ if (!success)
+ currentPitData.Clear();
+
+ return (success);
+}
+
+void MainWindow::UpdatePackageUserInterface(void)
+{
+ supportedDevicesListWidget->clear();
+ includedFilesListWidget->clear();
+
+ if (loadedPackageData.IsCleared())
+ {
+ // Package Interface
+ firmwareNameLineEdit->clear();
+ versionLineEdit->clear();
+
+ developerNamesLineEdit->clear();
+
+ platformLineEdit->clear();
+
+ repartitionRadioButton->setChecked(false);
+ noRebootRadioButton->setChecked(false);
+ }
+ else
+ {
+ firmwareNameLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetName());
+ versionLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetVersion());
+
+ QString developerNames;
+
+ if (!loadedPackageData.GetFirmwareInfo().GetDevelopers().isEmpty())
+ {
+ developerNames = loadedPackageData.GetFirmwareInfo().GetDevelopers()[0];
+ for (int i = 1; i < loadedPackageData.GetFirmwareInfo().GetDevelopers().length(); i++)
+ developerNames += ", " + loadedPackageData.GetFirmwareInfo().GetDevelopers()[i];
+ }
+
+ developerNamesLineEdit->setText(developerNames);
+
+ platformLineEdit->setText(loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetName() + " ("
+ + loadedPackageData.GetFirmwareInfo().GetPlatformInfo().GetVersion() + ")");
+
+ for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetDeviceInfos().length(); i++)
+ {
+ const DeviceInfo& deviceInfo = loadedPackageData.GetFirmwareInfo().GetDeviceInfos()[i];
+ supportedDevicesListWidget->addItem(deviceInfo.GetManufacturer() + " " + deviceInfo.GetName() + ": " + deviceInfo.GetProduct());
+ }
+
+ for (int i = 0; i < loadedPackageData.GetFirmwareInfo().GetFileInfos().length(); i++)
+ {
+ const FileInfo& fileInfo = loadedPackageData.GetFirmwareInfo().GetFileInfos()[i];
+ includedFilesListWidget->addItem(fileInfo.GetFilename());
+ }
+
+ repartitionRadioButton->setChecked(loadedPackageData.GetFirmwareInfo().GetRepartition());
+ noRebootRadioButton->setChecked(loadedPackageData.GetFirmwareInfo().GetNoReboot());
+ }
+
+ UpdateLoadPackageInterfaceAvailability();
+}
+
+bool MainWindow::IsArchive(QString path)
+{
+ // Not a real check but hopefully it gets the message across, don't directly flash archives!
+ return (path.endsWith(".tar", Qt::CaseInsensitive) || path.endsWith(".gz", Qt::CaseInsensitive) || path.endsWith(".zip", Qt::CaseInsensitive)
+ || path.endsWith(".bz2", Qt::CaseInsensitive) || path.endsWith(".7z", Qt::CaseInsensitive) || path.endsWith(".rar", Qt::CaseInsensitive));
+}
+
+QString MainWindow::PromptFileSelection(const QString& caption, const QString& filter)
+{
+ QString path = QFileDialog::getOpenFileName(this, caption, lastDirectory, filter);
+
+ if (path != "")
+ lastDirectory = path.left(path.lastIndexOf('/') + 1);
+
+ return (path);
+}
+
+QString MainWindow::PromptFileCreation(const QString& caption, const QString& filter)
+{
+ QString path = QFileDialog::getSaveFileName(this, caption, lastDirectory, filter);
+
+ if (path != "")
+ lastDirectory = path.left(path.lastIndexOf('/') + 1);
+
+ return (path);
+}
+
+void MainWindow::UpdateLoadPackageInterfaceAvailability(void)
+{
+ if (loadedPackageData.IsCleared())
+ {
+ developerHomepageButton->setEnabled(false);
+ developerDonateButton->setEnabled(false);
+
+ loadFirmwareButton->setEnabled(false);
+ }
+ else
+ {
+ if (!loadedPackageData.GetFirmwareInfo().GetUrl().isEmpty())
+ developerHomepageButton->setEnabled(true);
+ else
+ developerHomepageButton->setEnabled(false);
+
+ if (!loadedPackageData.GetFirmwareInfo().GetDonateUrl().isEmpty())
+ developerDonateButton->setEnabled(true);
+ else
+ developerDonateButton->setEnabled(false);
+
+ loadFirmwareButton->setEnabled(heimdallState == HeimdallState::Stopped);
+ }
+}
+
+void MainWindow::UpdateFlashInterfaceAvailability(void)
+{
+ if (heimdallState == HeimdallState::Stopped)
+ {
+ partitionNameComboBox->setEnabled(partitionsListWidget->currentRow() >= 0);
+
+ bool allPartitionsValid = true;
+ QList<FileInfo>& fileList = workingPackageData.GetFirmwareInfo().GetFileInfos();
+
+ for (int i = 0; i < fileList.length(); i++)
+ {
+ if (fileList[i].GetFilename().isEmpty())
+ {
+ allPartitionsValid = false;
+ break;
+ }
+ }
+
+ bool validFlashSettings = allPartitionsValid && fileList.length() > 0;
+
+ flashProgressBar->setEnabled(false);
+ optionsGroup->setEnabled(true);
+ sessionGroup->setEnabled(true);
+ startFlashButton->setEnabled(validFlashSettings);
+ noRebootCheckBox->setEnabled(validFlashSettings);
+ resumeCheckbox->setEnabled(validFlashSettings);
+ }
+ else
+ {
+ partitionNameComboBox->setEnabled(false);
+
+ flashProgressBar->setEnabled(true);
+ optionsGroup->setEnabled(false);
+ sessionGroup->setEnabled(false);
+ }
+}
+
+void MainWindow::UpdateCreatePackageInterfaceAvailability(void)
+{
+ if (heimdallState == HeimdallState::Stopped)
+ {
+ const FirmwareInfo& firmwareInfo = workingPackageData.GetFirmwareInfo();
+
+ if (firmwareInfo.GetName().isEmpty() || firmwareInfo.GetVersion().isEmpty() || firmwareInfo.GetPlatformInfo().GetName().isEmpty()
+ || firmwareInfo.GetPlatformInfo().GetVersion().isEmpty() || firmwareInfo.GetDevelopers().isEmpty() || firmwareInfo.GetDeviceInfos().isEmpty())
+ {
+ buildPackageButton->setEnabled(false);
+ }
+ else
+ {
+ buildPackageButton->setEnabled(true);
+ }
+
+ if (addDeveloperButton->text().isEmpty())
+ addDeveloperButton->setEnabled(false);
+ else
+ addDeveloperButton->setEnabled(true);
+
+ if (createDevelopersListWidget->currentRow() >= 0)
+ removeDeveloperButton->setEnabled(true);
+ else
+ removeDeveloperButton->setEnabled(false);
+ }
+ else
+ {
+ buildPackageButton->setEnabled(false);
+ }
+}
+
+void MainWindow::UpdateUtilitiesInterfaceAvailability(void)
+{
+ if (heimdallState == HeimdallState::Stopped)
+ {
+ detectDeviceButton->setEnabled(true);
+ closePcScreenButton->setEnabled(true);
+ pitSaveAsButton->setEnabled(true);
+
+ downloadPitButton->setEnabled(!pitDestinationLineEdit->text().isEmpty());
+
+ if (printPitDeviceRadioBox->isChecked())
+ {
+ // Device
+ printLocalPitGroup->setEnabled(false);
+ printPitButton->setEnabled(true);
+ }
+ else
+ {
+ // Local File
+ printLocalPitGroup->setEnabled(true);
+ printLocalPitLineEdit->setEnabled(true);
+ printLocalPitBrowseButton->setEnabled(true);
+
+ printPitButton->setEnabled(!printLocalPitLineEdit->text().isEmpty());
+ }
+ }
+ else
+ {
+ detectDeviceButton->setEnabled(false);
+ closePcScreenButton->setEnabled(false);
+ pitSaveAsButton->setEnabled(false);
+ downloadPitButton->setEnabled(false);
+
+ printLocalPitGroup->setEnabled(false);
+ printPitButton->setEnabled(false);
+ }
+}
+
+void MainWindow::UpdateInterfaceAvailability(void)
+{
+ UpdateLoadPackageInterfaceAvailability();
+ UpdateFlashInterfaceAvailability();
+ UpdateCreatePackageInterfaceAvailability();
+ UpdateUtilitiesInterfaceAvailability();
+
+ if (heimdallState == HeimdallState::Stopped)
+ {
+ // Enable/disable tabs
+
+ for (int i = 0; i < functionTabWidget->count(); i++)
+ functionTabWidget->setTabEnabled(i, true);
+
+ functionTabWidget->setTabEnabled(functionTabWidget->indexOf(createPackageTab), startFlashButton->isEnabled());
+ }
+ else
+ {
+ // Disable non-current tabs
+
+ for (int i = 0; i < functionTabWidget->count(); i++)
+ {
+ if (i == functionTabWidget->currentIndex())
+ functionTabWidget->setTabEnabled(i, true);
+ else
+ functionTabWidget->setTabEnabled(i, false);
+ }
+ }
+}
+
+void MainWindow::UpdatePartitionNamesInterface(void)
+{
+ populatingPartitionNames = true;
+
+ partitionNameComboBox->clear();
+
+ int partitionsListWidgetRow = partitionsListWidget->currentRow();
+
+ if (partitionsListWidgetRow >= 0)
+ {
+ const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];
+
+ for (int i = 0; i < unusedPartitionIds.length(); i++)
+ partitionNameComboBox->addItem(currentPitData.FindEntry(unusedPartitionIds[i])->GetPartitionName());
+
+ partitionNameComboBox->addItem(currentPitData.FindEntry(partitionInfo.GetPartitionId())->GetPartitionName());
+ partitionNameComboBox->setCurrentIndex(unusedPartitionIds.length());
+ }
+
+ populatingPartitionNames = false;
+
+ UpdateFlashInterfaceAvailability();
+}
+
+MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
+{
+ setupUi(this);
+
+ heimdallState = HeimdallState::Stopped;
+
+ lastDirectory = QDir::toNativeSeparators(QApplication::applicationDirPath());
+
+ populatingPartitionNames = false;
+
+ verboseOutput = false;
+ resume = false;
+
+ tabIndex = functionTabWidget->currentIndex();
+ functionTabWidget->setTabEnabled(functionTabWidget->indexOf(createPackageTab), false);
+
+ QObject::connect(functionTabWidget, SIGNAL(currentChanged(int)), this, SLOT(FunctionTabChanged(int)));
+
+ // Menu
+ QObject::connect(actionDonate, SIGNAL(triggered()), this, SLOT(OpenDonationWebpage()));
+ QObject::connect(actionVerboseOutput, SIGNAL(toggled(bool)), this, SLOT(SetVerboseOutput(bool)));
+ QObject::connect(actionResumeConnection, SIGNAL(toggled(bool)), this, SLOT(SetResume(bool)));
+ QObject::connect(actionAboutHeimdall, SIGNAL(triggered()), this, SLOT(ShowAbout()));
+
+ // Load Package Tab
+ QObject::connect(browseFirmwarePackageButton, SIGNAL(clicked()), this, SLOT(SelectFirmwarePackage()));
+ QObject::connect(developerHomepageButton, SIGNAL(clicked()), this, SLOT(OpenDeveloperHomepage()));
+ QObject::connect(developerDonateButton, SIGNAL(clicked()), this, SLOT(OpenDeveloperDonationWebpage()));
+ QObject::connect(loadFirmwareButton, SIGNAL(clicked()), this, SLOT(LoadFirmwarePackage()));
+
+ QObject::connect(partitionsListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectPartition(int)));
+ QObject::connect(addPartitionButton, SIGNAL(clicked()), this, SLOT(AddPartition()));
+ QObject::connect(removePartitionButton, SIGNAL(clicked()), this, SLOT(RemovePartition()));
+
+ // Flash Tab
+ QObject::connect(partitionNameComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(SelectPartitionName(int)));
+ QObject::connect(partitionFileBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPartitionFile()));
+
+ QObject::connect(pitBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPit()));
+
+ QObject::connect(repartitionCheckBox, SIGNAL(stateChanged(int)), this, SLOT(SetRepartition(int)));
+
+ QObject::connect(noRebootCheckBox, SIGNAL(stateChanged(int)), this, SLOT(SetNoReboot(int)));
+ QObject::connect(resumeCheckbox, SIGNAL(stateChanged(int)), this, SLOT(SetResume(int)));
+
+ QObject::connect(startFlashButton, SIGNAL(clicked()), this, SLOT(StartFlash()));
+
+ // Create Package Tab
+ QObject::connect(createFirmwareNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(FirmwareNameChanged(const QString&)));
+ QObject::connect(createFirmwareVersionLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(FirmwareVersionChanged(const QString&)));
+ QObject::connect(createPlatformNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(PlatformNameChanged(const QString&)));
+ QObject::connect(createPlatformVersionLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(PlatformVersionChanged(const QString&)));
+
+ QObject::connect(createHomepageLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(HomepageUrlChanged(const QString&)));
+ QObject::connect(createDonateLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DonateUrlChanged(const QString&)));
+
+ QObject::connect(createDevelopersListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectDeveloper(int)));
+ QObject::connect(createDeveloperNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeveloperNameChanged(const QString&)));
+ QObject::connect(addDeveloperButton, SIGNAL(clicked()), this, SLOT(AddDeveloper()));
+ QObject::connect(removeDeveloperButton, SIGNAL(clicked()), this, SLOT(RemoveDeveloper()));
+
+ QObject::connect(createDevicesListWidget, SIGNAL(currentRowChanged(int)), this, SLOT(SelectDevice(int)));
+ QObject::connect(deviceManufacturerLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
+ QObject::connect(deviceNameLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
+ QObject::connect(deviceProductCodeLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(DeviceInfoChanged(const QString&)));
+ QObject::connect(addDeviceButton, SIGNAL(clicked()), this, SLOT(AddDevice()));
+ QObject::connect(removeDeviceButton, SIGNAL(clicked()), this, SLOT(RemoveDevice()));
+
+ QObject::connect(buildPackageButton, SIGNAL(clicked()), this, SLOT(BuildPackage()));
+
+ // Utilities Tab
+ QObject::connect(detectDeviceButton, SIGNAL(clicked()), this, SLOT(DetectDevice()));
+
+ QObject::connect(closePcScreenButton, SIGNAL(clicked()), this, SLOT(ClosePcScreen()));
+
+ QObject::connect(printPitDeviceRadioBox, SIGNAL(toggled(bool)), this, SLOT(DevicePrintPitToggled(bool)));
+ QObject::connect(printPitLocalFileRadioBox, SIGNAL(toggled(bool)), this, SLOT(LocalFilePrintPitToggled(bool)));
+ QObject::connect(printLocalPitBrowseButton, SIGNAL(clicked()), this, SLOT(SelectPrintPitFile()));
+ QObject::connect(printPitButton, SIGNAL(clicked()), this, SLOT(PrintPit()));
+
+ QObject::connect(pitSaveAsButton, SIGNAL(clicked()), this, SLOT(SelectPitDestination()));
+ QObject::connect(downloadPitButton, SIGNAL(clicked()), this, SLOT(DownloadPit()));
+
+ // Heimdall Command Line
+ QObject::connect(&heimdallProcess, SIGNAL(readyRead()), this, SLOT(HandleHeimdallStdout()));
+ QObject::connect(&heimdallProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(HandleHeimdallReturned(int, QProcess::ExitStatus)));
+ QObject::connect(&heimdallProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(HandleHeimdallError(QProcess::ProcessError)));
+}
+
+MainWindow::~MainWindow()
+{
+}
+
+void MainWindow::OpenDonationWebpage(void)
+{
+ QDesktopServices::openUrl(QUrl("http://www.glassechidna.com.au/donate/", QUrl::StrictMode));
+}
+
+void MainWindow::SetVerboseOutput(bool enabled)
+{
+ verboseOutput = enabled;
+}
+
+void MainWindow::ShowAbout(void)
+{
+ aboutForm.show();
+}
+
+void MainWindow::FunctionTabChanged(int index)
+{
+ tabIndex = index;
+ deviceDetectedRadioButton->setChecked(false);
+}
+
+void MainWindow::SelectFirmwarePackage(void)
+{
+ loadedPackageData.Clear();
+ UpdatePackageUserInterface();
+
+ QString path = PromptFileSelection("Select Package", "*.tar.gz");
+ firmwarePackageLineEdit->setText(path);
+
+ if (firmwarePackageLineEdit->text() != "")
+ {
+ if (Packaging::ExtractPackage(firmwarePackageLineEdit->text(), &loadedPackageData))
+ UpdatePackageUserInterface();
+ else
+ loadedPackageData.Clear();
+ }
+}
+
+void MainWindow::OpenDeveloperHomepage(void)
+{
+ if(!QDesktopServices::openUrl(QUrl(loadedPackageData.GetFirmwareInfo().GetUrl(), QUrl::TolerantMode)))
+ Alerts::DisplayWarning(QString("Cannot open invalid URL:\n%1").arg(loadedPackageData.GetFirmwareInfo().GetUrl()));
+}
+
+void MainWindow::OpenDeveloperDonationWebpage(void)
+{
+ if (!QDesktopServices::openUrl(QUrl(loadedPackageData.GetFirmwareInfo().GetDonateUrl(), QUrl::TolerantMode)))
+ Alerts::DisplayWarning(QString("Cannot open invalid URL:\n%1").arg(loadedPackageData.GetFirmwareInfo().GetDonateUrl()));
+}
+
+void MainWindow::LoadFirmwarePackage(void)
+{
+ workingPackageData.Clear();
+ currentPitData.Clear();
+
+ workingPackageData.GetFiles().append(loadedPackageData.GetFiles());
+ loadedPackageData.RemoveAllFiles();
+
+ const QList<FileInfo> packageFileInfos = loadedPackageData.GetFirmwareInfo().GetFileInfos();
+
+ for (int i = 0; i < packageFileInfos.length(); i++)
+ {
+ bool fileFound = false;
+
+ for (int j = 0; j < workingPackageData.GetFiles().length(); j++)
+ {
+ if (workingPackageData.GetFiles()[j]->fileTemplate() == ("XXXXXX-" + packageFileInfos[i].GetFilename()))
+ {
+ FileInfo partitionInfo(packageFileInfos[i].GetPartitionId(), QDir::current().absoluteFilePath(workingPackageData.GetFiles()[j]->fileName()));
+ workingPackageData.GetFirmwareInfo().GetFileInfos().append(partitionInfo);
+
+ fileFound = true;
+ break;
+ }
+ }
+
+ if (!fileFound)
+ Alerts::DisplayWarning(QString("%1 is missing from the package.").arg(packageFileInfos[i].GetFilename()));
+ }
+
+ // Find the PIT file and read it
+ for (int i = 0; i < workingPackageData.GetFiles().length(); i++)
+ {
+ QTemporaryFile *file = workingPackageData.GetFiles()[i];
+
+ if (file->fileTemplate() == ("XXXXXX-" + loadedPackageData.GetFirmwareInfo().GetPitFilename()))
+ {
+ workingPackageData.GetFirmwareInfo().SetPitFilename(QDir::current().absoluteFilePath(file->fileName()));
+
+ if (!ReadPit(file))
+ {
+ Alerts::DisplayError("Failed to read PIT file.");
+
+ loadedPackageData.Clear();
+ UpdatePackageUserInterface();
+
+ workingPackageData.Clear();
+ UpdateUnusedPartitionIds();
+ return;
+ }
+
+ break;
+ }
+ }
+
+ UpdateUnusedPartitionIds();
+ workingPackageData.GetFirmwareInfo().SetRepartition(loadedPackageData.GetFirmwareInfo().GetRepartition());
+ workingPackageData.GetFirmwareInfo().SetNoReboot(loadedPackageData.GetFirmwareInfo().GetNoReboot());
+
+ loadedPackageData.Clear();
+ UpdatePackageUserInterface();
+ firmwarePackageLineEdit->clear();
+
+ partitionsListWidget->clear();
+
+ // Populate partitionsListWidget with partition names (from the PIT file)
+ for (int i = 0; i < workingPackageData.GetFirmwareInfo().GetFileInfos().length(); i++)
+ {
+ const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[i];
+
+ const PitEntry *pitEntry = currentPitData.FindEntry(partitionInfo.GetPartitionId());
+
+ if (pitEntry)
+ {
+ partitionsListWidget->addItem(pitEntry->GetPartitionName());
+ }
+ else
+ {
+ Alerts::DisplayError("Firmware package includes invalid partition IDs.");
+
+ loadedPackageData.GetFirmwareInfo().Clear();
+ currentPitData.Clear();
+ UpdateUnusedPartitionIds();
+
+ partitionsListWidget->clear();
+ return;
+ }
+ }
+
+ partitionNameComboBox->clear();
+ partitionIdLineEdit->clear();
+ partitionFileLineEdit->clear();
+ partitionFileBrowseButton->setEnabled(false);
+
+ repartitionCheckBox->setEnabled(true);
+ repartitionCheckBox->setChecked(workingPackageData.GetFirmwareInfo().GetRepartition());
+ noRebootCheckBox->setEnabled(true);
+ noRebootCheckBox->setChecked(workingPackageData.GetFirmwareInfo().GetNoReboot());
+
+ partitionsListWidget->setEnabled(true);
+ addPartitionButton->setEnabled(true);
+ removePartitionButton->setEnabled(true && partitionsListWidget->currentRow() >= 0);
+
+ pitLineEdit->setText(workingPackageData.GetFirmwareInfo().GetPitFilename());
+
+ functionTabWidget->setCurrentWidget(flashTab);
+
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::SelectPartitionName(int index)
+{
+ if (!populatingPartitionNames && index != -1 && index != unusedPartitionIds.length())
+ {
+ unsigned int newPartitionIndex = unusedPartitionIds[index];
+ unusedPartitionIds.removeAt(index);
+
+ FileInfo& fileInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];
+ unusedPartitionIds.append(fileInfo.GetPartitionId());
+ fileInfo.SetPartitionId(newPartitionIndex);
+
+ PitEntry *pitEntry = currentPitData.FindEntry(newPartitionIndex);
+
+ QString title("File");
+
+ if (pitEntry && strlen(pitEntry->GetFlashFilename()) > 0)
+ title += " (" + QString(pitEntry->GetFlashFilename()) + ")";
+
+ partitionFileGroup->setTitle(title);
+
+ if (!fileInfo.GetFilename().isEmpty())
+ {
+ QString partitionFilename = pitEntry->GetFlashFilename();
+ int lastPeriod = partitionFilename.lastIndexOf(QChar('.'));
+
+ if (lastPeriod >= 0)
+ {
+ QString partitionFileExtension = partitionFilename.mid(lastPeriod + 1);
+
+ lastPeriod = fileInfo.GetFilename().lastIndexOf(QChar('.'));
+
+ if (lastPeriod < 0 || fileInfo.GetFilename().mid(lastPeriod + 1) != partitionFileExtension)
+ Alerts::DisplayWarning(QString("%1 partition expects files with file extension \"%2\".").arg(pitEntry->GetPartitionName(), partitionFileExtension));
+ }
+ }
+
+ partitionNameComboBox->clear();
+
+ // Update interface
+ UpdatePartitionNamesInterface();
+ partitionIdLineEdit->setText(QString::number(newPartitionIndex));
+ partitionsListWidget->currentItem()->setText(currentPitData.FindEntry(newPartitionIndex)->GetPartitionName());
+ }
+}
+
+void MainWindow::SelectPartitionFile(void)
+{
+ QString path = PromptFileSelection();
+
+ if (path != "")
+ {
+ FileInfo& fileInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[partitionsListWidget->currentRow()];
+ PitEntry *pitEntry = currentPitData.FindEntry(fileInfo.GetPartitionId());
+
+ QString partitionFilename = pitEntry->GetFlashFilename();
+ int lastPeriod = partitionFilename.lastIndexOf(QChar('.'));
+
+ if (lastPeriod >= 0)
+ {
+ QString partitionFileExtension = partitionFilename.mid(lastPeriod + 1);
+
+ lastPeriod = path.lastIndexOf(QChar('.'));
+
+ if (lastPeriod < 0 || path.mid(lastPeriod + 1) != partitionFileExtension)
+ Alerts::DisplayWarning(QString("%1 partition expects files with file extension \"%2\".").arg(pitEntry->GetPartitionName(), partitionFileExtension));
+ }
+
+ fileInfo.SetFilename(path);
+ partitionFileLineEdit->setText(path);
+
+ pitBrowseButton->setEnabled(true);
+ partitionsListWidget->setEnabled(true);
+ UpdateInterfaceAvailability();
+
+ if (unusedPartitionIds.length() > 0)
+ addPartitionButton->setEnabled(true);
+ }
+}
+
+void MainWindow::SelectPartition(int row)
+{
+ if (row >= 0)
+ {
+ const FileInfo& partitionInfo = workingPackageData.GetFirmwareInfo().GetFileInfos()[row];
+
+ UpdatePartitionNamesInterface();
+
+ partitionIdLineEdit->setText(QString::number(partitionInfo.GetPartitionId()));
+ partitionFileLineEdit->setText(partitionInfo.GetFilename());
+ partitionFileBrowseButton->setEnabled(true);
+
+ removePartitionButton->setEnabled(true);
+
+ QString title("File");
+
+ PitEntry *pitEntry = currentPitData.FindEntry(partitionInfo.GetPartitionId());
+
+ if (pitEntry && strlen(pitEntry->GetFlashFilename()) > 0)
+ title += " (" + QString(pitEntry->GetFlashFilename()) + ")";
+
+ partitionFileGroup->setTitle(title);
+ }
+ else
+ {
+ UpdatePartitionNamesInterface();
+
+ partitionIdLineEdit->clear();
+ partitionFileLineEdit->clear();
+ partitionFileBrowseButton->setEnabled(false);
+
+ removePartitionButton->setEnabled(false);
+
+ partitionFileGroup->setTitle("File");
+ }
+}
+
+void MainWindow::AddPartition(void)
+{
+ FileInfo partitionInfo(unusedPartitionIds.first(), "");
+ workingPackageData.GetFirmwareInfo().GetFileInfos().append(partitionInfo);
+ UpdateUnusedPartitionIds();
+
+ pitBrowseButton->setEnabled(false);
+ addPartitionButton->setEnabled(false);
+
+ partitionsListWidget->addItem(currentPitData.FindEntry(partitionInfo.GetPartitionId())->GetPartitionName());
+ partitionsListWidget->setCurrentRow(partitionsListWidget->count() - 1);
+ partitionsListWidget->setEnabled(false);
+
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::RemovePartition(void)
+{
+ workingPackageData.GetFirmwareInfo().GetFileInfos().removeAt(partitionsListWidget->currentRow());
+ UpdateUnusedPartitionIds();
+
+ QListWidgetItem *item = partitionsListWidget->currentItem();
+ partitionsListWidget->setCurrentRow(-1);
+ delete item;
+
+ pitBrowseButton->setEnabled(true);
+ addPartitionButton->setEnabled(true);
+ partitionsListWidget->setEnabled(true);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::SelectPit(void)
+{
+ QString path = PromptFileSelection("Select PIT", "*.pit");
+ bool validPit = path != "";
+
+ if (validPit)
+ {
+ // In order to map files in the old PIT to file in the new one, we first must use partition names instead of IDs.
+ QList<FileInfo> fileInfos = workingPackageData.GetFirmwareInfo().GetFileInfos();
+
+ int partitionNamesCount = fileInfos.length();
+ QString *partitionNames = new QString[fileInfos.length()];
+ for (int i = 0; i < fileInfos.length(); i++)
+ partitionNames[i] = currentPitData.FindEntry(fileInfos[i].GetPartitionId())->GetPartitionName();
+
+ currentPitData.Clear();
+
+ QFile pitFile(path);
+
+ if (ReadPit(&pitFile))
+ {
+ workingPackageData.GetFirmwareInfo().SetPitFilename(path);
+
+ partitionsListWidget->clear();
+ int partitionInfoIndex = 0;
+
+ for (int i = 0; i < partitionNamesCount; i++)
+ {
+ const PitEntry *pitEntry = currentPitData.FindEntry(partitionNames[i].toLatin1().constData());
+
+ if (pitEntry)
+ {
+ fileInfos[partitionInfoIndex++].SetPartitionId(pitEntry->GetIdentifier());
+ partitionsListWidget->addItem(pitEntry->GetPartitionName());
+ }
+ else
+ {
+ fileInfos.removeAt(partitionInfoIndex);
+ }
+ }
+ }
+ else
+ {
+ validPit = false;
+ }
+
+ // If the selected PIT was invalid, attempt to reload the old one.
+ if (!validPit)
+ {
+ Alerts::DisplayError("The file selected was not a valid PIT file.");
+
+ if (!workingPackageData.GetFirmwareInfo().GetPitFilename().isEmpty())
+ {
+ QFile originalPitFile(workingPackageData.GetFirmwareInfo().GetPitFilename());
+
+ if (ReadPit(&originalPitFile))
+ {
+ validPit = true;
+ }
+ else
+ {
+ Alerts::DisplayError("Failed to reload working PIT data.");
+
+ workingPackageData.Clear();
+ partitionsListWidget->clear();
+ }
+ }
+ }
+
+ UpdateUnusedPartitionIds();
+
+ delete [] partitionNames;
+
+ pitLineEdit->setText(workingPackageData.GetFirmwareInfo().GetPitFilename());
+
+ repartitionCheckBox->setEnabled(validPit);
+ noRebootCheckBox->setEnabled(validPit);
+ partitionsListWidget->setEnabled(validPit);
+
+ addPartitionButton->setEnabled(validPit);
+ removePartitionButton->setEnabled(validPit && partitionsListWidget->currentRow() >= 0);
+
+ UpdateInterfaceAvailability();
+ }
+}
+
+
+void MainWindow::SetRepartition(int enabled)
+{
+ workingPackageData.GetFirmwareInfo().SetRepartition(enabled);
+
+ repartitionCheckBox->setChecked(enabled);
+}
+
+void MainWindow::SetNoReboot(int enabled)
+{
+ workingPackageData.GetFirmwareInfo().SetNoReboot(enabled);
+
+ noRebootCheckBox->setChecked(enabled);
+}
+
+void MainWindow::SetResume(bool enabled)
+{
+ resume = enabled;
+
+ actionResumeConnection->setChecked(enabled);
+ resumeCheckbox->setChecked(enabled);
+}
+
+void MainWindow::SetResume(int enabled)
+{
+ SetResume(enabled != 0);
+}
+
+void MainWindow::StartFlash(void)
+{
+ outputPlainTextEdit->clear();
+
+ heimdallState = HeimdallState::Flashing;
+ heimdallFailed = false;
+
+ const FirmwareInfo& firmwareInfo = workingPackageData.GetFirmwareInfo();
+ const QList<FileInfo>& fileInfos = firmwareInfo.GetFileInfos();
+
+ QStringList arguments;
+ arguments.append("flash");
+
+ if (firmwareInfo.GetRepartition())
+ arguments.append("--repartition");
+
+ arguments.append("--pit");
+ arguments.append(firmwareInfo.GetPitFilename());
+
+ for (int i = 0; i < fileInfos.length(); i++)
+ {
+ QString flag;
+ flag.sprintf("--%u", fileInfos[i].GetPartitionId());
+
+ arguments.append(flag);
+ arguments.append(fileInfos[i].GetFilename());
+ }
+
+ if (firmwareInfo.GetNoReboot())
+ arguments.append("--no-reboot");
+
+ if (resume)
+ arguments.append("--resume");
+
+ if (verboseOutput)
+ arguments.append("--verbose");
+
+ arguments.append("--stdout-errors");
+
+ StartHeimdall(arguments);
+}
+
+void MainWindow::FirmwareNameChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().SetName(text);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::FirmwareVersionChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().SetVersion(text);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::PlatformNameChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().GetPlatformInfo().SetName(text);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::PlatformVersionChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().GetPlatformInfo().SetVersion(text);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::HomepageUrlChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().SetUrl(text);
+}
+
+void MainWindow::DonateUrlChanged(const QString& text)
+{
+ workingPackageData.GetFirmwareInfo().SetDonateUrl(text);
+}
+
+void MainWindow::DeveloperNameChanged(const QString& text)
+{
+ UNUSED(text);
+
+ UpdateCreatePackageInterfaceAvailability();
+}
+
+void MainWindow::SelectDeveloper(int row)
+{
+ UNUSED(row);
+
+ UpdateCreatePackageInterfaceAvailability();
+}
+
+void MainWindow::AddDeveloper(void)
+{
+ workingPackageData.GetFirmwareInfo().GetDevelopers().append(createDeveloperNameLineEdit->text());
+
+ createDevelopersListWidget->addItem(createDeveloperNameLineEdit->text());
+ createDeveloperNameLineEdit->clear();
+
+ UpdateCreatePackageInterfaceAvailability();
+}
+
+void MainWindow::RemoveDeveloper(void)
+{
+ workingPackageData.GetFirmwareInfo().GetDevelopers().removeAt(createDevelopersListWidget->currentRow());
+
+ QListWidgetItem *item = createDevelopersListWidget->currentItem();
+ createDevelopersListWidget->setCurrentRow(-1);
+ delete item;
+
+ removeDeveloperButton->setEnabled(false);
+
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::DeviceInfoChanged(const QString& text)
+{
+ UNUSED(text);
+
+ if (deviceManufacturerLineEdit->text().isEmpty() || deviceNameLineEdit->text().isEmpty() || deviceProductCodeLineEdit->text().isEmpty())
+ addDeviceButton->setEnabled(false);
+ else
+ addDeviceButton->setEnabled(true);
+}
+
+void MainWindow::SelectDevice(int row)
+{
+ if (row >= 0)
+ removeDeviceButton->setEnabled(true);
+ else
+ removeDeviceButton->setEnabled(false);
+}
+
+void MainWindow::AddDevice(void)
+{
+ workingPackageData.GetFirmwareInfo().GetDeviceInfos().append(DeviceInfo(deviceManufacturerLineEdit->text(), deviceProductCodeLineEdit->text(),
+ deviceNameLineEdit->text()));
+
+ createDevicesListWidget->addItem(deviceManufacturerLineEdit->text() + " " + deviceNameLineEdit->text() + ": " + deviceProductCodeLineEdit->text());
+ deviceManufacturerLineEdit->clear();
+ deviceNameLineEdit->clear();
+ deviceProductCodeLineEdit->clear();
+
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::RemoveDevice(void)
+{
+ workingPackageData.GetFirmwareInfo().GetDeviceInfos().removeAt(createDevicesListWidget->currentRow());
+
+ QListWidgetItem *item = createDevicesListWidget->currentItem();
+ createDevicesListWidget->setCurrentRow(-1);
+ delete item;
+
+ removeDeviceButton->setEnabled(false);
+
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::BuildPackage(void)
+{
+ QString packagePath = PromptFileCreation("Save Package", "*.tar.gz");
+
+ if (!packagePath.isEmpty())
+ {
+ if (!packagePath.endsWith(".tar.gz", Qt::CaseInsensitive))
+ {
+ if (packagePath.endsWith(".tar", Qt::CaseInsensitive))
+ packagePath.append(".gz");
+ else if (packagePath.endsWith(".gz", Qt::CaseInsensitive))
+ packagePath.replace(packagePath.length() - 3, ".tar.gz");
+ else if (packagePath.endsWith(".tgz", Qt::CaseInsensitive))
+ packagePath.replace(packagePath.length() - 4, ".tar.gz");
+ else
+ packagePath.append(".tar.gz");
+ }
+
+ Packaging::BuildPackage(packagePath, workingPackageData.GetFirmwareInfo());
+ }
+}
+
+void MainWindow::DetectDevice(void)
+{
+ deviceDetectedRadioButton->setChecked(false);
+ utilityOutputPlainTextEdit->clear();
+
+ heimdallState = HeimdallState::DetectingDevice;
+ heimdallFailed = false;
+
+ QStringList arguments;
+ arguments.append("detect");
+
+ if (verboseOutput)
+ arguments.append("--verbose");
+
+ arguments.append("--stdout-errors");
+
+ StartHeimdall(arguments);
+}
+
+void MainWindow::ClosePcScreen(void)
+{
+ utilityOutputPlainTextEdit->clear();
+
+ heimdallState = HeimdallState::ClosingPcScreen;
+ heimdallFailed = false;
+
+ QStringList arguments;
+ arguments.append("close-pc-screen");
+
+ if (resume)
+ arguments.append("--resume");
+
+ if (verboseOutput)
+ arguments.append("--verbose");
+
+ arguments.append("--stdout-errors");
+
+ StartHeimdall(arguments);
+}
+
+void MainWindow::SelectPitDestination(void)
+{
+ QString path = PromptFileCreation("Save PIT", "*.pit");
+
+ if (path != "")
+ {
+ if (!path.endsWith(".pit"))
+ path.append(".pit");
+
+ pitDestinationLineEdit->setText(path);
+
+ UpdateInterfaceAvailability();
+ }
+}
+
+void MainWindow::DownloadPit(void)
+{
+ deviceDetectedRadioButton->setChecked(false);
+ utilityOutputPlainTextEdit->clear();
+
+ heimdallState = HeimdallState::DownloadingPit;
+ heimdallFailed = false;
+
+ QStringList arguments;
+ arguments.append("download-pit");
+
+ arguments.append("--output");
+ arguments.append(pitDestinationLineEdit->text());
+
+ arguments.append("--no-reboot");
+
+ if (resume)
+ arguments.append("--resume");
+
+ if (verboseOutput)
+ arguments.append("--verbose");
+
+ arguments.append("--stdout-errors");
+
+ StartHeimdall(arguments);
+}
+
+void MainWindow::DevicePrintPitToggled(bool checked)
+{
+ if (checked)
+ {
+ if (printPitLocalFileRadioBox->isChecked())
+ printPitLocalFileRadioBox->setChecked(false);
+ }
+
+ UpdateUtilitiesInterfaceAvailability();
+}
+
+void MainWindow::LocalFilePrintPitToggled(bool checked)
+{
+ if (checked)
+ {
+ if (printPitDeviceRadioBox->isChecked())
+ printPitDeviceRadioBox->setChecked(false);
+ }
+
+ UpdateUtilitiesInterfaceAvailability();
+}
+
+void MainWindow::SelectPrintPitFile(void)
+{
+ QString path = PromptFileSelection("Select PIT", "*.pit");
+
+ if (path != "")
+ printLocalPitLineEdit->setText(path);
+
+ if (printLocalPitLineEdit->text() != "")
+ printPitButton->setEnabled(true);
+ else
+ printPitButton->setEnabled(false);
+}
+
+void MainWindow::PrintPit(void)
+{
+ utilityOutputPlainTextEdit->clear();
+
+ heimdallState = HeimdallState::PrintingPit;
+ heimdallFailed = false;
+
+ QStringList arguments;
+ arguments.append("print-pit");
+
+ if (printPitLocalFileRadioBox->isChecked())
+ {
+ arguments.append("--file");
+ arguments.append(printLocalPitLineEdit->text());
+ }
+
+ arguments.append("--stdout-errors");
+ arguments.append("--no-reboot");
+
+ if (resume)
+ arguments.append("--resume");
+
+ if (verboseOutput)
+ arguments.append("--verbose");
+
+ StartHeimdall(arguments);
+}
+
+void MainWindow::HandleHeimdallStdout(void)
+{
+ QString output = heimdallProcess.readAll();
+
+ // We often receive multiple lots of output from Heimdall at one time. So we use regular expressions
+ // to ensure we don't miss out on any important information.
+ QRegExp uploadingExp("Uploading [^\n]+\n");
+ if (output.lastIndexOf(uploadingExp) > -1)
+ flashLabel->setText(uploadingExp.cap().left(uploadingExp.cap().length() - 1));
+
+ QRegExp percentExp("[\b\n][0-9]+%");
+ if (output.lastIndexOf(percentExp) > -1)
+ {
+ QString percentString = percentExp.cap();
+ flashProgressBar->setValue(percentString.mid(1, percentString.length() - 2).toInt());
+ }
+
+ output.remove(QChar('\b'));
+ output.replace(QChar('%'), QString("%\n"));
+
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ outputPlainTextEdit->insertPlainText(output);
+ outputPlainTextEdit->ensureCursorVisible();
+ }
+ else
+ {
+ utilityOutputPlainTextEdit->insertPlainText(output);
+ utilityOutputPlainTextEdit->ensureCursorVisible();
+ }
+}
+
+void MainWindow::HandleHeimdallReturned(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ HandleHeimdallStdout();
+
+ if (exitStatus == QProcess::NormalExit && exitCode == 0)
+ {
+ bool executedNoReboot = (heimdallState == HeimdallState::Flashing && loadedPackageData.GetFirmwareInfo().GetNoReboot())
+ || (heimdallState == HeimdallState::PrintingPit && printPitDeviceRadioBox->isChecked()) || heimdallState == HeimdallState::DownloadingPit;
+
+ SetResume(executedNoReboot);
+
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ flashLabel->setText("Flash completed successfully!");
+ }
+ else if (heimdallState == HeimdallState::DetectingDevice)
+ {
+ deviceDetectedRadioButton->setChecked(true);
+ }
+ }
+ else
+ {
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ QString error = heimdallProcess.readAllStandardError();
+
+ int lastNewLineChar = error.lastIndexOf('\n');
+
+ if (lastNewLineChar == 0)
+ error = error.mid(1).remove("ERROR: ");
+ else
+ error = error.left(lastNewLineChar).remove("ERROR: ");
+
+ flashLabel->setText(error);
+ }
+ else if (heimdallState == HeimdallState::DetectingDevice)
+ {
+ deviceDetectedRadioButton->setChecked(false);
+ }
+ }
+
+ heimdallState = HeimdallState::Stopped;
+ flashProgressBar->setEnabled(false);
+ UpdateInterfaceAvailability();
+}
+
+void MainWindow::HandleHeimdallError(QProcess::ProcessError error)
+{
+ if (error == QProcess::FailedToStart || error == QProcess::Timedout)
+ {
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ flashLabel->setText("Failed to start Heimdall!");
+ flashProgressBar->setEnabled(false);
+ }
+ else
+ {
+ utilityOutputPlainTextEdit->setPlainText("\nFRONTEND ERROR: Failed to start Heimdall!");
+ }
+
+ heimdallFailed = true;
+ heimdallState = HeimdallState::Stopped;
+ UpdateInterfaceAvailability();
+ }
+ else if (error == QProcess::Crashed)
+ {
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ flashLabel->setText("Heimdall crashed!");
+ flashProgressBar->setEnabled(false);
+ }
+ else
+ {
+ utilityOutputPlainTextEdit->appendPlainText("\nFRONTEND ERROR: Heimdall crashed!");
+ }
+
+ heimdallState = HeimdallState::Stopped;
+ UpdateInterfaceAvailability();
+ }
+ else
+ {
+ if (heimdallState == HeimdallState::Flashing)
+ {
+ flashLabel->setText("Heimdall reported an unknown error!");
+ flashProgressBar->setEnabled(false);
+ }
+ else
+ {
+ utilityOutputPlainTextEdit->appendPlainText("\nFRONTEND ERROR: Heimdall reported an unknown error!");
+ }
+
+ heimdallState = HeimdallState::Stopped;
+ UpdateInterfaceAvailability();
+ }
+}
diff --git a/heimdall-frontend/source/mainwindow.h b/heimdall-frontend/source/mainwindow.h
new file mode 100644
index 0000000..8783792
--- /dev/null
+++ b/heimdall-frontend/source/mainwindow.h
@@ -0,0 +1,183 @@
+/* Copyright (c) 2010-2014 Benjamin Dobell, Glass Echidna
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.*/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+// Qt
+#include <QList>
+#include <QMainWindow>
+#include <QProcess>
+#include <QTemporaryFile>
+
+// libpit
+#include "libpit.h"
+
+// Heimdall Frontend
+#include "aboutform.h"
+#include "ui_mainwindow.h"
+#include "PackageData.h"
+
+using namespace libpit;
+
+namespace HeimdallFrontend
+{
+ class MainWindow : public QMainWindow, public Ui::MainWindow
+ {
+ Q_OBJECT
+
+ private:
+
+ enum class HeimdallState
+ {
+ Stopped = 0,
+ Flashing,
+ DetectingDevice,
+ ClosingPcScreen,
+ PrintingPit,
+ DownloadingPit
+ };
+
+ enum
+ {
+ kPrintPitSourceDevice = 0,
+ kPrintPitSourceLocalFile
+ };
+
+ AboutForm aboutForm;
+
+ QString lastDirectory;
+
+ int tabIndex;
+
+ bool heimdallFailed;
+ HeimdallState heimdallState;
+ QProcess heimdallProcess;
+
+ PackageData loadedPackageData;
+
+ PitData currentPitData;
+ PackageData workingPackageData;
+
+ bool populatingPartitionNames;
+ QList<unsigned int> unusedPartitionIds;
+
+ bool verboseOutput;
+ bool resume;
+
+
+ void StartHeimdall(const QStringList& arguments);
+
+ void UpdateUnusedPartitionIds(void);
+ bool ReadPit(QFile *file);
+
+ void UpdatePackageUserInterface(void);
+
+ bool IsArchive(QString path);
+
+ QString PromptFileSelection(const QString& caption = QString("Select File"), const QString& filter = QString());
+ QString PromptFileCreation(const QString& caption = QString("Save File"), const QString& filter = QString());
+
+ void UpdateLoadPackageInterfaceAvailability(void);
+ void UpdateFlashInterfaceAvailability(void);
+ void UpdateCreatePackageInterfaceAvailability(void);
+ void UpdateUtilitiesInterfaceAvailability(void);
+ void UpdateInterfaceAvailability(void);
+
+ void UpdatePartitionNamesInterface(void);
+
+ public:
+
+ explicit MainWindow(QWidget *parent = 0);
+ ~MainWindow();
+
+ public slots:
+
+ void OpenDonationWebpage(void);
+ void SetVerboseOutput(bool enabled);
+ void ShowAbout(void);
+
+ void FunctionTabChanged(int index);
+
+ // Load Package Tab
+ void SelectFirmwarePackage(void);
+ void OpenDeveloperHomepage(void);
+ void OpenDeveloperDonationWebpage(void);
+ void LoadFirmwarePackage(void);
+
+ // Flash Tab
+ void SelectPartitionName(int index);
+ void SelectPartitionFile(void);
+
+ void SelectPartition(int row);
+ void AddPartition(void);
+ void RemovePartition(void);
+
+ void SelectPit(void);
+
+ void SetRepartition(int enabled);
+
+ void SetNoReboot(int enabled);
+ void SetResume(bool enabled);
+ void SetResume(int enabled);
+
+ void StartFlash(void);
+
+ // Create Package Tab
+ void FirmwareNameChanged(const QString& text);
+ void FirmwareVersionChanged(const QString& text);
+ void PlatformNameChanged(const QString& text);
+ void PlatformVersionChanged(const QString& text);
+
+ void HomepageUrlChanged(const QString& text);
+ void DonateUrlChanged(const QString& text);
+
+ void DeveloperNameChanged(const QString& text);
+ void SelectDeveloper(int row);
+ void AddDeveloper(void);
+ void RemoveDeveloper(void);
+
+ void DeviceInfoChanged(const QString& text);
+ void SelectDevice(int row);
+ void AddDevice(void);
+ void RemoveDevice(void);
+
+ void BuildPackage(void);
+
+ // Utilities Tab
+ void DetectDevice(void);
+ void ClosePcScreen(void);
+
+ void SelectPitDestination(void);
+ void DownloadPit(void);
+
+ void DevicePrintPitToggled(bool checked);
+ void LocalFilePrintPitToggled(bool checked);
+ void SelectPrintPitFile(void);
+ void PrintPit(void);
+
+ // Heimdall Command Line
+ void HandleHeimdallStdout(void);
+ void HandleHeimdallReturned(int exitCode, QProcess::ExitStatus exitStatus);
+ void HandleHeimdallError(QProcess::ProcessError error);
+ };
+}
+
+#endif // MAINWINDOW_H