GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/dirs.cpp Lines: 29 157 18.5 %
Date: 2021-03-17 Branches: 18 248 7.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2011-2019  The ManaPlus Developers
6
 *  Copyright (C) 2019-2021  Andrei Karas
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
#include "dirs.h"
25
26
#include "client.h"
27
#include "configuration.h"
28
#include "logger.h"
29
#include "main.h"
30
#include "settings.h"
31
32
#include "fs/mkdir.h"
33
#include "fs/paths.h"
34
35
#include "fs/virtfs/fs.h"
36
37
#include "utils/base64.h"
38
#if defined(__native_client__) || (defined(ANDROID) && defined(USE_SDL2))
39
#include "fs/files.h"
40
#endif  // defined(__native_client__) || (defined(ANDROID) &&
41
        // defined(USE_SDL2))
42
43
#include "utils/cast.h"
44
#include "utils/gettext.h"
45
46
#ifdef ANDROID
47
#ifdef USE_SDL2
48
#include "main.h"
49
50
#include "render/graphics.h"
51
#endif  // USE_SDL2
52
#endif  // ANDROID
53
54
#ifdef __APPLE__
55
#include <CoreFoundation/CFBundle.h>
56
#endif  // __APPLE__
57
58
#ifdef WIN32
59
PRAGMA48(GCC diagnostic push)
60
PRAGMA48(GCC diagnostic ignored "-Wshadow")
61
#include <SDL_syswm.h>
62
PRAGMA48(GCC diagnostic pop)
63
#include "fs/specialfolder.h"
64
#undef ERROR
65
#endif  // WIN32
66
67
#include <sys/stat.h>
68
69
#include <sstream>
70
71
#include "debug.h"
72
73
#if defined __native_client__
74
#define _nacl_dir std::string("/persistent/manaplus")
75
#endif  // defined __native_client__
76
77
#ifdef ANDROID
78
#ifdef USE_SDL2
79
80
int loadingProgressCounter = 1;
81
82
static void updateProgress(int cnt)
83
{
84
    const int progress = cnt + loadingProgressCounter;
85
    const int h = mainGraphics->mHeight;
86
    mainGraphics->setColor(Color(255, 255, 255, 255));
87
    const int maxSize = mainGraphics->mWidth - 100;
88
    const int width = maxSize * progress / 50;
89
    mainGraphics->fillRectangle(Rect(50, h - 100, width, 50));
90
    mainGraphics->updateScreen();
91
}
92
93
void Dirs::setProgress()
94
{
95
    loadingProgressCounter++;
96
    updateProgress(loadingProgressCounter);
97
}
98
99
static void resetProgress()
100
{
101
    loadingProgressCounter = 0;
102
    updateProgress(loadingProgressCounter);
103
}
104
105
void extractAssets()
106
{
107
    if (!getenv("APPDIR"))
108
    {
109
        logger->log("error: APPDIR is not set!");
110
        return;
111
    }
112
    const std::string fileName = pathJoin(getenv("APPDIR"),
113
        "data.zip");
114
    logger->log("Extracting asset into: " + fileName);
115
    uint8_t *buf = new uint8_t[1000000];
116
117
    FILE *const file = fopen(fileName.c_str(), "w");
118
    for (int f = 0; f < 100; f ++)
119
    {
120
        std::string part = strprintf("manaplus-data.zip%u%u",
121
            CAST_U32(f / 10),
122
            CAST_U32(f % 10));
123
        logger->log("testing asset: " + part);
124
        SDL_RWops *const rw = SDL_RWFromFile(part.c_str(), "r");
125
        if (rw)
126
        {
127
            const int size = SDL_RWsize(rw);
128
            int size2 = SDL_RWread(rw, buf, 1, size);
129
            logger->log("asset size: %d", size2);
130
            fwrite(buf, 1, size2, file);
131
            SDL_RWclose(rw);
132
            Dirs::setProgress();
133
        }
134
        else
135
        {
136
            break;
137
        }
138
    }
139
    fclose(file);
140
141
    const std::string fileName2 = pathJoin(getenv("APPDIR"),
142
        "locale.zip");
143
    FILE *const file2 = fopen(fileName2.c_str(), "w");
144
    SDL_RWops *const rw = SDL_RWFromFile("manaplus-locale.zip", "r");
145
    if (rw)
146
    {
147
        const int size = SDL_RWsize(rw);
148
        int size2 = SDL_RWread(rw, buf, 1, size);
149
        fwrite(buf, 1, size2, file2);
150
        SDL_RWclose(rw);
151
        Dirs::setProgress();
152
    }
153
    fclose(file2);
154
155
    delete [] buf;
156
}
157
158
#endif  // USE_SDL2
159
#endif  // ANDROID
160
161
void Dirs::updateDataPath()
162
{
163
    if (settings.options.dataPath.empty()
164
        && !branding.getStringValue("dataPath").empty())
165
    {
166
        if (isRealPath(branding.getStringValue("dataPath")))
167
        {
168
            settings.options.dataPath = branding.getStringValue("dataPath");
169
        }
170
        else
171
        {
172
            settings.options.dataPath = pathJoin(branding.getDirectory(),
173
                branding.getStringValue("dataPath"));
174
        }
175
        settings.options.skipUpdate = true;
176
    }
177
}
178
179
void Dirs::extractDataDir()
180
{
181
#if defined(ANDROID) && defined(USE_SDL2)
182
    Files::setCopyCallBack(&updateProgress);
183
    resetProgress();
184
    extractAssets();
185
186
    const std::string zipName = pathJoin(getenv("APPDIR"), "data.zip");
187
    const std::string dirName = pathJoin(getenv("APPDIR"), "data");
188
    VirtFs::mountZip2(zipName,
189
        "data",
190
        Append_false);
191
    VirtFs::mountZip2(zipName,
192
        "data/perserver/default",
193
        Append_false);
194
    Files::extractLocale();
195
#endif  // defined(ANDROID) && defined(USE_SDL2)
196
}
197
198
void Dirs::mountDataDir()
199
{
200
    VirtFs::mountDirSilent(PKG_DATADIR "data/perserver/default",
201
        Append_false);
202
    VirtFs::mountDirSilent("data/perserver/default",
203
        Append_false);
204
205
#if defined __APPLE__
206
    CFBundleRef mainBundle = CFBundleGetMainBundle();
207
    CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL(mainBundle);
208
    char path[PATH_MAX];
209
    if (!CFURLGetFileSystemRepresentation(resourcesURL,
210
        TRUE,
211
        reinterpret_cast<uint8_t*>(path),
212
        PATH_MAX))
213
    {
214
        fprintf(stderr, "Can't find Resources directory\n");
215
    }
216
    CFRelease(resourcesURL);
217
    std::string path2 = pathJoin(path, "data");
218
    VirtFs::mountDir(pathJoin(path2, "perserver/default"), Append_false);
219
    VirtFs::mountDir(path2, Append_false);
220
// possible this need for support run client from dmg images.
221
//    mPackageDir = path;
222
#endif  // defined __APPLE__
223
224
    VirtFs::mountDirSilent(PKG_DATADIR "data", Append_false);
225
    setPackageDir(PKG_DATADIR "data");
226
    VirtFs::mountDirSilent("data", Append_false);
227
228
#ifdef ANDROID
229
#ifdef USE_SDL2
230
    if (getenv("APPDIR"))
231
    {
232
        const std::string appDir = getenv("APPDIR");
233
        VirtFs::mountDir(appDir + "/data", Append_false);
234
        VirtFs::mountDir(appDir + "/data/perserver/default",
235
            Append_false);
236
    }
237
#endif  // USE_SDL2
238
#endif  // ANDROID
239
240
#if defined __native_client__
241
    VirtFs::mountZip("/http/data.zip", Append_false);
242
    VirtFs::mountZip2("/http/data.zip",
243
        "perserver/default",
244
        Append_false);
245
#endif  // defined __native_client__
246
247
#ifndef WIN32
248
    // Add branding/data to VirtFS search path
249
    if (!settings.options.brandingPath.empty())
250
    {
251
        std::string path = settings.options.brandingPath;
252
253
        // Strip blah.manaplus from the path
254
        const int loc = CAST_S32(path.find_last_of('/'));
255
256
        if (loc > 0)
257
        {
258
            VirtFs::mountDir(path.substr(
259
                0, loc + 1).append("data"),
260
                Append_false);
261
        }
262
    }
263
#endif  // WIN32
264
}
265
266
291
void Dirs::initRootDir()
267
{
268
582
    settings.rootDir = VirtFs::getBaseDir();
269
582
    const std::string portableName = settings.rootDir + "portable.xml";
270
    struct stat statbuf;
271
272

582
    if (stat(portableName.c_str(), &statbuf) == 0 &&
273
        S_ISREG(statbuf.st_mode))
274
    {
275
        std::string dir;
276
        Configuration portable;
277
        portable.init(portableName,
278
            UseVirtFs_false,
279
            SkipError_false);
280
281
        if (settings.options.brandingPath.empty())
282
        {
283
            branding.init(portableName,
284
                UseVirtFs_false,
285
                SkipError_false);
286
            setBrandingDefaults(branding);
287
        }
288
289
        logger->log("Portable file: %s", portableName.c_str());
290
291
        if (settings.options.localDataDir.empty())
292
        {
293
            dir = portable.getValue("dataDir", "");
294
            if (!dir.empty())
295
            {
296
                settings.options.localDataDir = settings.rootDir + dir;
297
                logger->log("Portable data dir: %s",
298
                    settings.options.localDataDir.c_str());
299
            }
300
        }
301
302
        if (settings.options.configDir.empty())
303
        {
304
            dir = portable.getValue("configDir", "");
305
            if (!dir.empty())
306
            {
307
                settings.options.configDir = settings.rootDir + dir;
308
                logger->log("Portable config dir: %s",
309
                    settings.options.configDir.c_str());
310
            }
311
        }
312
313
        if (settings.options.screenshotDir.empty())
314
        {
315
            dir = portable.getValue("screenshotDir", "");
316
            if (!dir.empty())
317
            {
318
                settings.options.screenshotDir = settings.rootDir + dir;
319
                logger->log("Portable screenshot dir: %s",
320
                    settings.options.screenshotDir.c_str());
321
            }
322
        }
323
    }
324
291
}
325
326
/**
327
 * Initializes the home directory. On UNIX and FreeBSD, ~/.mana is used. On
328
 * Windows and other systems we use the current working directory.
329
 */
330
291
void Dirs::initHomeDir()
331
{
332
291
    initLocalDataDir();
333
291
    initTempDir();
334
291
    initConfigDir();
335
291
}
336
337
291
void Dirs::initLocalDataDir()
338
{
339
291
    settings.localDataDir = settings.options.localDataDir;
340
341
291
    if (settings.localDataDir.empty())
342
    {
343
#ifdef __APPLE__
344
        // Use Application Directory instead of .mana
345
        settings.localDataDir = pathJoin(VirtFs::getUserDir(),
346
            "Library/Application Support",
347
            branding.getValue("appName", "ManaPlus"));
348
#elif defined __HAIKU__
349
        settings.localDataDir = pathJoin(VirtFs::getUserDir(),
350
           "config/cache/Mana");
351
#elif defined WIN32
352
        settings.localDataDir = getSpecialFolderLocation(CSIDL_LOCAL_APPDATA);
353
        if (settings.localDataDir.empty())
354
            settings.localDataDir = VirtFs::getUserDir();
355
        settings.localDataDir = pathJoin(settings.localDataDir,
356
            "Mana");
357
#elif defined __ANDROID__
358
        settings.localDataDir = pathJoin(getSdStoragePath(),
359
            branding.getValue("appShort", "ManaPlus"),
360
            "local");
361
#elif defined __native_client__
362
        settings.localDataDir = pathJoin(_nacl_dir, "local");
363
#elif defined __SWITCH__
364
        settings.localDataDir = pathJoin(VirtFs::getUserDir(), "local");
365
#else  // __APPLE__
366
367

2328
        settings.localDataDir = pathJoin(VirtFs::getUserDir(),
368
291
            ".local/share/mana");
369
#endif  // __APPLE__
370
    }
371
372
291
    if (mkdir_r(settings.localDataDir.c_str()) != 0)
373
    {
374
        // TRANSLATORS: directory creation error
375
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
376
            "Exiting."), settings.localDataDir.c_str()));
377
    }
378
#ifdef USE_PROFILER
379
    Perfomance::init(pathJoin(settings.localDataDir, "profiler.log"));
380
#endif  // USE_PROFILER
381
291
}
382
383
291
void Dirs::initTempDir()
384
{
385
2037
    settings.tempDir = pathJoin(settings.localDataDir, "temp");
386
387
291
    if (mkdir_r(settings.tempDir.c_str()) != 0)
388
    {
389
        // TRANSLATORS: directory creation error
390
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
391
            "Exiting."), settings.tempDir.c_str()));
392
    }
393
//    ResourceManager::deleteFilesInDirectory(settings.tempDir);
394
291
}
395
396
291
void Dirs::initConfigDir()
397
{
398
291
    settings.configDir = settings.options.configDir;
399
400
291
    if (settings.configDir.empty())
401
    {
402
#ifdef __APPLE__
403
        settings.configDir = pathJoin(settings.localDataDir,
404
            branding.getValue("appShort", "mana"));
405
#elif defined __HAIKU__
406
        settings.configDir = pathJoin(VirtFs::getUserDir(),
407
           "config/settings/Mana",
408
           branding.getValue("appName", "ManaPlus"));
409
#elif defined WIN32
410
        settings.configDir = getSpecialFolderLocation(CSIDL_APPDATA);
411
        if (settings.configDir.empty())
412
        {
413
            settings.configDir = settings.localDataDir;
414
        }
415
        else
416
        {
417
            settings.configDir = pathJoin(settings.configDir,
418
                "mana",
419
                branding.getValue("appShort", "mana"));
420
        }
421
#elif defined __ANDROID__
422
        settings.configDir = pathJoin(getSdStoragePath(),
423
            branding.getValue("appShort", "ManaPlus"),
424
            "config");
425
#elif defined __native_client__
426
        settings.configDir = pathJoin(_nacl_dir, "config");
427
#elif defined __SWITCH__
428
        settings.configDir = pathJoin(VirtFs::getUserDir(), "config");
429
#else  // __APPLE__
430
431


2328
        settings.configDir = pathJoin(VirtFs::getUserDir(),
432
            ".config/mana",
433

2619
            branding.getValue("appShort", "mana"));
434
#endif  // __APPLE__
435
436
582
        logger->log("Generating config dir: " + settings.configDir);
437
    }
438
439
291
    if (mkdir_r(settings.configDir.c_str()) != 0)
440
    {
441
        // TRANSLATORS: directory creation error
442
        logger->error(strprintf(_("%s doesn't exist and can't be created! "
443
            "Exiting."), settings.configDir.c_str()));
444
    }
445
291
}
446
447
/**
448
 * Parse the update host and determine the updates directory
449
 * Then verify that the directory exists (creating if needed).
450
 */
451
void Dirs::initUpdatesDir()
452
{
453
    std::stringstream updates;
454
455
    // If updatesHost is currently empty, fill it from config file
456
    if (settings.updateHost.empty())
457
        settings.updateHost = config.getStringValue("updatehost");
458
    if (!checkPath(settings.updateHost))
459
        return;
460
461
    // Don't go out of range int he next check
462
    if (settings.updateHost.length() < 2)
463
    {
464
        if (settings.updatesDir.empty())
465
            settings.updatesDir = pathJoin("updates", settings.serverName);
466
        return;
467
    }
468
469
    const size_t sz = settings.updateHost.size();
470
    // Remove any trailing slash at the end of the update host
471
    if (settings.updateHost.at(sz - 1) == '/')
472
        settings.updateHost.resize(sz - 1);
473
474
    // Parse out any "http://" or "https://", and set the updates directory
475
    const size_t pos = settings.updateHost.find("://");
476
    if (pos != std::string::npos)
477
    {
478
        if (pos + 3 < settings.updateHost.length()
479
            && !settings.updateHost.empty())
480
        {
481
            updates << "updates/" << settings.updateHost.substr(pos + 3);
482
            settings.updatesDir = updates.str();
483
        }
484
        else
485
        {
486
            logger->log("Error: Invalid update host: %s",
487
                settings.updateHost.c_str());
488
            // TRANSLATORS: update server initialisation error
489
            errorMessage = strprintf(_("Invalid update host: %s."),
490
                settings.updateHost.c_str());
491
            client->setState(State::ERROR);
492
        }
493
    }
494
    else
495
    {
496
        logger->log1("Warning: no protocol was specified for the update host");
497
        updates << "updates/" << settings.updateHost;
498
        settings.updatesDir = updates.str();
499
    }
500
501
#ifdef WIN32
502
    if (settings.updatesDir.find(":") != std::string::npos)
503
        replaceAll(settings.updatesDir, ":", "_");
504
#endif  // WIN32
505
506
    const std::string updateDir("/" + settings.updatesDir);
507
508
    // Verify that the updates directory exists. Create if necessary.
509
    if (!VirtFs::isDirectory(updateDir))
510
    {
511
        if (!VirtFs::mkdir(updateDir))
512
        {
513
#if defined WIN32
514
            std::string newDir = pathJoin(settings.localDataDir,
515
                settings.updatesDir);
516
            if (!CreateDirectory(newDir.c_str(), nullptr) &&
517
                GetLastError() != ERROR_ALREADY_EXISTS)
518
            {
519
                logger->log("Error: %s can't be made, but doesn't exist!",
520
                            newDir.c_str());
521
                // TRANSLATORS: update server initialisation error
522
                errorMessage = _("Error creating updates directory!");
523
                client->setState(State::ERROR);
524
            }
525
#else  // defined WIN32
526
527
            logger->log("Error: %s/%s can't be made, but doesn't exist!",
528
                settings.localDataDir.c_str(),
529
                settings.updatesDir.c_str());
530
            // TRANSLATORS: update server initialisation error
531
            errorMessage = _("Error creating updates directory!");
532
            client->setState(State::ERROR);
533
#endif  // defined WIN32
534
        }
535
    }
536
    const std::string updateLocal = pathJoin(updateDir, "local");
537
    const std::string updateFix = pathJoin(updateDir, "fix");
538
    if (!VirtFs::isDirectory(updateLocal))
539
        VirtFs::mkdir(updateLocal);
540
    if (!VirtFs::isDirectory(updateFix))
541
        VirtFs::mkdir(updateFix);
542
}
543
544
void Dirs::initScreenshotDir()
545
{
546
    if (!settings.options.screenshotDir.empty())
547
    {
548
        settings.screenshotDir = settings.options.screenshotDir;
549
        if (mkdir_r(settings.screenshotDir.c_str()) != 0)
550
        {
551
            logger->log(strprintf(
552
                // TRANSLATORS: directory creation error
553
                _("Error: %s doesn't exist and can't be created! "
554
                "Exiting."), settings.screenshotDir.c_str()));
555
        }
556
    }
557
    else if (settings.screenshotDir.empty())
558
    {
559
#ifdef __native_client__
560
        settings.screenshotDir = pathJoin(_nacl_dir, "screenshots/");
561
#else  // __native_client__
562
        settings.screenshotDir = decodeBase64String(
563
            config.getStringValue("screenshotDirectory3"));
564
        if (settings.screenshotDir.empty())
565
        {
566
#ifdef __ANDROID__
567
            settings.screenshotDir = getSdStoragePath()
568
                + std::string("/images");
569
570
            if (mkdir_r(settings.screenshotDir.c_str()))
571
            {
572
                logger->log(strprintf(
573
                    // TRANSLATORS: directory creation error
574
                    _("Error: %s doesn't exist and can't be created! "
575
                    "Exiting."), settings.screenshotDir.c_str()));
576
            }
577
#else  // ANDROID
578
            settings.screenshotDir = getPicturesDir();
579
#endif  // ANDROID
580
            if (config.getBoolValue("useScreenshotDirectorySuffix"))
581
            {
582
                const std::string configScreenshotSuffix =
583
                    branding.getValue("screenshots", "ManaPlus");
584
585
                if (!configScreenshotSuffix.empty())
586
                {
587
                    settings.screenshotDir = pathJoin(settings.screenshotDir,
588
                        configScreenshotSuffix);
589
                }
590
            }
591
            config.setValue("screenshotDirectory3",
592
                encodeBase64String(settings.screenshotDir));
593
        }
594
#endif  // __native_client__
595
    }
596
    logger->log("screenshotDirectory: " + settings.screenshotDir);
597
}
598
599
void Dirs::initUsersDir()
600
{
601
    settings.usersDir = settings.serverConfigDir + "/users/";
602
    if (mkdir_r(settings.usersDir.c_str()) != 0)
603
    {
604
        // TRANSLATORS: directory creation error
605
        logger->error(strprintf(_("%s doesn't exist and can't be created!"),
606
            settings.usersDir.c_str()));
607
    }
608
609
    settings.npcsDir = settings.serverConfigDir + "/npcs/";
610
    if (mkdir_r(settings.npcsDir.c_str()) != 0)
611
    {
612
        // TRANSLATORS: directory creation error
613
        logger->error(strprintf(_("%s doesn't exist and can't be created!"),
614
            settings.npcsDir.c_str()));
615
    }
616
617
    settings.usersIdDir = settings.serverConfigDir + "/usersid/";
618
    if (mkdir_r(settings.usersIdDir.c_str()) != 0)
619
    {
620
        // TRANSLATORS: directory creation error
621
        logger->error(strprintf(_("%s doesn't exist and can't be created!"),
622
            settings.usersIdDir.c_str()));
623
    }
624
}