Subversion Repositories tpanel

Rev

Rev 475 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
446 andreas 1
/*
475 andreas 2
 * Copyright (C) 2022 to 2024 by Andreas Theofilu <andreas@theosys.at>
446 andreas 3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software Foundation,
16
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
17
 */
18
 
19
#include <iostream>
20
#include <fstream>
21
#include <cstring>
22
#include <iomanip>
23
#include <algorithm>
24
#include <sstream>
25
#ifdef __LINUX__
26
#include <bits/stdc++.h>
27
#endif
28
 
29
#include "readtp4.h"
30
#include "expand.h"
475 andreas 31
#include "tscramble.h"
446 andreas 32
#include "terror.h"
33
 
34
#if __cplusplus < 201402L
35
#   error "This module requires at least C++14 standard!"
36
#else
37
#   if __cplusplus < 201703L
38
#       include <experimental/filesystem>
39
        namespace fs = std::experimental::filesystem;
40
#       warning "Support for C++14 and experimental filesystem will be removed in a future version!"
41
#   else
42
#       include <filesystem>
43
#       ifdef __ANDROID__
44
            namespace fs = std::__fs::filesystem;
45
#       else
46
            namespace fs = std::filesystem;
47
#       endif
48
#   endif
49
#endif
50
 
51
using namespace reader;
52
using namespace std;
53
 
476 andreas 54
/**
55
 * @brief Password
56
 * This definitions are the password and salt used to encrypt all XML files
57
 * in a TP5 file (produced by TPDesign5).
58
 * TPanel is using it to decrypt the files. All files other then XML files
59
 * are not encrypted or zipped.
60
 * 
61
 * The encryption was done with the following settings:
62
 *  Cipher: AES-128-CBC
63
 *  Digest: SHA1
64
 *  Iterations: 5
65
 */
66
string password = "8P0puxB5OVUFI6uX";
67
string salt = "MarkRobs";
68
 
446 andreas 69
ReadTP4::~ReadTP4()
70
{
71
    DECL_TRACER("ReadTP4::~ReadTP4()");
72
 
73
    deleteIndex();
74
}
75
 
76
bool ReadTP4::isReady()
77
{
78
    DECL_TRACER("ReadTP4::isReady()");
79
 
80
    if (access(fname.c_str(), R_OK) != 0)
81
        return false;
82
 
83
    if (access(target.c_str(), F_OK) == 0)
84
    {
85
        if (access(target.c_str(), R_OK | W_OK | X_OK) != 0)
86
            return false;
87
 
88
        return true;
89
    }
90
 
91
    return true;
92
}
93
 
94
bool ReadTP4::doRead()
95
{
96
    if (fname.length() == 0)
97
    {
98
        MSG_ERROR("Missing input file!");
99
        return false;
100
    }
101
 
102
    ifstream fn(fname, ios::in | ios::binary);
103
 
104
    if (!fn.is_open())
105
    {
106
        MSG_ERROR("Could not open file " << fname << "!");
107
        return false;
108
    }
109
 
110
    struct HEADER head;
111
    struct BLOCK block;
112
    struct USAGE_BLOCK usageBlock;
113
    struct FILE_HEAD fhead;
114
 
115
    unsigned char memblock[1024];
116
 
117
    try
118
    {
119
        fn.read(reinterpret_cast<char*>(&head), SIZE_HEADER);
120
 
121
        if (memcmp(head.abyFileID, "\0FSFILE\0", 8) != 0)
122
        {
123
            MSG_ERROR("File " << fname << " is not an FSF file!");
124
            fn.close();
125
            return false;
126
        }
127
 
128
        fn.read(reinterpret_cast<char*>(&memblock[0]), SIZE_BLOCK);
129
        fillBlock(block, memblock);
130
        fillFileHead(fhead, memblock);
131
 
132
        uint32_t nextBlock = head.listStartBlock;
133
 
134
        // first we read the index
135
        while (nextBlock > 0)
136
        {
137
            fn.seekg(calcBlockPos(nextBlock), ios::beg);
138
            fn.read(reinterpret_cast<char*>(memblock), SIZE_BLOCK);
139
            fillUsageBlock(usageBlock, memblock);
140
            fillFileHead(fhead, memblock);
141
 
142
            if (fhead.thisBlock != nextBlock)
143
            {
144
                MSG_ERROR("No valid block position (" << to_string(fn.tellg()) << " [" << toHex(fn.tellg(), 8) << "])");
145
                break;
146
            }
147
 
148
            appendUBlock(&usageBlock);
149
            nextBlock = fhead.nextBlock;
150
        }
151
 
152
        // Now we read the files
153
        struct INDEX *act = idx;
154
 
155
        while (act != nullptr)
156
        {
157
            string ofile;
158
 
159
            string f(cp1250ToUTF8((char *)act->ublock->filePath));
160
            struct MANIFEST mf;
161
            mf.size = act->ublock->sizeBytes;
162
            mf.tmCreate = act->ublock->tmCreate;
163
            mf.tmModify = act->ublock->tmModify;
164
            mf.fname.assign(f);
165
            manifest.push_back(mf);
166
            string dir;
167
 
168
            if (f.find(".png") != string::npos || f.find(".jpg") != string::npos ||
169
                    f.find(".gif") != string::npos || f.find(".tiff") != string::npos)
170
                dir = "/images";
171
            else if (f.find(".wav") != string::npos || f.find(".mp3") != string::npos)
172
                dir = "/sounds";
173
            else if (f.find(".ttf") != string::npos)
174
                dir = "/fonts";
175
 
176
            if (!fs::exists(target + dir))
177
                fs::create_directories(target + dir);
178
 
179
            ofile = target + dir + "/" + f;
180
 
181
            MSG_DEBUG("Writing file " << ofile);
182
            ofstream of(ofile, ios::out | ios::binary);
183
 
184
            if (!of)
185
            {
186
                MSG_ERROR("Error opening target file" << ofile << "!");
187
                fn.close();
188
                return false;
189
            }
190
 
191
            nextBlock = act->ublock->startBlock;
192
            bool compressed = false;
475 andreas 193
            bool encrypted = false;
446 andreas 194
 
195
            for (uint32_t i = 0; i < act->ublock->sizeBlocks; i++)
196
            {
197
                fn.seekg(calcBlockPos(nextBlock), ios::beg);
198
                fn.read(reinterpret_cast<char*>(memblock), SIZE_BLOCK);
199
                fillBlock(block, memblock);
200
 
201
                if (i == 0 && block.abyData[0] == 0x1f && block.abyData[1] == 0x8b)
202
                    compressed = true;
475 andreas 203
                else if (i == 0 && (ofile.find(".xma") != string::npos || ofile.find(".xml") != string::npos) && block.abyData[0] != '<' && block.abyData[1] != '?')
204
                    encrypted = true;
446 andreas 205
 
206
                nextBlock = block.nextBlock;
207
                of.write(reinterpret_cast<char*>(block.abyData), block.bytesUsed);
208
            }
209
 
210
            of.close();
211
 
212
            if (compressed)
213
            {
214
                MSG_DEBUG("Decompressing file " << ofile << " ...");
215
                Expand exp(ofile);
216
                int ret = exp.unzip();
217
 
218
                if (ret != 0)
219
                    MSG_WARNING("File " << ofile << " was not decompressed!");
220
            }
475 andreas 221
            else if (encrypted)
222
            {
223
                MSG_DEBUG("Decrypting file " << ofile << " ...");
224
                TScramble scramble;
476 andreas 225
                scramble.aesInit(password, salt);
446 andreas 226
 
475 andreas 227
                if (!scramble.aesDecodeFile(ofile))
228
                {
229
                    MSG_WARNING("Error decrypting file " << ofile << "!");
230
                }
231
                else
232
                {
233
                    // Write buffer to file
234
                    ofstream outFile;
235
                    outFile.open(ofile, ios::out | ios::binary | ios::trunc);
236
                    string decr = scramble.getDecrypted();
237
                    outFile.write(decr.c_str(), decr.length());
238
                    outFile.close();
239
                    tp5Type = true;
240
                }
241
            }
242
 
446 andreas 243
            act = act->next;
244
        }
245
 
246
        ofstream mfStream;
247
        string manFileName = target + "/manifest.xma";
248
 
249
        mfStream.open(manFileName, ios::out | ios::binary | ios::trunc);
250
        sort(manifest.begin(), manifest.end(), compareManifest);
251
        size_t num = manifest.size();
252
        size_t cnt = 0;
253
 
254
        for (auto itr = manifest.begin(); itr != manifest.end(); ++itr)
255
        {
256
            mfStream << itr->size << "|" << itr->tmCreate << "|" << itr->tmModify << "|" << itr->fname;
257
            cnt++;
258
 
259
            if (cnt == num)
260
                mfStream << "\r";
261
            else
262
                mfStream << "\r\n";
263
        }
264
 
265
        mfStream.close();
266
    }
267
    catch (exception& e)
268
    {
269
        MSG_ERROR("ReadTP4::doRead: " << e.what());
270
        fn.close();
271
        return false;
272
    }
273
 
274
    fn.close();
275
    return true;
276
}
277
 
278
void ReadTP4::fillBlock(struct BLOCK &bl, const unsigned char *buf)
279
{
280
    bl.thisBlock = makeDWord(buf);
281
    bl.prevBlock = makeDWord(buf + 4);
282
    bl.nextBlock = makeDWord(buf + 8);
283
    bl.bytesUsed = makeWord(buf + 12);
284
    memcpy(&bl.abyData, buf + 14, 512);
285
}
286
 
287
void ReadTP4::fillUsageBlock(struct USAGE_BLOCK &ub, const unsigned char *buf)
288
{
289
    ub.thisBlock = makeDWord(buf);
290
    ub.prevBlock = makeDWord(buf + 4);
291
    ub.nextBlock = makeDWord(buf + 8);
292
    ub.bytesUsed = makeWord(buf + 12);
293
    memcpy(&ub.filePath, buf + 14, 260);
294
    ub.tmCreate = makeDWord(buf + 274);
295
    ub.tmModify = makeDWord(buf + 278);
296
    ub.flags = makeDWord(buf + 282);
297
    ub.startBlock = makeDWord(buf + 286);
298
    ub.sizeBlocks = makeDWord(buf + 290);
299
    ub.sizeBytes = makeDWord(buf + 294);
300
}
301
 
302
void ReadTP4::fillFileHead(struct FILE_HEAD &fh, const unsigned char *buf)
303
{
304
    fh.thisBlock = makeDWord(buf);
305
    fh.prevBlock = makeDWord(buf + 4);
306
    fh.nextBlock = makeDWord(buf + 8);
307
    fh.blockLen = makeWord(buf + 12);
308
}
309
 
310
struct INDEX *ReadTP4::appendUBlock(const struct USAGE_BLOCK *ub)
311
{
312
    struct USAGE_BLOCK *nb;
313
    struct INDEX *act, *nidx;
314
 
315
    try
316
    {
317
        nb = new struct USAGE_BLOCK;
318
        memcpy(nb, ub, sizeof(struct USAGE_BLOCK));
319
 
320
        if (idx == nullptr)
321
        {
322
            act = new struct INDEX;
323
            act->prev = nullptr;
324
            act->next = nullptr;
325
            act->ublock = nb;
326
            idx = act;
327
        }
328
        else
329
        {
330
            act = idx;
331
 
332
            while (act->next != nullptr)
333
                act = act->next;
334
 
335
            nidx = new struct INDEX;
336
            nidx->prev = act;
337
            nidx->next = nullptr;
338
            nidx->ublock = nb;
339
            act->next = nidx;
340
            act = nidx;
341
        }
342
    }
343
    catch (exception& e)
344
    {
345
        MSG_ERROR("ReadTP4::appendUBlock: " << e.what());
346
        exit(3);
347
    }
348
 
349
    return act;
350
}
351
 
352
void ReadTP4::deleteIndex()
353
{
354
    struct INDEX *tmp, *act = idx;
355
 
356
    while (act != nullptr)
357
    {
358
        tmp = act->next;
359
 
360
        if (act->ublock != nullptr)
361
            delete act->ublock;
362
 
363
        delete act;
364
        act = tmp;
365
    }
366
 
367
    idx = nullptr;
368
}
369
 
370
size_t ReadTP4::calcBlockPos(uint32_t block)
371
{
372
    if (block == 0)
373
        return SIZE_HEADER;
374
 
375
    size_t first = SIZE_HEADER;
376
    return (first + (SIZE_BLOCK * block));
377
}
378
 
379
uint32_t ReadTP4::makeDWord(const unsigned char *buf)
380
{
381
    unsigned short b1, b2, b3, b4;
382
 
383
    b1 = *buf;
384
    b2 = *(buf + 1);
385
    b3 = *(buf + 2);
386
    b4 = *(buf + 3);
387
    uint32_t dword = ((b4 << 24) & 0xff000000) | ((b3 << 16) & 0x00ff0000) | ((b2 << 8) & 0x0000ff00) | b1;
388
    return dword;
389
}
390
 
391
uint16_t ReadTP4::makeWord(const unsigned char *buf)
392
{
393
    uint16_t word = ((*(buf + 1) << 8) & 0xff00) | *buf;
394
    return word;
395
}
396
 
397
string ReadTP4::toHex(int num, int width)
398
{
399
    string ret;
400
    std::stringstream stream;
401
    stream << std::setfill('0') << std::setw(width) << std::hex << num;
402
    ret = stream.str();
403
    return ret;
404
}
405
 
406
bool ReadTP4::compareManifest(struct MANIFEST& m1, struct MANIFEST& m2)
407
{
408
    size_t pos1 = m1.fname.find_last_of(".");
409
    size_t pos2 = m2.fname.find_last_of(".");
410
    string ext1, ext2;
411
    int weight1, weight2;
412
 
413
    if (pos1 != string::npos)
414
        ext1 = m1.fname.substr(pos1 + 1);
415
    else
416
        ext1 = m1.fname;
417
 
418
    if (pos2 != string::npos)
419
        ext2 = m2.fname.substr(pos2 + 1);
420
    else
421
        ext2 = m2.fname;
422
 
423
    if (ext1.compare("xma") == 0)
424
        weight1 = 1;
425
    else if (ext1.compare("xml") == 0)
426
        weight1 = 2;
427
    else if (ext1.compare("ttf") == 0)
428
        weight1 = 3;
429
    else if (ext1.compare("png") == 0 || ext1.compare("jpg") == 0 || ext1.compare("gif") == 0)
430
        weight1 = 4;
431
    else
432
        weight1 = 5;
433
 
434
    if (ext2.compare("xma") == 0)
435
        weight2 = 1;
436
    else if (ext2.compare("xml") == 0)
437
        weight2 = 2;
438
    else if (ext2.compare("ttf") == 0)
439
        weight2 = 3;
440
    else if (ext2.compare("png") == 0 || ext2.compare("jpg") == 0 || ext2.compare("gif") == 0)
441
        weight2 = 4;
442
    else
443
        weight2 = 5;
444
 
445
    if (weight1 == weight2)
446
        return m1.fname.compare(m2.fname) < 0;
447
 
448
    return weight1 < weight2;
449
}
450
 
451
string ReadTP4::cp1250ToUTF8(const string& str)
452
{
453
    string out;
454
 
455
    for (size_t j = 0; j < str.length(); j++)
456
    {
457
        int i = -1;
458
        unsigned char ch = str.at(j);
459
        short utf = -1;
460
 
461
        if (ch < 0x80)
462
        {
463
            do
464
            {
465
                i++;
466
 
467
                if (__cht[i].ch == ch)
468
                {
469
                    utf = __cht[i].byte;
470
                    break;
471
                }
472
            }
473
            while (__cht[i].ch != 0xff);
474
 
475
            if (utf < 0)
476
                utf = ch;
477
        }
478
        else
479
            utf = ch;
480
 
481
        if (utf > 0x00ff)
482
        {
483
            out.push_back((utf >> 8) & 0x00ff);
484
            out.push_back(utf & 0x00ff);
485
        }
486
        else if (ch > 0x7f)
487
        {
488
            out.push_back(0xc0 | ch >> 6);
489
            out.push_back(0x80 | (ch & 0x3f));
490
        }
491
        else
492
            out.push_back(ch);
493
    }
494
 
495
    return out;
496
}