Files
edgetx-gx12/companion/src/storage/firmwareinterface.cpp
T

548 lines
13 KiB
C++

/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "firmwareinterface.h"
#include "helpers.h"
#include <QFile>
#include <QMessageBox>
#include <vector>
#define FW_MARK "FW"
#define VERS_MARK "VERS"
#define DATE_MARK "DATE"
#define TIME_MARK "TIME"
#define EEPR_MARK "EEPR"
#define FSIZE_MAX Boards::getFlashSize(Board::BOARD_UNKNOWN)
#define UF2_HEADER_SIZE 32
#define UF2_BLOCK_SIZE 512
#define UF2_MAGICSTART0 0x0A324655
#define UF2_MAGICSTART1 0x9E5D5157
#define UF2_MAGICEND 0x0AB16F30
#define UF2_FLAG_NOT_MAIN_FLASH 0x00000001
#define UF2_FLAG_FILE_CONTAINER 0x00001000
#define UF2_FLAG_FAMILYID_PRESENT 0x00002000
#define UF2_FLAG_MD5_CHECKSUM_PRESENT 0x00004000
#define UF2_FLAG_EXT_FLAGS_PRESENT 0x00008000
class RleBitmap
{
public:
RleBitmap(const uint8_t *src, size_t offset) :
state(RLE_FIRST_BYTE), curPtr(src), byte(0), curCount(0), pos(0)
{
width = *curPtr++;
rawRows = *curPtr++;
rows = (rawRows +1)/2;
skip(offset);
}
void skip(size_t count)
{
while(count)
{
count--;
getNext();
}
}
uint8_t getNext()
{
pos++;
switch(state)
{
case RLE_FIRST_BYTE:
byte = *curPtr++;
if(byte == *curPtr)
state = RLE_SECOND_BYTE;
break;
case RLE_SECOND_BYTE:
byte = *curPtr++;
curCount = (*curPtr++)+1;
state = RLE_CONTINUE;
// fall through
case RLE_CONTINUE:
curCount--;
if(!curCount)
state = RLE_FIRST_BYTE;
break;
}
return byte;
}
bool to(uint8_t* buf, size_t size)
{
while(pos<size)
*buf++ = getNext();
return pos == size;
}
uint8_t getWidth() const { return width; }
uint8_t getRows() const { return rows; }
uint8_t getRawRows() const { return rawRows; }
void goToNextRow()
{
size_t offset = pos%width;
if(offset)
skip(width - pos%width);
}
static size_t encode(uint8_t *buf, size_t bytes)
{
uint8_t* readPtr = buf;
uint8_t* writePtr = buf;
uint8_t state = 0;
uint8_t count = 0;
uint16_t prevByte = 0xFFFF;
while(bytes)
{
bytes--;
uint8_t byte = *readPtr++;
if(state == 0)
{
*writePtr++ = byte;;
if(prevByte == byte)
{
state = 1;
count = 0;
} else {
prevByte = byte;
}
} else {
if(prevByte == byte)
{
count++;
if(count == 255)
{
*writePtr++ = count;
prevByte = 0xFFFF;
state = 0;
}
} else {
*writePtr++ = count;
*writePtr++ = byte;
prevByte = byte;
state = 0;
}
}
}
if(state != 0)
*writePtr++ = count;
return writePtr - buf;
}
private:
enum State {RLE_FIRST_BYTE, RLE_SECOND_BYTE, RLE_CONTINUE} state;
const uint8_t* curPtr;
uint8_t width;
uint8_t rows;
uint8_t rawRows;
uint8_t byte;
uint16_t curCount;
size_t pos;
};
FirmwareInterface::FirmwareInterface(const QString &filename, QDialog *parent) :
parentDialog(parent)
{
if (filename.isEmpty()) return;
QFile file(filename);
if (file.open(QIODevice::ReadOnly)) {
initFlash(file.readAll());
file.close();
} else {
// TODO: error handling
}
}
FirmwareInterface::FirmwareInterface(const QByteArray &flashData, QDialog *parent) :
parentDialog(parent)
{
initFlash(flashData);
}
void FirmwareInterface::initFlash(const QByteArray& flashData)
{
flash = flashData;
flashSize = flashData.size();
if (flashSize > 0) {
type = (flash.startsWith("UF2\n") ? FIRMWARE_TYPE_UF2 : FIRMWARE_TYPE_BIN);
if (type == FIRMWARE_TYPE_UF2 && !concatUF2Payloads(flash)) {
qDebug() << "Unable to concatenate UF2 payloads";
isValidFlag = false;
return;
}
flavour = seekLabel(FW_MARK).remove("edgetx-");
version = seekLabel(VERS_MARK);
if (version.startsWith("opentx-")) {
// old version format
int index = version.lastIndexOf('-');
flavour = version.mid(0, index);
version = version.mid(index + 1);
}
date = seekLabel(DATE_MARK);
time = seekLabel(TIME_MARK);
eepromId = seekLabel(EEPR_MARK);
if (eepromId.contains('-')) {
QStringList list = eepromId.split('-');
eepromVersion = list[0].toInt();
eepromVariant = list[1].toInt();
} else {
eepromVersion = eepromId.toInt();
}
seekSplash();
isValidFlag = !version.isEmpty();
}
}
QString FirmwareInterface::seekString(const QString & string)
{
QString result = "";
int start = flash.indexOf(string.toUtf8());
if (start > 0) {
start += string.length();
int end = -1;
for (int i = start; i < start + 50; i++) {
char c = flash.at(i);
if (c == '\0' || c == '\036') {
end = i;
break;
}
}
if (end > 0) {
result = flash.mid(start, (end - start)).trimmed();
}
}
return result;
}
QString FirmwareInterface::seekLabel(const QString & label)
{
QString result = seekString(label + "\037\033:");
if (result.isEmpty()) {
result = seekString(label + "\037\075:"); // This is for Horus
}
if (!result.isEmpty()) {
return result;
}
return seekString(label + ":");
}
bool FirmwareInterface::isHardwareCompatible(const FirmwareInterface &previousFirmware) const
{
QString newFlavour = getFlavour();
if (newFlavour.isEmpty())
return true;
QString previousFlavour = previousFirmware.getFlavour();
if (previousFlavour.isEmpty())
return true;
qDebug() << "Firmware previous:" << previousFlavour << "new:" << newFlavour;
return (newFlavour == previousFlavour);
}
bool FirmwareInterface::seekSplash(QByteArray splash)
{
int start = flash.indexOf(splash);
if (start > 0) {
splashOffset = start;
splashSize = splash.size();
return true;
} else {
return false;
}
}
bool FirmwareInterface::seekSplash(QByteArray sps, QByteArray spe, int size)
{
int start = 0;
while (start >= 0) {
start = flash.indexOf(sps, start+1);
if (start>0) {
int end = start + sps.size() + size;
if (end == flash.indexOf(spe, end)) {
splashOffset = start + sps.size();
splashSize = end - start - sps.size();
return true;
} else {
qDebug() << flash.indexOf(spe, start) << end << sps.size() << spe;
}
}
}
return false;
}
#define ETX_SPS_9X "SPS\0\200\100"
#define ETX_SPS_TARANIS "SPS\0\324\100"
#define ETX_SPS_SIZE 6
#define ETX_SPE "SPE"
#define ETX_SPE_SIZE 4
void FirmwareInterface::seekSplash()
{
splashSize = 0;
splashOffset = 0;
splashWidth = SPLASH_WIDTH;
splashHeight = SPLASH_HEIGHT;
splashFormat = QImage::Format_Mono;
if (seekSplash(QByteArray(ETX_SPS_9X, ETX_SPS_SIZE), QByteArray(ETX_SPE, ETX_SPE_SIZE), 1024)) {
return;
}
if (seekSplash(QByteArray(ETX_SPS_TARANIS, ETX_SPS_SIZE), QByteArray(ETX_SPE, ETX_SPE_SIZE), RLE_SPLASH_MAX_SIZE)) {
splashWidth = SPLASHX9D_WIDTH;
splashHeight = SPLASHX9D_HEIGHT;
splashFormat = QImage::Format_Indexed8;
return;
}
}
bool FirmwareInterface::setSplash(const QImage & newsplash)
{
if (splashOffset == 0 || splashSize == 0) {
return false;
}
uint8_t b[SPLASH_SIZE_MAX] = {0};
QColor color;
QByteArray splash;
if (splashFormat == QImage::Format_Indexed8) {
for (unsigned int y = 0; y < splashHeight; y++) {
unsigned int idx = (y / 2) * splashWidth;
for (unsigned int x = 0; x < splashWidth; x++, idx++) {
QRgb gray = qGray(newsplash.pixel(x, y));
uint8_t z = ((255 - gray) * 15) / 255;
if (y & 1) z <<= 4;
b[idx] |= z;
}
}
if (splashWidth == SPLASHX9D_WIDTH && splashHeight == SPLASHX9D_HEIGHT)
{
splashSize = RleBitmap::encode(b, SPLASH_SIZE_MAX);
if (splashSize > RLE_SPLASH_MAX_SIZE){
if (parentDialog)
QMessageBox::critical(parentDialog, CPN_STR_TTL_ERROR, QObject::tr("Compressed image size exceeds reserved space."));
return false;
}
}
} else {
QColor black = QColor(0,0,0);
QImage blackNwhite = newsplash.convertToFormat(QImage::Format_MonoLSB);
for (uint y = 0; y < splashHeight; y++) {
for (uint x = 0; x < splashWidth; x++) {
color = QColor(blackNwhite.pixel(x, y));
b[splashWidth * (y / 8) + x] |= ((color == black ? 1 : 0) << (y % 8));
}
}
}
splash.clear();
splash.append((char *)b, splashSize);
flash.replace(splashOffset, splashSize, splash);
return true;
}
int FirmwareInterface::getSplashWidth()
{
return splashWidth;
}
uint FirmwareInterface::getSplashHeight()
{
return splashHeight;
}
QImage::Format FirmwareInterface::getSplashFormat()
{
return splashFormat;
}
QImage FirmwareInterface::getSplash()
{
if (splashOffset == 0 || splashSize == 0) {
return QImage(); // empty image
}
if (splashFormat == QImage::Format_Indexed8) {
QImage image(splashWidth, splashHeight, QImage::Format_RGB888);
if(splashWidth == SPLASHX9D_WIDTH && splashHeight == SPLASHX9D_HEIGHT)
{
std::vector<uint8_t> data;
data.resize(splashWidth * splashHeight);
RleBitmap img((uint8_t*)flash.data() + splashOffset - 2, 0);
img.to(&data[0], data.size());
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
unsigned int idx = (y/2)*splashWidth;
for (unsigned int x=0; x<splashWidth; x++, idx++) {
uint8_t byte = data[idx];
unsigned int z = (y & 1) ? (byte >> 4) : (byte & 0x0F);
z = 255-(z*255)/15;
QRgb rgb = qRgb(z, z, z);
image.setPixel(x, y, rgb);
}
}
}
} else {
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
unsigned int idx = (y/2)*splashWidth;
for (unsigned int x=0; x<splashWidth; x++, idx++) {
uint8_t byte = flash.at(splashOffset+idx);
unsigned int z = (y & 1) ? (byte >> 4) : (byte & 0x0F);
z = 255-(z*255)/15;
QRgb rgb = qRgb(z, z, z);
image.setPixel(x, y, rgb);
}
}
}
}
return image;
}
else {
QImage image(splashWidth, splashHeight, QImage::Format_Mono);
if (splashOffset > 0) {
for (unsigned int y=0; y<splashHeight; y++) {
for(unsigned int x=0; x<splashWidth; x++) {
image.setPixel(x, y, (flash.at(splashOffset+(splashWidth*(y/8)+x)) & (1<<(y % 8))) ? 0 : 1);
}
}
}
return image;
}
}
bool FirmwareInterface::hasSplash()
{
return (splashOffset > 0 ? true : false);
}
bool FirmwareInterface::isValid()
{
return isValidFlag;
}
unsigned int FirmwareInterface::save(const QString & filename)
{
uint8_t * binflash = (uint8_t*)malloc(FSIZE_MAX);
if (binflash == NULL) {
return -1;
}
memcpy(binflash, flash.constData(), flashSize);
QFile file(filename);
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
free(binflash);
return -1;
}
file.write((char*)binflash, flashSize);
file.close();
free(binflash);
return flashSize;
}
bool FirmwareInterface::concatUF2Payloads(QByteArray & flashData)
{
qsizetype flashSize = flashData.size();
int lastBlock = -1;
UF2_Block uf2Block;
QByteArray out;
QByteArray ba;
qsizetype totalBytesRead = 0;
do {
ba = flashData.mid(totalBytesRead, UF2_BLOCK_SIZE);
qsizetype bytesRead = ba.size();
if (bytesRead < UF2_BLOCK_SIZE) {
qDebug() << "bytes read" << bytesRead << "less than expected" << UF2_BLOCK_SIZE;
break;
}
totalBytesRead += bytesRead;
memcpy(&uf2Block, ba.constData(), sizeof(UF2_Block));
if (uf2Block.magicStart0 != UF2_MAGICSTART0 ||
uf2Block.magicStart1 != UF2_MAGICSTART1 ||
uf2Block.magicEnd != UF2_MAGICEND) {
qDebug() << "unexpected magic numbers";
return false;
}
if ((int)uf2Block.blockNo != ++lastBlock) {
qDebug() << "Block out of sequence";
return false;
}
out.append(ba.mid(UF2_HEADER_SIZE, uf2Block.payloadSize));
} while (totalBytesRead < flashSize);
flashData = out;
return true;
}