1 /* coded by Ketmar // Vampire Avalon (psyc://ketmar.no-ip.org/~Ketmar)
2  * (c)DWTFYW
3  *
4  * This program is free software. It comes without any warranty, to
5  * the extent permitted by applicable law. You can redistribute it
6  * and/or modify it under the terms of the Do What The Fuck You Want
7  * To Public License, Version 2, as published by Sam Hocevar. See
8  * http://sam.zoy.org/wtfpl/COPYING for more details.
9  */
10 #include <QDebug>
11 //#include <QtCore>
12 
13 #include "xcrthemefx.h"
14 
15 #include <unistd.h>
16 #include <zlib.h>
17 
18 #include <QSet>
19 #include <QStringList>
20 #include <QTextStream>
21 
22 #include "xcrimg.h"
23 #include "xcrxcur.h"
24 #include "xcrtheme.h"
25 
26 
27 static const char *curShapeName[] = {
28   "standard arrow",
29   "help arrow (the one with '?')",
30   "working arrow",
31   "busy cursor",
32   "precision select",
33   "text select",
34   "handwriting",
35   "unavailable",
36   "north (vert) resize",
37   "south resize",
38   "west (vert-means horiz?) resize",
39   "east resize",
40   "north-west resize",
41   "south-east resize",
42   "north-east resize",
43   "south-west resize",
44   "move",
45   "alternate select",
46   "hand",
47   "button"
48 };
49 
50 
str2num(const QString & s,quint32 & res)51 bool XCursorThemeFX::str2num (const QString &s, quint32 &res) {
52   quint64 n = 0;
53   if (s.isEmpty()) return false;
54   for (int f = 0; f < s.length(); f++) {
55     QChar ch = s.at(f);
56     if (!ch.isDigit()) return false;
57     n = n*10+ch.unicode()-'0';
58   }
59   //if (n >= (quint64)0x100000000LL) n = 0xffffffffLL;
60   if (n >= (quint64)0x80000000LL) n = 0x7fffffffLL;
61   res = n;
62   return true;
63 }
64 
65 
parseScript(const QString & script,quint32 maxFrame)66 QList<XCursorThemeFX::tAnimSeq> XCursorThemeFX::parseScript (const QString &script, quint32 maxFrame) {
67   QList<tAnimSeq> res;
68   QString scp = script; scp.replace(QLatin1String("\r"), QLatin1String("\n"));
69   const QStringList scpL = scp.split(QLatin1Char('\n'), Qt::SkipEmptyParts);
70   for (const QString &ss : scpL) {
71     const QString s = ss.simplified();
72     //qDebug() << s;
73     QStringList fld = s.split(QLatin1Char(','), Qt::SkipEmptyParts); //BUG!BUG!BUG!
74     if (fld.size() != 2) {
75      qDebug() << "script error:" << s;
76       qWarning() << "script error:" << s;
77       continue;
78     }
79     // frame[s]
80     int hyph = fld[0].indexOf(QLatin1Char('-'));
81     tAnimSeq a;
82     if (hyph == -1) {
83       // just a number
84       if (!str2num(fld[0], a.from)) {
85        qDebug() << "script error (frame):" << s;
86         qWarning() << "script error (frame):" << s;
87         continue;
88       }
89       a.from = qMax(qMin(maxFrame, a.from), (quint32)1)-1;
90       a.to = a.from;
91     } else {
92       // a..b
93       if (!str2num(fld[0].left(hyph), a.from)) {
94        qDebug() << "script error (frame from):" << s;
95         qWarning() << "script error (frame from):" << s;
96         continue;
97       }
98       a.from = qMax(qMin(maxFrame, a.from), (quint32)1)-1;
99       if (!str2num(fld[0].mid(hyph+1), a.to)) {
100        qDebug() << "script error (frame to):" << s;
101         qWarning() << "script error (frame to):" << s;
102         continue;
103       }
104       a.to = qMax(qMin(maxFrame, a.to), (quint32)1)-1;
105     }
106     // delay
107     if (!str2num(fld[1], a.delay)) {
108      qDebug() << "script error (delay):" << s;
109       qWarning() << "script error (delay):" << s;
110       continue;
111     }
112     if (a.delay < 10) a.delay = 10;
113     qDebug() << "from" << a.from << "to" << a.to << "delay" << a.delay;
114     res << a;
115   }
116   return res;
117 }
118 
119 
120 ///////////////////////////////////////////////////////////////////////////////
zlibInflate(const void * buf,int bufSz,int destSz)121 static QByteArray zlibInflate (const void *buf, int bufSz, int destSz) {
122   QByteArray res;
123   z_stream stream;
124   int err;
125 
126   res.resize(destSz+1);
127   stream.next_in = (Bytef *)buf;
128   stream.avail_in = bufSz;
129   stream.zalloc = (alloc_func)nullptr;
130   stream.zfree = (free_func)nullptr;
131   stream.next_out = (Bytef *)res.data();
132   stream.avail_out = destSz;
133 
134   err = inflateInit(&stream);
135   if (err != Z_OK) return QByteArray();
136   err = inflate(&stream, Z_SYNC_FLUSH);
137   fprintf(stderr, "inflate result: %i\n", err);
138   switch (err) {
139     case Z_STREAM_END:
140       err = inflateEnd(&stream);
141       fprintf(stderr, "Z_STREAM_END: inflate result: %i\n", err);
142       if (err != Z_OK) return QByteArray();
143       break;
144     case Z_OK:
145       err = inflateEnd(&stream);
146       fprintf(stderr, "Z_OK: inflate result: %i\n", err);
147       if (err != Z_OK) return QByteArray();
148       break;
149     default: return QByteArray();
150   }
151   return res;
152 }
153 
154 
baGetDW(QByteArray & ba,int & pos)155 static quint32 baGetDW (QByteArray &ba, int &pos) {
156   const uchar *d = (const uchar *)ba.constData();
157   d += pos+3;
158   pos += 4;
159   quint32 res = 0;
160   for (int f = 4; f > 0; f--, d--) {
161     res <<= 8;
162     res |= *d;
163   }
164   return res;
165 }
166 
167 
168 ///////////////////////////////////////////////////////////////////////////////
XCursorThemeFX(const QString & aFileName)169 XCursorThemeFX::XCursorThemeFX (const QString &aFileName) : XCursorTheme() {
170   if (!parseCursorFXTheme(aFileName)) {
171     qDeleteAll(mList);
172     mList.clear();
173   }
174 }
175 
176 
parseCursorFXTheme(const QString & aFileName)177 bool XCursorThemeFX::parseCursorFXTheme (const QString &aFileName) {
178  qDebug() << "loading" << aFileName;
179   QFile fl(aFileName);
180   if (!fl.open(QIODevice::ReadOnly)) return false; // shit!
181   QByteArray ba(fl.readAll());
182   fl.close();
183   if (ba.size() < 0xb8) return false; // shit!
184   int pos = 0;
185   if (baGetDW(ba, pos) != 1) return false; // invalid version
186   quint32 mainHdrSize = baGetDW(ba, pos);
187   if (mainHdrSize < 0xb8) return false; // invalid header size
188   quint32 unDataSize = baGetDW(ba, pos);
189   if (unDataSize < 0x4c) return false; // no cursors anyway
190   struct {
191     quint32 ofs;
192     quint32 len;
193   } infoFields[6];
194   pos = 0x84;
195   for (int f = 0; f < 6; f++) {
196     infoFields[f].ofs = baGetDW(ba, pos);
197     infoFields[f].len = baGetDW(ba, pos);
198   }
199   pos = 0xb4;
200   quint32 ihdrSize = baGetDW(ba, pos);
201   // now read data
202   pos = mainHdrSize;
203  qDebug() << "reading data from" << pos;
204   QByteArray unp(zlibInflate(ba.constData()+pos, ba.size()-pos, unDataSize));
205   if (unp.isEmpty()) {
206    qDebug() << "CursorFX: can't depack data";
207     qWarning() << "CursorFX: can't depack data";
208     return false;
209   }
210   // process info section
211   for (int f = 0; f < 6; f++) {
212     int len = infoFields[f].len;
213     if (!len) continue;
214     pos = infoFields[f].ofs;
215     if ((quint32)pos >= ihdrSize || (quint32)pos+len >= ihdrSize) continue; // skip invalid one
216     QByteArray sBA(unp.mid(pos, len));
217     sBA.append('\0'); sBA.append('\0');
218     QString s = QString::fromUtf16((const ushort *)sBA.constData()).simplified();
219     switch (f) {
220       case 0: setTitle(s); break;
221       case 1: setAuthor(s); break;
222       //case 2: setVersion(s); break;
223       case 3: setSite(s); break;
224       case 4: setMail(s); break;
225       case 5: setDescr(s); break;
226       default: ;
227     }
228   }
229   // process resources
230   QSet<quint8> shapesSeen;
231   pos = ihdrSize;
232  qDebug() << "resources started at hex" << QString::number(pos, 16);
233   while (pos <= unp.size()-12) {
234     quint32 ipos = pos; // will be fixed later
235     quint32 rtype = baGetDW(unp, pos);
236     quint32 basicHdrSize = baGetDW(unp, pos);
237     quint32 itemSize = baGetDW(unp, pos);
238    qDebug() << "pos hex:" << QString::number(pos, 16) << "rtype:" << rtype << "bhdrsz hex:" << QString::number(basicHdrSize, 16) << "itemsz hex:" << QString::number(itemSize, 16);
239     if (itemSize < 12) {
240       // invalid data
241      qDebug() << "CursorFX: invalid data chunk size";
242       qWarning() << "CursorFX: invalid data chunk size";
243       return false;
244     }
245     pos = ipos+itemSize; // skip it
246     if (rtype != 2) continue; // not cursor resource
247     if (itemSize < 0x4c) {
248      qDebug() << "CursorFX: invalid cursor chunk size:" << itemSize;
249       qWarning() << "CursorFX: invalid cursor chunk size:" << itemSize;
250       return false;
251     }
252     // cursor
253     int cps = ipos+3*4;
254     rtype = baGetDW(unp, cps);
255     if (rtype != 2) {
256      qDebug() << "CursorFX: invalid cursor chunk type:" << rtype;
257       qWarning() << "CursorFX: invalid cursor chunk type:" << rtype;
258       return false;
259     }
260     quint32 curShape = baGetDW(unp, cps);
261     quint32 curType = baGetDW(unp, cps);
262     if (curShape > 19) {
263      qDebug() << "CursorFX: unknown cursor shape:" << curShape;
264       qWarning() << "CursorFX: unknown cursor shape:" << curShape;
265       return false;
266     }
267     if (curType != 1) {
268      qDebug() << "skipping 'press' cursor; shape no" << curShape << "named" << curShapeName[curShape];
269       continue;
270       // we need only 'normal' cursors
271     }
272    qDebug() << "cursor shape:" << curShape;
273     const char **nlst = findCursorByFXId((int)curShape);
274     if (!nlst) {
275       // unknown cursor type, skip it
276      qDebug() << "CursorFX: skipping cursor shape:" << curShapeName[curShape];
277       qWarning() << "CursorFX: skipping cursor shape:" << curShapeName[curShape];
278       continue;
279     }
280     if (shapesSeen.contains(curShape&0xff)) {
281       // unknown cursor type, skip it
282      qDebug() << "CursorFX: skipping duplicate cursor shape:" << curShapeName[curShape];
283       qWarning() << "CursorFX: skipping duplicate cursor shape:" << curShapeName[curShape];
284       continue;
285     }
286     shapesSeen << (curShape&0xff);
287    qDebug() << "importing cursor" << *nlst;
288     quint32 unk0 = baGetDW(unp, cps); // unknown field
289     quint32 frameCnt = baGetDW(unp, cps);
290     if (frameCnt < 1) frameCnt = 1; // just in case
291     quint32 imgWdt = baGetDW(unp, cps);
292     quint32 imgHgt = baGetDW(unp, cps);
293     quint32 imgDelay = baGetDW(unp, cps);
294     quint32 aniFlags = baGetDW(unp, cps);
295     quint32 unk1 = baGetDW(unp, cps); // unknown field
296     quint32 imgXHot = baGetDW(unp, cps);
297     quint32 imgYHot = baGetDW(unp, cps);
298     quint32 realHdrSize = baGetDW(unp, cps);
299     quint32 imgDataSize = baGetDW(unp, cps);
300     quint32 addonOfs = baGetDW(unp, cps);
301     quint32 addonLen = baGetDW(unp, cps);
302     if (imgDelay < 10) imgDelay = 10;
303     if (imgDelay >= (quint32)0x80000000LL) imgDelay = 0x7fffffffLL;
304    qDebug() <<
305      "cursor data:" <<
306      "\n  frames:" << frameCnt <<
307      "\n  width:" << imgWdt <<
308      "\n  height:" << imgHgt <<
309      "\n  delay:" << imgDelay <<
310      "\n  flags:" << QString::number(aniFlags, 2) <<
311      "\n  xhot:" << imgXHot <<
312      "\n  yhot:" << imgYHot <<
313      "\n  unk0:" << unk0 <<
314      "\n  unk1:" << unk1 <<
315      "\n  rhdata:" << QString::number(realHdrSize, 16) <<
316      "\n  dataOfs:" << QString::number(ipos+realHdrSize, 16) <<
317      "\n  cdataOfs:" << QString::number(ipos+basicHdrSize+addonLen, 16)
318    ;
319     // now check if we have enought data
320     if (ipos+realHdrSize+imgDataSize > (quint32)pos) {
321      qDebug() << "CursorFX: cursor data too big";
322       qWarning() << "CursorFX: cursor data too big";
323       return false;
324     }
325     // addon is the script; parse it later
326     QList<tAnimSeq> aseq;
327     QString script;
328     if (addonLen) {
329       // script
330       if (addonOfs < 0x4c || addonOfs > realHdrSize) {
331        qDebug() << "CursorFX: invalid addon data offset";
332         qWarning() << "CursorFX: invalid addon data offset";
333         return false;
334       }
335       QByteArray bs(unp.mid(ipos+addonOfs, addonLen));
336       bs.append('\0'); bs.append('\0');
337       script = QString::fromUtf16((const ushort *)bs.constData());
338       qDebug() << "script:\n" << script;
339       // create animseq
340       aseq = parseScript(script, frameCnt);
341     } else {
342       // create 'standard' animseq
343       tAnimSeq a;
344       a.from = 0; a.to = frameCnt-1; a.delay = imgDelay;
345       aseq << a;
346       // and back if 'alternate' flag set
347       if (aniFlags&0x01) {
348         a.from = frameCnt-1; a.to = 0;
349         aseq << a;
350       }
351     }
352     if (imgWdt*imgHgt*4 != imgDataSize) {
353      qDebug() << "data size:" << imgDataSize << "but should be" << imgWdt*imgHgt*4;
354       continue;
355     }
356     // decode image
357     QImage img((const uchar *)unp.constData()+ipos+realHdrSize, imgWdt, imgHgt, QImage::Format_ARGB32);
358     img = img.mirrored(false, true);
359     //
360     XCursorImages *cim = new XCursorImages(QString::fromUtf8(*nlst));
361     cim->setScript(script);
362     //!!!
363     //!!!img.save(QString("_png/%1.png").arg(cim->name()));
364     //!!!
365     quint32 frameWdt = img.width()/frameCnt;
366    qDebug() << "frameWdt:" << frameWdt << "left:" << img.width()%(frameWdt*frameCnt);
367     // now build animation sequence
368     int fCnt = 0;
369     for (const tAnimSeq &a : qAsConst(aseq)) {
370       bool back = a.from > a.to; // going backwards
371       quint32 fNo = a.from;
372       for (;; fCnt++) {
373        //k8:qDebug() << "  frame:" << fNo << "delay:" << a.delay;
374         // copy frame
375         QImage frame(img.copy(fNo*frameWdt, 0, frameWdt, img.height()));
376         //frame.save(QString("_png/%1_%2.png").arg(cim->name()).arg(QString::number(f)));
377         XCursorImage *i = new XCursorImage(QStringLiteral("%1%2").arg(cim->name(), QString::number(fCnt)),
378           frame, imgXHot, imgYHot, a.delay, 1
379         );
380         cim->append(i);
381         //
382         if (fNo == a.to) break;
383         if (back) fNo--; else fNo++;
384       }
385     }
386     // append if not empty
387     if (cim->count()) {
388       // now check if it is looped and cancel looping if necessary
389       if ((aniFlags & 0x02) == 0) {
390         // not looped
391        qDebug() << "  anti-loop fix";
392         XCursorImage *i = cim->item(cim->count()-1);
393         i->setDelay(0x7fffffffL);
394         //i->setCSize(2); // ???
395       }
396       mList << cim;
397     }
398   }
399   return true;
400 }
401