summaryrefslogtreecommitdiff
path: root/daemon/appmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'daemon/appmanager.cpp')
-rw-r--r--daemon/appmanager.cpp142
1 files changed, 132 insertions, 10 deletions
diff --git a/daemon/appmanager.cpp b/daemon/appmanager.cpp
index 10f2e3e..8745160 100644
--- a/daemon/appmanager.cpp
+++ b/daemon/appmanager.cpp
@@ -4,6 +4,17 @@
#include <QJsonArray>
#include <QDir>
#include "appmanager.h"
+#include "unpacker.h"
+#include "stm32crc.h"
+
+namespace {
+struct ResourceEntry {
+ int index;
+ quint32 offset;
+ quint32 length;
+ quint32 crc;
+};
+}
AppManager::AppManager(QObject *parent)
: QObject(parent),
@@ -116,18 +127,55 @@ void AppManager::scanApp(const QString &path)
info.setWatchface(watchapp["watchface"].toBool());
info.setJSKit(appDir.exists("pebble-js-app.js"));
- const QJsonArray capabilities = root["capabilities"].toArray();
- AppInfo::Capabilities caps = 0;
- for (QJsonArray::const_iterator it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) {
- QString cap = (*it).toString();
- if (cap == "location") caps |= AppInfo::Location;
- if (cap == "configurable") caps |= AppInfo::Configurable;
+ if (root.contains("capabilities")) {
+ const QJsonArray capabilities = root["capabilities"].toArray();
+ AppInfo::Capabilities caps = 0;
+ for (auto it = capabilities.constBegin(); it != capabilities.constEnd(); ++it) {
+ QString cap = (*it).toString();
+ if (cap == "location") caps |= AppInfo::Location;
+ if (cap == "configurable") caps |= AppInfo::Configurable;
+ }
+ info.setCapabilities(caps);
}
- info.setCapabilities(caps);
- const QJsonObject appkeys = root["appKeys"].toObject();
- for (QJsonObject::const_iterator it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) {
- info.addAppKey(it.key(), it.value().toInt());
+ if (root.contains("appKeys")) {
+ const QJsonObject appkeys = root["appKeys"].toObject();
+ for (auto it = appkeys.constBegin(); it != appkeys.constEnd(); ++it) {
+ info.addAppKey(it.key(), it.value().toInt());
+ }
+ }
+
+ if (root.contains("resources")) {
+ const QJsonObject resources = root["resources"].toObject();
+ const QJsonArray media = resources["media"].toArray();
+ int index = 0;
+
+ for (auto it = media.constBegin(); it != media.constEnd(); ++it) {
+ const QJsonObject res = (*it).toObject();
+ const QJsonValue menuIcon = res["menuIcon"];
+
+ bool is_menu_icon = false;
+ switch (menuIcon.type()) {
+ case QJsonValue::Bool:
+ is_menu_icon = menuIcon.toBool();
+ break;
+ case QJsonValue::String:
+ is_menu_icon = !menuIcon.toString().isEmpty();
+ break;
+ default:
+ break;
+ }
+
+ if (is_menu_icon) {
+ QByteArray data = extractFromResourcePack(appDir.filePath("app_resources.pbpack"), index);
+ if (!data.isEmpty()) {
+ QImage icon = decodeResourceImage(data);
+ info.setMenuIcon(icon);
+ }
+ }
+
+ index++;
+ }
}
info.setPath(path);
@@ -143,3 +191,77 @@ void AppManager::scanApp(const QString &path)
const char *type = info.isWatchface() ? "watchface" : "app";
logger()->debug() << "found installed" << type << info.shortName() << info.versionLabel() << "with uuid" << info.uuid().toString();
}
+
+QByteArray AppManager::extractFromResourcePack(const QString &file, int wanted_id) const
+{
+ QFile f(file);
+ if (!f.open(QIODevice::ReadOnly)) {
+ logger()->warn() << "cannot open resource file" << f.fileName();
+ return QByteArray();
+ }
+
+ QByteArray data = f.readAll();
+ Unpacker u(data);
+
+ int num_files = u.readLE<quint32>();
+ u.readLE<quint32>(); // crc for entire file
+ u.readLE<quint32>(); // timestamp
+
+ logger()->debug() << "reading" << num_files << "resources from" << file;
+
+ QList<ResourceEntry> table;
+
+ for (int i = 0; i < num_files; i++) {
+ ResourceEntry e;
+ e.index = u.readLE<quint32>();
+ e.offset = u.readLE<quint32>();
+ e.length = u.readLE<quint32>();
+ e.crc = u.readLE<quint32>();
+
+ if (u.bad()) {
+ logger()->warn() << "short read on resource file";
+ return QByteArray();
+ }
+
+ table.append(e);
+ }
+
+ if (wanted_id >= table.size()) {
+ logger()->warn() << "specified resource does not exist";
+ return QByteArray();
+ }
+
+ const ResourceEntry &e = table[wanted_id];
+
+ int offset = 12 + 256 * 16 + e.offset;
+
+ QByteArray res = data.mid(offset, e.length);
+
+ Stm32Crc crc;
+ crc.addData(res);
+
+ if (crc.result() != e.crc) {
+ logger()->warn() << "CRC failure in resource" << e.index << "on file" << file;
+ return QByteArray();
+ }
+
+ return res;
+}
+
+QImage AppManager::decodeResourceImage(const QByteArray &data) const
+{
+ Unpacker u(data);
+ int scanline = u.readLE<quint16>();
+ u.skip(sizeof(quint16) + sizeof(quint32));
+ int width = u.readLE<quint16>();
+ int height = u.readLE<quint16>();
+
+ QImage img(width, height, QImage::Format_MonoLSB);
+ const uchar *src = reinterpret_cast<const uchar *>(&data.constData()[12]);
+ for (int line = 0; line < height; ++line) {
+ memcpy(img.scanLine(line), src, qMin(scanline, img.bytesPerLine()));
+ src += scanline;
+ }
+
+ return img;
+}