You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
401 lines
13 KiB
401 lines
13 KiB
/* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
|
|
* (c)DWTFYW
|
|
*
|
|
* This program is free software. It comes without any warranty, to
|
|
* the extent permitted by applicable law. You can redistribute it
|
|
* and/or modify it under the terms of the Do What The Fuck You Want
|
|
* To Public License, Version 2, as published by Sam Hocevar. See
|
|
* http://sam.zoy.org/wtfpl/COPYING for more details.
|
|
*/
|
|
#include <QDebug>
|
|
//#include <QtCore>
|
|
|
|
#include "xcrthemefx.h"
|
|
|
|
#include <unistd.h>
|
|
#include <zlib.h>
|
|
|
|
#include <QSet>
|
|
#include <QStringList>
|
|
#include <QTextStream>
|
|
|
|
#include "xcrimg.h"
|
|
#include "xcrxcur.h"
|
|
#include "xcrtheme.h"
|
|
|
|
|
|
static const char *curShapeName[] = {
|
|
"standard arrow",
|
|
"help arrow (the one with '?')",
|
|
"working arrow",
|
|
"busy cursor",
|
|
"precision select",
|
|
"text select",
|
|
"handwriting",
|
|
"unavailable",
|
|
"north (vert) resize",
|
|
"south resize",
|
|
"west (vert-means horiz?) resize",
|
|
"east resize",
|
|
"north-west resize",
|
|
"south-east resize",
|
|
"north-east resize",
|
|
"south-west resize",
|
|
"move",
|
|
"alternate select",
|
|
"hand",
|
|
"button"
|
|
};
|
|
|
|
|
|
bool XCursorThemeFX::str2num (const QString &s, quint32 &res) {
|
|
quint64 n = 0;
|
|
if (s.isEmpty()) return false;
|
|
for (int f = 0; f < s.length(); f++) {
|
|
QChar ch = s.at(f);
|
|
if (!ch.isDigit()) return false;
|
|
n = n*10+ch.unicode()-'0';
|
|
}
|
|
//if (n >= (quint64)0x100000000LL) n = 0xffffffffLL;
|
|
if (n >= (quint64)0x80000000LL) n = 0x7fffffffLL;
|
|
res = n;
|
|
return true;
|
|
}
|
|
|
|
|
|
QList<XCursorThemeFX::tAnimSeq> XCursorThemeFX::parseScript (const QString &script, quint32 maxFrame) {
|
|
QList<tAnimSeq> res;
|
|
QString scp = script; scp.replace("\r", "\n");
|
|
QStringList scpL = scp.split('\n', QString::SkipEmptyParts);
|
|
foreach (QString s, scpL) {
|
|
s = s.simplified();
|
|
//qDebug() << s;
|
|
QStringList fld = s.split(',', QString::SkipEmptyParts); //BUG!BUG!BUG!
|
|
if (fld.size() != 2) {
|
|
qDebug() << "script error:" << s;
|
|
qWarning() << "script error:" << s;
|
|
continue;
|
|
}
|
|
// frame[s]
|
|
int hyph = fld[0].indexOf('-');
|
|
tAnimSeq a;
|
|
if (hyph == -1) {
|
|
// just a number
|
|
if (!str2num(fld[0], a.from)) {
|
|
qDebug() << "script error (frame):" << s;
|
|
qWarning() << "script error (frame):" << s;
|
|
continue;
|
|
}
|
|
a.from = qMax(qMin(maxFrame, a.from), (quint32)1)-1;
|
|
a.to = a.from;
|
|
} else {
|
|
// a..b
|
|
if (!str2num(fld[0].left(hyph), a.from)) {
|
|
qDebug() << "script error (frame from):" << s;
|
|
qWarning() << "script error (frame from):" << s;
|
|
continue;
|
|
}
|
|
a.from = qMax(qMin(maxFrame, a.from), (quint32)1)-1;
|
|
if (!str2num(fld[0].mid(hyph+1), a.to)) {
|
|
qDebug() << "script error (frame to):" << s;
|
|
qWarning() << "script error (frame to):" << s;
|
|
continue;
|
|
}
|
|
a.to = qMax(qMin(maxFrame, a.to), (quint32)1)-1;
|
|
}
|
|
// delay
|
|
if (!str2num(fld[1], a.delay)) {
|
|
qDebug() << "script error (delay):" << s;
|
|
qWarning() << "script error (delay):" << s;
|
|
continue;
|
|
}
|
|
if (a.delay < 10) a.delay = 10;
|
|
qDebug() << "from" << a.from << "to" << a.to << "delay" << a.delay;
|
|
res << a;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
static QByteArray zlibInflate (const void *buf, int bufSz, int destSz) {
|
|
QByteArray res;
|
|
z_stream stream;
|
|
int err;
|
|
|
|
res.resize(destSz+1);
|
|
stream.next_in = (Bytef *)buf;
|
|
stream.avail_in = bufSz;
|
|
stream.zalloc = (alloc_func)0;
|
|
stream.zfree = (free_func)0;
|
|
stream.next_out = (Bytef *)res.data();
|
|
stream.avail_out = destSz;
|
|
|
|
err = inflateInit(&stream);
|
|
if (err != Z_OK) return QByteArray();
|
|
err = inflate(&stream, Z_SYNC_FLUSH);
|
|
fprintf(stderr, "inflate result: %i\n", err);
|
|
switch (err) {
|
|
case Z_STREAM_END:
|
|
err = inflateEnd(&stream);
|
|
fprintf(stderr, "Z_STREAM_END: inflate result: %i\n", err);
|
|
if (err != Z_OK) return QByteArray();
|
|
break;
|
|
case Z_OK:
|
|
err = inflateEnd(&stream);
|
|
fprintf(stderr, "Z_OK: inflate result: %i\n", err);
|
|
if (err != Z_OK) return QByteArray();
|
|
break;
|
|
default: return QByteArray();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
static quint32 baGetDW (QByteArray &ba, int &pos) {
|
|
const uchar *d = (const uchar *)ba.constData();
|
|
d += pos+3;
|
|
pos += 4;
|
|
quint32 res = 0;
|
|
for (int f = 4; f > 0; f--, d--) {
|
|
res <<= 8;
|
|
res |= *d;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
XCursorThemeFX::XCursorThemeFX (const QString &aFileName) : XCursorTheme() {
|
|
if (!parseCursorFXTheme(aFileName)) {
|
|
qDeleteAll(mList);
|
|
mList.clear();
|
|
}
|
|
}
|
|
|
|
|
|
bool XCursorThemeFX::parseCursorFXTheme (const QString &aFileName) {
|
|
qDebug() << "loading" << aFileName;
|
|
QFile fl(aFileName);
|
|
if (!fl.open(QIODevice::ReadOnly)) return false; // shit!
|
|
QByteArray ba(fl.readAll());
|
|
fl.close();
|
|
if (ba.size() < 0xb8) return false; // shit!
|
|
int pos = 0;
|
|
if (baGetDW(ba, pos) != 1) return false; // invalid version
|
|
quint32 mainHdrSize = baGetDW(ba, pos);
|
|
if (mainHdrSize < 0xb8) return false; // invalid header size
|
|
quint32 unDataSize = baGetDW(ba, pos);
|
|
if (unDataSize < 0x4c) return false; // no cursors anyway
|
|
struct {
|
|
quint32 ofs;
|
|
quint32 len;
|
|
} infoFields[6];
|
|
pos = 0x84;
|
|
for (int f = 0; f < 6; f++) {
|
|
infoFields[f].ofs = baGetDW(ba, pos);
|
|
infoFields[f].len = baGetDW(ba, pos);
|
|
}
|
|
pos = 0xb4;
|
|
quint32 ihdrSize = baGetDW(ba, pos);
|
|
// now read data
|
|
pos = mainHdrSize;
|
|
qDebug() << "reading data from" << pos;
|
|
QByteArray unp(zlibInflate(ba.constData()+pos, ba.size()-pos, unDataSize));
|
|
if (unp.isEmpty()) {
|
|
qDebug() << "CursorFX: can't depack data";
|
|
qWarning() << "CursorFX: can't depack data";
|
|
return false;
|
|
}
|
|
// process info section
|
|
for (int f = 0; f < 6; f++) {
|
|
int len = infoFields[f].len;
|
|
if (!len) continue;
|
|
pos = infoFields[f].ofs;
|
|
if ((quint32)pos >= ihdrSize || (quint32)pos+len >= ihdrSize) continue; // skip invalid one
|
|
QByteArray sBA(unp.mid(pos, len));
|
|
sBA.append('\0'); sBA.append('\0');
|
|
QString s = QString::fromUtf16((const ushort *)sBA.constData()).simplified();
|
|
switch (f) {
|
|
case 0: setTitle(s); break;
|
|
case 1: setAuthor(s); break;
|
|
//case 2: setVersion(s); break;
|
|
case 3: setSite(s); break;
|
|
case 4: setMail(s); break;
|
|
case 5: setDescr(s); break;
|
|
default: ;
|
|
}
|
|
}
|
|
// process resources
|
|
QSet<quint8> shapesSeen;
|
|
pos = ihdrSize;
|
|
qDebug() << "resources started at hex" << QString::number(pos, 16);
|
|
while (pos <= unp.size()-12) {
|
|
quint32 ipos = pos; // will be fixed later
|
|
quint32 rtype = baGetDW(unp, pos);
|
|
quint32 basicHdrSize = baGetDW(unp, pos);
|
|
quint32 itemSize = baGetDW(unp, pos);
|
|
qDebug() << "pos hex:" << QString::number(pos, 16) << "rtype:" << rtype << "bhdrsz hex:" << QString::number(basicHdrSize, 16) << "itemsz hex:" << QString::number(itemSize, 16);
|
|
if (itemSize < 12) {
|
|
// invalid data
|
|
qDebug() << "CursorFX: invalid data chunk size";
|
|
qWarning() << "CursorFX: invalid data chunk size";
|
|
return false;
|
|
}
|
|
pos = ipos+itemSize; // skip it
|
|
if (rtype != 2) continue; // not cursor resource
|
|
if (itemSize < 0x4c) {
|
|
qDebug() << "CursorFX: invalid cursor chunk size:" << itemSize;
|
|
qWarning() << "CursorFX: invalid cursor chunk size:" << itemSize;
|
|
return false;
|
|
}
|
|
// cursor
|
|
int cps = ipos+3*4;
|
|
rtype = baGetDW(unp, cps);
|
|
if (rtype != 2) {
|
|
qDebug() << "CursorFX: invalid cursor chunk type:" << rtype;
|
|
qWarning() << "CursorFX: invalid cursor chunk type:" << rtype;
|
|
return false;
|
|
}
|
|
quint32 curShape = baGetDW(unp, cps);
|
|
quint32 curType = baGetDW(unp, cps);
|
|
if (curShape > 19) {
|
|
qDebug() << "CursorFX: unknown cursor shape:" << curShape;
|
|
qWarning() << "CursorFX: unknown cursor shape:" << curShape;
|
|
return false;
|
|
}
|
|
if (curType != 1) {
|
|
qDebug() << "skipping 'press' cursor; shape no" << curShape << "named" << curShapeName[curShape];
|
|
continue;
|
|
// we need only 'normal' cursors
|
|
}
|
|
qDebug() << "cursor shape:" << curShape;
|
|
const char **nlst = findCursorByFXId((int)curShape);
|
|
if (!nlst) {
|
|
// unknown cursor type, skip it
|
|
qDebug() << "CursorFX: skipping cursor shape:" << curShapeName[curShape];
|
|
qWarning() << "CursorFX: skipping cursor shape:" << curShapeName[curShape];
|
|
continue;
|
|
}
|
|
if (shapesSeen.contains(curShape&0xff)) {
|
|
// unknown cursor type, skip it
|
|
qDebug() << "CursorFX: skipping duplicate cursor shape:" << curShapeName[curShape];
|
|
qWarning() << "CursorFX: skipping duplicate cursor shape:" << curShapeName[curShape];
|
|
continue;
|
|
}
|
|
shapesSeen << (curShape&0xff);
|
|
qDebug() << "importing cursor" << *nlst;
|
|
quint32 unk0 = baGetDW(unp, cps); // unknown field
|
|
quint32 frameCnt = baGetDW(unp, cps);
|
|
if (frameCnt < 1) frameCnt = 1; // just in case
|
|
quint32 imgWdt = baGetDW(unp, cps);
|
|
quint32 imgHgt = baGetDW(unp, cps);
|
|
quint32 imgDelay = baGetDW(unp, cps);
|
|
quint32 aniFlags = baGetDW(unp, cps);
|
|
quint32 unk1 = baGetDW(unp, cps); // unknown field
|
|
quint32 imgXHot = baGetDW(unp, cps);
|
|
quint32 imgYHot = baGetDW(unp, cps);
|
|
quint32 realHdrSize = baGetDW(unp, cps);
|
|
quint32 imgDataSize = baGetDW(unp, cps);
|
|
quint32 addonOfs = baGetDW(unp, cps);
|
|
quint32 addonLen = baGetDW(unp, cps);
|
|
if (imgDelay < 10) imgDelay = 10;
|
|
if (imgDelay >= (quint32)0x80000000LL) imgDelay = 0x7fffffffLL;
|
|
qDebug() <<
|
|
"cursor data:" <<
|
|
"\n frames:" << frameCnt <<
|
|
"\n width:" << imgWdt <<
|
|
"\n height:" << imgHgt <<
|
|
"\n delay:" << imgDelay <<
|
|
"\n flags:" << QString::number(aniFlags, 2) <<
|
|
"\n xhot:" << imgXHot <<
|
|
"\n yhot:" << imgYHot <<
|
|
"\n unk0:" << unk0 <<
|
|
"\n unk1:" << unk1 <<
|
|
"\n rhdata:" << QString::number(realHdrSize, 16) <<
|
|
"\n dataOfs:" << QString::number(ipos+realHdrSize, 16) <<
|
|
"\n cdataOfs:" << QString::number(ipos+basicHdrSize+addonLen, 16)
|
|
;
|
|
// now check if we have enought data
|
|
if (ipos+realHdrSize+imgDataSize > (quint32)pos) {
|
|
qDebug() << "CursorFX: cursor data too big";
|
|
qWarning() << "CursorFX: cursor data too big";
|
|
return false;
|
|
}
|
|
// addon is the script; parse it later
|
|
QList<tAnimSeq> aseq;
|
|
QString script;
|
|
if (addonLen) {
|
|
// script
|
|
if (addonOfs < 0x4c || addonOfs > realHdrSize) {
|
|
qDebug() << "CursorFX: invalid addon data offset";
|
|
qWarning() << "CursorFX: invalid addon data offset";
|
|
return false;
|
|
}
|
|
QByteArray bs(unp.mid(ipos+addonOfs, addonLen));
|
|
bs.append('\0'); bs.append('\0');
|
|
script = QString::fromUtf16((const ushort *)bs.constData());
|
|
qDebug() << "script:\n" << script;
|
|
// create animseq
|
|
aseq = parseScript(script, frameCnt);
|
|
} else {
|
|
// create 'standard' animseq
|
|
tAnimSeq a;
|
|
a.from = 0; a.to = frameCnt-1; a.delay = imgDelay;
|
|
aseq << a;
|
|
// and back if 'alternate' flag set
|
|
if (aniFlags&0x01) {
|
|
a.from = frameCnt-1; a.to = 0;
|
|
aseq << a;
|
|
}
|
|
}
|
|
if (imgWdt*imgHgt*4 != imgDataSize) {
|
|
qDebug() << "data size:" << imgDataSize << "but should be" << imgWdt*imgHgt*4;
|
|
continue;
|
|
}
|
|
// decode image
|
|
QImage img((const uchar *)unp.constData()+ipos+realHdrSize, imgWdt, imgHgt, QImage::Format_ARGB32);
|
|
img = img.mirrored(false, true);
|
|
//
|
|
XCursorImages *cim = new XCursorImages(*nlst);
|
|
cim->setScript(script);
|
|
//!!!
|
|
//!!!img.save(QString("_png/%1.png").arg(cim->name()));
|
|
//!!!
|
|
quint32 frameWdt = img.width()/frameCnt;
|
|
qDebug() << "frameWdt:" << frameWdt << "left:" << img.width()%(frameWdt*frameCnt);
|
|
// now build animation sequence
|
|
int fCnt = 0;
|
|
foreach (const tAnimSeq &a, aseq) {
|
|
bool back = a.from > a.to; // going backwards
|
|
quint32 fNo = a.from;
|
|
for (;; fCnt++) {
|
|
//k8:qDebug() << " frame:" << fNo << "delay:" << a.delay;
|
|
// copy frame
|
|
QImage frame(img.copy(fNo*frameWdt, 0, frameWdt, img.height()));
|
|
//frame.save(QString("_png/%1_%2.png").arg(cim->name()).arg(QString::number(f)));
|
|
XCursorImage *i = new XCursorImage(QString("%1%2").arg(cim->name()).arg(QString::number(fCnt)),
|
|
frame, imgXHot, imgYHot, a.delay, 1
|
|
);
|
|
cim->append(i);
|
|
//
|
|
if (fNo == a.to) break;
|
|
if (back) fNo--; else fNo++;
|
|
}
|
|
}
|
|
// append if not empty
|
|
if (cim->count()) {
|
|
// now check if it is looped and cancel looping if necessary
|
|
if ((aniFlags & 0x02) == 0) {
|
|
// not looped
|
|
qDebug() << " anti-loop fix";
|
|
XCursorImage *i = cim->item(cim->count()-1);
|
|
i->setDelay(0x7fffffffL);
|
|
//i->setCSize(2); // ???
|
|
}
|
|
mList << cim;
|
|
}
|
|
}
|
|
return true;
|
|
}
|