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