diff --git a/Sachesi.pro b/Sachesi.pro index 457a824..49b6bb6 100644 --- a/Sachesi.pro +++ b/Sachesi.pro @@ -4,7 +4,7 @@ TARGET="Sachesi" win32: RC_ICONS += assets/sachesi-114.ico else:mac: ICON = assets/sachesi-114.icns else: ICON = assets/sachesi-114.png -VERSION = 1.9.6 +VERSION = 1.9.9 # Global specific CONFIG += c++11 diff --git a/qml/generic/Boot.qml b/qml/generic/Boot.qml index ecd6fc6..9bae0c5 100644 --- a/qml/generic/Boot.qml +++ b/qml/generic/Boot.qml @@ -62,7 +62,7 @@ Column { spacing: 3 delegate: Label { - text: qsTr("Blackberry " + (modelData == "1" ? "Bootloader" : ((modelData == "8013") ? "USB (Unix)" : "USB (Windows)"))); + text: "Blackberry " + (modelData == "1" ? "Bootloader" : ((modelData == "8013") ? "USB (Unix)" : (modelData == "8017" ? "USB (Autodetect)" : "USB (Windows)"))); font.pointSize: 12 } } diff --git a/qml/generic/Installer.qml b/qml/generic/Installer.qml index 18bd08f..fa86709 100644 --- a/qml/generic/Installer.qml +++ b/qml/generic/Installer.qml @@ -21,19 +21,41 @@ Item { text: qsTr("View Install (") + i.dgProgress + ")" onClicked: installWin.visible = true } + Window { + visible: i.extractInstallZip + color: "lightgray" + width: patientText.width + 20 + height: patientText.height + 20 + onVisibleChanged: if (visible) { + x = window.x + (window.width - width) / 2 + y = window.y + (window.height - height) / 2 + } + Label { + id: patientText + text: qsTr("Please be patient while the installation zip is extracted.") + } + } Window { id: installWin visible: i.dgProgress >= 0 - width: parent.width / 3; height: Math.min(parent.height / 2, width + 20); + width: parent.width / 3; height: Math.min(parent.height / 2, width + 20 + warningLabel.height * 2 * warningLabel.visible); onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } color: "lightgray" title: i.firmwareUpdate ? qsTr("Firmware Update") : qsTr("Install") + Label { + id: warningLabel + visible: i.knownOS.indexOf("10.3") == 0 + text: "Your device may show 0% progress.\nThis is normal on your OS." + } + CircleProgress { - anchors.fill: parent + width: parent.width + height: parent.height + anchors.bottom: parent.bottom currentValue: i.curDGProgress overallValue: i.dgProgress curId: i.dgPos + 1 diff --git a/qml/generic/USBConnect.qml b/qml/generic/USBConnect.qml index 37f29c9..fbe5975 100644 --- a/qml/generic/USBConnect.qml +++ b/qml/generic/USBConnect.qml @@ -75,6 +75,7 @@ Item { case 1: return "Bootloader" case 8012: return "Windows" case 8013: return "Unix" + case 8017: return "Autodetect" default: return "" } diff --git a/qml/generic/VersionLookup.qml b/qml/generic/VersionLookup.qml index 19f8e1a..684fd02 100644 --- a/qml/generic/VersionLookup.qml +++ b/qml/generic/VersionLookup.qml @@ -31,14 +31,14 @@ Window { SpinBox { id: minor width: qt_new ? implicitWidth : implicitWidth + 25 - value: 0 + value: 1 maximumValue: 255 onEditingFinished: relookup.clicked() } SpinBox { id: build width: qt_new ? implicitWidth : implicitWidth + 25 - value: 1418 + value: 821 maximumValue: 9999 stepSize: 3 onEditingFinished: relookup.clicked() diff --git a/src/installer.cpp b/src/installer.cpp index b0bcd39..cbc9f3a 100644 --- a/src/installer.cpp +++ b/src/installer.cpp @@ -26,7 +26,7 @@ InstallNet::InstallNet( QObject* parent) : QObject(parent), manager(nullptr), reply(nullptr), cookieJar(nullptr), _knownOS(""), _knownRadio("N/A"), _knownBattery(-1), _knownHWFamily(0), _knownPIN(""), _wrongPass(false), _loginBlock(false), _state(0), _dlBytes(0), _dlTotal(0), _dgProgress(-1), _curDGProgress(-1), - _completed(false), _installing(false), _restoring(false), _backing(false), + _completed(false), _extractInstallZip(false), _installing(false), _restoring(false), _backing(false), _hadPassword(true), currentBackupZip(nullptr), _zipFile(nullptr) { #ifdef _MSC_VER @@ -101,7 +101,7 @@ void InstallNet::scanProps() } } -BarInfo InstallNet::checkInstallableInfo(QString name) +BarInfo InstallNet::checkInstallableInfo(QString name, bool blitz) { BarInfo barInfo = {name, "", NotInstallableType}; // Check if it's a 'hidden' file as we use these for temporary file downloads. @@ -138,27 +138,37 @@ BarInfo InstallNet::checkInstallableInfo(QString name) if (barInfo.type == OSType) { QString installableOS = appName.split("os.").last().remove(".desktop").replace("verizon", "factory"); if (_knownConnectedOSType != "" && installableOS != _knownConnectedOSType) { - int choice = QMessageBox::critical(nullptr, "WARNING", "The OS file you have selected to install is for a different device!\nOS Type: " + installableOS + "\nYour Device: " + _knownConnectedOSType, "Ignore Warning [Stupid]", "Skip OS", "Cancel Install", 2); - if (choice == 1) { + if (blitz) { barInfo.type = NotInstallableType; return barInfo; - } - if (choice == 2) { - barInfo.name = "EXIT"; - return barInfo; + } else { + int choice = QMessageBox::critical(nullptr, "WARNING", "The OS file you have selected to install is for a different device!\nOS Type: " + installableOS + "\nYour Device: " + _knownConnectedOSType, "Ignore Warning [Stupid]", "Skip OS", "Cancel Install", 2); + if (choice == 1) { + barInfo.type = NotInstallableType; + return barInfo; + } + if (choice == 2) { + barInfo.name = "EXIT"; + return barInfo; + } } } } else if (barInfo.type == RadioType) { QString installableRadio = appName.split("radio.").last().remove(".omadm"); if (_knownConnectedRadioType != "" && installableRadio != _knownConnectedRadioType) { - int choice = QMessageBox::critical(nullptr, "WARNING", "The Radio file you have selected to install is for a different device!\nRadio Type: " + installableRadio + "\nYour Device: " + _knownConnectedRadioType, "Ignore Warning [Stupid]", "Skip Radio", "Cancel Install", 2); - if (choice == 1) { + if (blitz) { barInfo.type = NotInstallableType; return barInfo; - } - if (choice == 2) { - barInfo.name = "EXIT"; - return barInfo; + } else { + int choice = QMessageBox::critical(nullptr, "WARNING", "The Radio file you have selected to install is for a different device!\nRadio Type: " + installableRadio + "\nYour Device: " + _knownConnectedRadioType, "Ignore Warning [Stupid]", "Skip Radio", "Cancel Install", 2); + if (choice == 1) { + barInfo.type = NotInstallableType; + return barInfo; + } + if (choice == 2) { + barInfo.name = "EXIT"; + return barInfo; + } } } } @@ -175,37 +185,159 @@ BarInfo InstallNet::checkInstallableInfo(QString name) return barInfo; } +BarInfo InstallNet::blitzCheck(QString name) +{ + BarInfo barInfo = {name, "", NotInstallableType}; + // Check if it's a 'hidden' file as we use these for temporary file downloads. + if (QFileInfo(name).fileName().startsWith('.')) + return barInfo; + + QuaZipFile manifest(name, "META-INF/MANIFEST.MF", QuaZip::csSensitive); + if (!manifest.open(QIODevice::ReadOnly)) + return barInfo; + QString appName, type; + while (!manifest.atEnd()) { + QByteArray newLine = manifest.readLine(); + if (newLine.startsWith("Package-Name:")) { + appName = newLine.split(':').last().simplified(); + } + else if (newLine.startsWith("Package-Type:")) { + type = newLine.split(':').last().simplified(); + if (type == "system" && barInfo.type == NotInstallableType) + barInfo.type = OSType; + else + break; + } + else if (newLine.startsWith("System-Type:")) { + if (newLine.split(':').last().simplified() == "radio") + barInfo.type = RadioType; + break; + } + } + if (barInfo.type != OSType && barInfo.type != RadioType) + return barInfo; + + barInfo.name = "GOOD"; + if (barInfo.type == OSType) { + QString installableOS = appName.split("os.").last().remove(".desktop").replace("verizon", "factory"); + if (_knownConnectedOSType != "" && installableOS != _knownConnectedOSType) { + barInfo.name = "BAD"; + } + } else if (barInfo.type == RadioType) { + QString installableRadio = appName.split("radio.").last().remove(".omadm"); + if (_knownConnectedRadioType != "" && installableRadio != _knownConnectedRadioType) { + barInfo.name = "BAD"; + } + } +} + void InstallNet::install(QList files) { _installInfo.clear(); setFirmwareUpdate(false); - foreach(QUrl url, files) - { - if (!url.isLocalFile()) - continue; - QString name = url.toLocalFile(); + QList filenames; + + // Zip install + if (files.count() == 1 && files.first().toLocalFile().endsWith(".zip")) { + // A collection of bars + _extractInstallZip = true; emit extractInstallZipChanged(); + QuaZip zip(files.first().toLocalFile()); + zip.open(QuaZip::mdUnzip); + + QuaZipFile file(&zip); - if (QFileInfo(name).isDir()) + QString baseName = zip.getZipName().split('.').first(); + if (!QDir(baseName).mkpath(".")) + QMessageBox::information(nullptr, "Error", "Was unable to extract the zip container."); + + for(bool f=zip.goToFirstFile(); f; f=zip.goToNextFile()) { + QString thisFile = baseName + "/" + zip.getCurrentFileName().split('/').last(); + if (QFile::exists(thisFile)) { + QuaZipFileInfo info; + zip.getCurrentFileInfo(&info); + if (QFile(thisFile).size() == info.uncompressedSize) { + filenames.append(thisFile); + continue; + } + } + if (!file.open(QIODevice::ReadOnly)) + continue; + // Check we have a zip + if (file.read(2).toHex() == "504b") { + QFile writeFile(thisFile); + if (!writeFile.open(QIODevice::WriteOnly)) { + file.close(); + continue; + } + writeFile.write(QByteArray::fromHex("504b")); + while (!file.atEnd()) { + qApp->processEvents(); + writeFile.write(file.read(4096)); + } + filenames.append(thisFile); + } + file.close(); + } + _extractInstallZip = false; emit extractInstallZipChanged(); + } else { + + // Grab file names (first pass) + foreach(QUrl url, files) { - QStringList barFiles = QDir(name).entryList(QStringList("*.bar")); - foreach (QString barFile, barFiles) + if (!url.isLocalFile()) + continue; + QString name = url.toLocalFile(); + if (QFileInfo(name).isDir()) { - BarInfo info = checkInstallableInfo(name + "/" + barFile); - if (info.name == "EXIT") - return setNewLine("Install aborted."); - - if (info.type != NotInstallableType) - _installInfo.append(info); + QStringList barFiles = QDir(name).entryList(QStringList("*.bar")); + foreach (QString barFile, barFiles) { + filenames.append(name + "/" + barFile); + } + } else { + filenames.append(name); } - } else { - BarInfo info = checkInstallableInfo(name); - if (info.name == "EXIT") - return setNewLine("Install aborted."); + } + } - if (info.type != NotInstallableType) - _installInfo.append(info); + // Detect Blitz files (second pass) + bool blitzOSIsSafe = false; + bool blitzRadioIsSafe = false; + int radioCount = 0, osCount = 0; + foreach(QString barFile, filenames) + { + BarInfo info = blitzCheck(barFile); + if (info.type == OSType) { + osCount++; + if (info.name == "GOOD") + blitzOSIsSafe = true; + } else if (info.type == RadioType) { + radioCount++; + if (info.name == "GOOD") + blitzRadioIsSafe = true; } } + if (osCount > 1 || radioCount > 1) { + setNewLine(QString("%1Blitz detected. %2 OSes and %3 Radios") + .arg((blitzOSIsSafe && blitzRadioIsSafe) ? "Safe " : "") + .arg(osCount) + .arg(radioCount)); + if (_knownConnectedRadioType == "" && radioCount > 1) { + QMessageBox::critical(nullptr, "Error", "Your device is reporting no known Radio. The blitz install is unable to detect the correct Radio for your system and cannot continue."); + } else if (_knownConnectedOSType == "" && osCount > 1) { + QMessageBox::critical(nullptr, "Error", "Your device is reporting no known OS. The blitz install is unable to detect the correct OS for your system and cannot continue."); + } + } + + // Detect everything (third pass) + foreach(QString barFile, filenames) + { + BarInfo info = checkInstallableInfo(barFile, osCount > 1 || radioCount > 1); + if (info.name == "EXIT") + return setNewLine("Install aborted."); + + if (info.type != NotInstallableType) + _installInfo.append(info); + } if (_installInfo.isEmpty()) { setNewLine("None of the selected files were installable."); return; @@ -482,11 +614,11 @@ void InstallNet::login() int flags = QNetworkInterface::IsUp | QNetworkInterface::IsRunning | QNetworkInterface::CanBroadcast | QNetworkInterface::CanMulticast; foreach(QNetworkInterface inter, QNetworkInterface::allInterfaces()) { - if ((inter.flags() & flags) == flags && !inter.flags().testFlag(QNetworkInterface::IsLoopBack)) + if (true)//((inter.flags() & flags) == flags && !inter.flags().testFlag(QNetworkInterface::IsLoopBack)) { foreach(QNetworkAddressEntry addr, inter.addressEntries()) { - if (addr.ip().protocol() == QAbstractSocket::IPv4Protocol) + if (addr.ip().protocol() == QAbstractSocket::IPv4Protocol && !addr.ip().toString().startsWith("127.")) { QList addrParts; foreach(QString addrString, addr.ip().toString().split('.')) @@ -1211,7 +1343,7 @@ void InstallNet::restoreReply() } else if (xml.name() == "RestoreSend") { - qIoSafeFree(_zipFile) + qIoSafeFree(_zipFile); _back.setCurMode(1); if (_back.curMode() == "complete") { postData.addQueryItem("status", "success"); @@ -1277,6 +1409,8 @@ void InstallNet::restoreError(QNetworkReply::NetworkError error) if (_state && this_ip == _ip) { resetVars(); + } else if (_state) { + return; } QString errString = QString("Communication Error: %1 (%2) from %3") .arg(error) diff --git a/src/installer.h b/src/installer.h index a57bb9a..6efdbd8 100644 --- a/src/installer.h +++ b/src/installer.h @@ -93,6 +93,7 @@ class InstallNet : public QObject { Q_PROPERTY(bool restoring MEMBER _restoring WRITE setRestoring NOTIFY restoringChanged) Q_PROPERTY(bool backing MEMBER _backing WRITE setBacking NOTIFY backingChanged) Q_PROPERTY(bool firmwareUpdate MEMBER _firmwareUpdate WRITE setFirmwareUpdate NOTIFY firmwareUpdateChanged) + Q_PROPERTY(bool extractInstallZip MEMBER _extractInstallZip NOTIFY extractInstallZipChanged) Q_PROPERTY(QStringList firmwareNames MEMBER _firmwareNames NOTIFY firmwareNamesChanged) Q_PROPERTY(QStringList firmwarePaths MEMBER _firmwarePaths NOTIFY firmwarePathsChanged) Q_PROPERTY(QString knownOS READ knownOS WRITE setKnownOS NOTIFY knownOSChanged) @@ -203,6 +204,7 @@ class InstallNet : public QObject { void firmwareUpdateChanged(); void firmwareNamesChanged(); void firmwarePathsChanged(); + void extractInstallZipChanged(); void knownOSChanged(); void knownRadioChanged(); void knownBatteryChanged(); @@ -232,7 +234,8 @@ private slots: QNetworkRequest setData(QString page, QString contentType); QNetworkReply* postQuery(QString page, QString contentType, const QUrlQuery& query); QNetworkReply* getQuery(QString page, QString contentType); - BarInfo checkInstallableInfo(QString name); + BarInfo checkInstallableInfo(QString name, bool blitz); + BarInfo blitzCheck(QString name); void install(); void restore(); void backup(); @@ -290,6 +293,7 @@ private slots: BackupInfo _back; bool _completed; bool _firmwareUpdate; + bool _extractInstallZip; QList _installInfo; QString _backupFileName; QStringList _currentApps; diff --git a/src/sachesi.cpp b/src/sachesi.cpp index afcda8f..8abc635 100644 --- a/src/sachesi.cpp +++ b/src/sachesi.cpp @@ -49,7 +49,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) app.setOrganizationName("Qtness"); app.setOrganizationDomain("qtness.com"); app.setApplicationName("Sachesi"); - app.setApplicationVersion("1.9.6"); + app.setApplicationVersion("1.9.9"); // Install translator by locale language string QTranslator appTranslator; diff --git a/src/splitter.h b/src/splitter.h index 5513e8f..c77bc11 100644 --- a/src/splitter.h +++ b/src/splitter.h @@ -92,6 +92,11 @@ class AutoloaderWriter: public QFile { AutoloaderWriter(QList selectedInfo) : _infos(selectedInfo) { + qSort(selectedInfo.begin(), selectedInfo.end(), compareSizes); + } + static bool compareSizes(QFileInfo i, QFileInfo j) + { + return i.size() > j.size(); } void create(QString name) { // Find potential file