2017-08-22 15:57:37 +02:00
|
|
|
#include <cstring>
|
2017-04-14 22:39:33 +02:00
|
|
|
#include <QtEndian>
|
|
|
|
#include <QFile>
|
2021-10-10 08:38:38 +02:00
|
|
|
#include "common/color.h"
|
2017-04-14 22:39:33 +02:00
|
|
|
#include "ozf.h"
|
|
|
|
|
|
|
|
|
2017-05-03 21:34:13 +02:00
|
|
|
#define OZF2_MAGIC 0x7778
|
|
|
|
#define OZF3_MAGIC 0x7780
|
|
|
|
|
|
|
|
static const quint8 XKEY[] =
|
|
|
|
{
|
|
|
|
0x2D, 0x4A, 0x43, 0xF1, 0x27, 0x9B, 0x69, 0x4F,
|
|
|
|
0x36, 0x52, 0x87, 0xEC, 0x5F, 0x42, 0x53, 0x22,
|
|
|
|
0x9E, 0x8B, 0x2D, 0x83, 0x3D, 0xD2, 0x84, 0xBA,
|
|
|
|
0xD8, 0x5B
|
|
|
|
};
|
|
|
|
|
|
|
|
static void decrypt(void *data, size_t size, quint8 init)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
|
|
reinterpret_cast<quint8*>(data)[i] ^= XKEY[i % sizeof(XKEY)] + init;
|
|
|
|
}
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
template<class T> bool OZF::readValue(T &val)
|
|
|
|
{
|
|
|
|
T data;
|
|
|
|
|
|
|
|
if (_file.read((char*)&data, sizeof(T)) < (qint64)sizeof(T))
|
|
|
|
return false;
|
|
|
|
|
2017-05-03 21:34:13 +02:00
|
|
|
if (_decrypt)
|
|
|
|
decrypt(&data, sizeof(T), _key);
|
|
|
|
|
2019-08-13 20:50:43 +02:00
|
|
|
val = qFromLittleEndian(data);
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
bool OZF::read(void *data, size_t size, size_t decryptSize)
|
2017-05-03 21:34:13 +02:00
|
|
|
{
|
|
|
|
if (_file.read((char*)data, size) < (qint64)size)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (_decrypt)
|
2017-08-11 00:22:21 +02:00
|
|
|
decrypt(data, decryptSize ? qMin(decryptSize, size) : size, _key);
|
2017-05-03 21:34:13 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
bool OZF::initOZF3()
|
2017-05-03 21:34:13 +02:00
|
|
|
{
|
|
|
|
quint8 randomNumber, initial;
|
2017-08-11 00:22:21 +02:00
|
|
|
quint8 h1[8];
|
2017-08-22 15:57:37 +02:00
|
|
|
quint8 h2[16], h2d[16];
|
2017-05-03 21:34:13 +02:00
|
|
|
|
|
|
|
|
|
|
|
if (!_file.seek(14))
|
|
|
|
return false;
|
|
|
|
if (!readValue(randomNumber))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!_file.seek(162))
|
|
|
|
return false;
|
|
|
|
if (!readValue(initial))
|
|
|
|
return false;
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
_decrypt = true;
|
|
|
|
_key = initial;
|
|
|
|
|
|
|
|
if (!_file.seek(0))
|
|
|
|
return false;
|
|
|
|
if (!read(h1, sizeof(h1)))
|
|
|
|
return false;
|
|
|
|
_tileSize = *(h1 + 6);
|
|
|
|
|
2017-08-22 15:57:37 +02:00
|
|
|
if (!_file.seek(15 + randomNumber + 4))
|
|
|
|
return false;
|
|
|
|
if (_file.read((char*)h2, sizeof(h2)) != (qint64)sizeof(h2))
|
|
|
|
return false;
|
2017-05-03 21:34:13 +02:00
|
|
|
|
2017-08-22 15:57:37 +02:00
|
|
|
for (int i = 0; i < 256; i++) {
|
|
|
|
memcpy(h2d, h2, sizeof(h2d));
|
|
|
|
decrypt(h2d, sizeof(h2d), (quint8)i);
|
2017-05-03 21:34:13 +02:00
|
|
|
|
2017-08-22 15:57:37 +02:00
|
|
|
if ((quint32)*h2d == 40 && (quint16)*(h2d + 12) == 1
|
|
|
|
&& (quint16)*(h2d + 14) == 8) {
|
|
|
|
_key = (quint8)i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2017-05-03 21:34:13 +02:00
|
|
|
}
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
bool OZF::initOZF2()
|
|
|
|
{
|
|
|
|
if (!_file.seek(6))
|
|
|
|
return false;
|
|
|
|
if (!readValue(_tileSize))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-04-14 22:39:33 +02:00
|
|
|
bool OZF::readHeaders()
|
|
|
|
{
|
|
|
|
quint16 magic;
|
|
|
|
|
2017-05-03 21:34:13 +02:00
|
|
|
if (!readValue(magic))
|
2017-04-14 22:39:33 +02:00
|
|
|
return false;
|
|
|
|
|
2017-05-03 21:34:13 +02:00
|
|
|
if (magic == OZF2_MAGIC) {
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!initOZF2())
|
2017-05-03 21:34:13 +02:00
|
|
|
return false;
|
|
|
|
} else if (magic == OZF3_MAGIC) {
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!initOZF3())
|
2017-05-03 21:34:13 +02:00
|
|
|
return false;
|
2017-08-22 15:57:37 +02:00
|
|
|
} else {
|
|
|
|
qWarning("%s: not a OZF2/OZF3 file", qPrintable(_file.fileName()));
|
2017-04-14 22:39:33 +02:00
|
|
|
return false;
|
2017-08-22 15:57:37 +02:00
|
|
|
}
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool OZF::readTileTable()
|
|
|
|
{
|
2019-03-07 22:58:43 +01:00
|
|
|
quint32 tableOffset, headerOffset, w, h;
|
2017-04-14 22:39:33 +02:00
|
|
|
quint16 x, y;
|
2017-08-11 00:22:21 +02:00
|
|
|
int zooms;
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!_file.seek(_file.size() - sizeof(tableOffset)))
|
2017-04-14 22:39:33 +02:00
|
|
|
return false;
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!readValue(tableOffset))
|
2017-04-14 22:39:33 +02:00
|
|
|
return false;
|
2017-08-31 15:32:44 +02:00
|
|
|
zooms = (int)((_file.size() - tableOffset - sizeof(quint32))
|
|
|
|
/ sizeof(quint32));
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
for (int i = 0; i < zooms - 2; i++) {
|
|
|
|
if (!_file.seek(tableOffset + i * sizeof(quint32)))
|
|
|
|
return false;
|
|
|
|
if (!readValue(headerOffset))
|
|
|
|
return false;
|
|
|
|
if (!_file.seek(headerOffset))
|
|
|
|
return false;
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!readValue(w))
|
|
|
|
return false;
|
|
|
|
if (!readValue(h))
|
|
|
|
return false;
|
|
|
|
if (!readValue(x))
|
|
|
|
return false;
|
|
|
|
if (!readValue(y))
|
|
|
|
return false;
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
Zoom zoom;
|
|
|
|
zoom.size = QSize(w, h);
|
|
|
|
zoom.dim = QSize(x, y);
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
zoom.palette = QVector<quint32>(256);
|
|
|
|
if (!read(&(zoom.palette[0]), sizeof(quint32) * 256))
|
|
|
|
return false;
|
2019-03-07 22:58:43 +01:00
|
|
|
for (int i = 0; i < zoom.palette.size(); i++)
|
|
|
|
zoom.palette[i] = Color::bgr2rgb(qFromLittleEndian(
|
|
|
|
zoom.palette.at(i)));
|
2017-08-11 00:22:21 +02:00
|
|
|
|
|
|
|
zoom.tiles = QVector<quint32>(zoom.dim.width() * zoom.dim.height() + 1);
|
|
|
|
for (int i = 0; i < zoom.tiles.size(); i++)
|
|
|
|
if (!readValue(zoom.tiles[i]))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
_zooms.append(zoom);
|
|
|
|
}
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-22 23:14:32 +02:00
|
|
|
return _zooms.isEmpty() ? false : true;
|
2017-04-14 22:39:33 +02:00
|
|
|
}
|
|
|
|
|
2018-03-08 02:24:10 +01:00
|
|
|
bool OZF::open()
|
2017-04-14 22:39:33 +02:00
|
|
|
{
|
|
|
|
if (!_file.open(QIODevice::ReadOnly))
|
|
|
|
return false;
|
|
|
|
|
2023-05-04 09:38:35 +02:00
|
|
|
if (!_zooms.isEmpty())
|
|
|
|
return true;
|
|
|
|
|
2017-04-14 22:39:33 +02:00
|
|
|
if (!readHeaders()) {
|
2017-08-22 15:57:37 +02:00
|
|
|
qWarning("%s: Invalid header", qPrintable(_file.fileName()));
|
2017-04-14 22:39:33 +02:00
|
|
|
_file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!readTileTable()) {
|
2017-08-22 15:57:37 +02:00
|
|
|
qWarning("%s: Invalid tile table", qPrintable(_file.fileName()));
|
2017-04-14 22:39:33 +02:00
|
|
|
_file.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
QPixmap OZF::tile(int zoom, int x, int y)
|
2017-04-14 22:39:33 +02:00
|
|
|
{
|
|
|
|
Q_ASSERT(_file.isOpen());
|
2017-08-11 00:22:21 +02:00
|
|
|
Q_ASSERT(0 <= zoom && zoom < _zooms.count());
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
const Zoom &z = _zooms.at(zoom);
|
|
|
|
|
|
|
|
int i = (y/tileSize().height()) * z.dim.width() + (x/tileSize().width());
|
|
|
|
if (i >= z.tiles.size() - 1 || i < 0)
|
2017-04-15 08:59:31 +02:00
|
|
|
return QPixmap();
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
int size = z.tiles.at(i+1) - z.tiles.at(i);
|
|
|
|
if (!_file.seek(z.tiles.at(i)))
|
2017-04-15 08:59:31 +02:00
|
|
|
return QPixmap();
|
2017-04-14 22:39:33 +02:00
|
|
|
|
2017-08-09 11:32:05 +02:00
|
|
|
quint32 bes = qToBigEndian(tileSize().width() * tileSize().height());
|
|
|
|
QByteArray ba;
|
|
|
|
ba.resize(sizeof(bes) + size);
|
2018-03-26 01:02:31 +02:00
|
|
|
memcpy(ba.data(), &bes, sizeof(bes));
|
2017-08-09 11:32:05 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
if (!read(ba.data() + sizeof(bes), size, 16))
|
2017-04-15 08:59:31 +02:00
|
|
|
return QPixmap();
|
2017-04-14 22:39:33 +02:00
|
|
|
QByteArray uba = qUncompress(ba);
|
|
|
|
if (uba.size() != tileSize().width() * tileSize().height())
|
2017-04-15 08:59:31 +02:00
|
|
|
return QPixmap();
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
QImage img((const uchar*)uba.constData(), tileSize().width(),
|
|
|
|
tileSize().height(), QImage::Format_Indexed8);
|
2017-08-11 00:22:21 +02:00
|
|
|
img.setColorTable(z.palette);
|
2017-04-14 22:39:33 +02:00
|
|
|
|
|
|
|
return QPixmap::fromImage(img.mirrored());
|
|
|
|
}
|
2017-07-29 22:19:03 +02:00
|
|
|
|
2017-08-11 00:22:21 +02:00
|
|
|
QSize OZF::size(int zoom) const
|
|
|
|
{
|
|
|
|
Q_ASSERT(0 <= zoom && zoom < _zooms.count());
|
|
|
|
|
|
|
|
return _zooms.at(zoom).size;
|
|
|
|
}
|
|
|
|
|
2018-03-08 02:24:10 +01:00
|
|
|
QPointF OZF::scale(int zoom) const
|
|
|
|
{
|
|
|
|
return QPointF((qreal)size(zoom).width() / (qreal)size(0).width(),
|
|
|
|
(qreal)size(zoom).height() / (qreal)size(0).height());
|
|
|
|
}
|
|
|
|
|
2017-07-29 22:19:03 +02:00
|
|
|
bool OZF::isOZF(const QString &path)
|
|
|
|
{
|
|
|
|
QFile file(path);
|
|
|
|
quint16 magic;
|
|
|
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
|
|
return false;
|
|
|
|
if (file.read((char*)&magic, sizeof(magic)) < (qint64)sizeof(magic))
|
|
|
|
return false;
|
|
|
|
|
2017-07-31 00:46:10 +02:00
|
|
|
magic = qFromLittleEndian(magic);
|
2017-07-29 22:19:03 +02:00
|
|
|
if (magic == OZF2_MAGIC || magic == OZF3_MAGIC)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|