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

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

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


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

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