• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Return-To-The-Roots / libsiedler2 / 14537997660

18 Apr 2025 04:06PM UTC coverage: 81.697% (-0.3%) from 82.012%
14537997660

push

github

Flamefire
Require Boost 1.73

An undefined `PTHREAD_STACK_MIN` causes a compile failure otherwise.

5200 of 6365 relevant lines covered (81.7%)

4237.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.45
/src/libsiedler2.cpp
1
// Copyright (C) 2005 - 2021 Settlers Freaks (sf-team at siedler25.org)
2
// Copyright (C) 2005 - 2021 Settlers Freaks <sf-team at siedler25.org>
3
//
4
// SPDX-License-Identifier: GPL-2.0-or-later
5

6
#include "libsiedler2.h"
7
#include "Archiv.h"
8
#include "ArchivItem_Bitmap.h"
9
#include "ArchivItem_BitmapBase.h"
10
#include "ArchivItem_Bitmap_Player.h"
11
#include "ArchivItem_Font.h"
12
#include "ErrorCodes.h"
13
#include "PixelBufferBGRA.h"
14
#include "StandardAllocator.h"
15
#include "prototypen.h"
16
#include "s25util/StringConversion.h"
17
#include "s25util/Tokenizer.h"
18
#include "s25util/strAlgos.h"
19
#include <boost/filesystem.hpp>
20
#include <boost/numeric/conversion/cast.hpp>
21
#include <algorithm>
22
#include <iostream>
23
#include <memory>
24
#include <stdexcept>
25

26
namespace bfs = boost::filesystem;
27
using boost::bad_numeric_cast;
28
using boost::numeric_cast;
29

30
/** @mainpage libsiedler2
31
 *
32
 *  libsiedler2 ist eine Grafikbibliothek zum einfachen Zugriff auf
33
 *  die Dateiformate von Siedler II.
34
 *
35
 *  Sie kann natürlich auch verschiedene andere Formate lesen und schreiben,
36
 *  damit man die verschiedenen Formate hin und her konvertieren kann.
37
 *
38
 */
39

40
namespace libsiedler2 {
41

42
/**
43
 *  Das gewählte Texturformat.
44

45
 *
46
 *  @ingroup textureformat
47
 */
48
static TextureFormat texturformat;
49

50
/**
51
 *  Der gesetzte Item-Allokator.
52
 */
53
static IAllocator* allocator = nullptr;
54

55
} // namespace libsiedler2
56

57
namespace {
58
struct Initializer
59
{
60
    Initializer()
1✔
61
    {
62
        assert(libsiedler2::allocator == nullptr);
1✔
63
        libsiedler2::setAllocator(new libsiedler2::StandardAllocator());
1✔
64
        libsiedler2::setGlobalTextureFormat(libsiedler2::TextureFormat::Original);
1✔
65
    }
1✔
66
    ~Initializer() { libsiedler2::setAllocator(nullptr); }
1✔
67
};
68
Initializer initializer__;
69
} // namespace
70

71
/** @namespace libsiedler2
72
 *
73
 *  @brief Hauptnamensraum von @p libsiedler2
74
 *
75
 *  Enthält alle Klassen und exportierten Funktionen von @p libsiedler2.
76
 */
77

78
namespace libsiedler2 {
79

80
/**
81
 *  Setzt das verwendete Texturausgabeformat.
82
 *
83
 *  @param[in] format gewünschte Format
84
 *
85
 *  @return liefert das vorherige Texturausgabeformat zurück
86
 */
87
TextureFormat setGlobalTextureFormat(TextureFormat format)
19✔
88
{
89
    // altes Texturformat sichern
90
    TextureFormat old = texturformat;
19✔
91

92
    // Neues setzen
93
    texturformat = format;
19✔
94

95
    // und Altes zurückliefern
96
    return old;
19✔
97
}
98

99
/**
100
 *  liefert das verwendete Texturausgabeformat.
101
 *
102
 *  @return liefert das Texturausgabeformat zurück
103
 */
104
TextureFormat getGlobalTextureFormat()
118✔
105
{
106
    // Aktuelles zurückliefern
107
    return texturformat;
118✔
108
}
109

110
const IAllocator& getAllocator()
336✔
111
{
112
    return *allocator;
336✔
113
}
114

115
/**
116
 *  Setzt den Item-Allocator.
117
 *
118
 *  @param[in] new_allocator Der neue Item-Allokator
119
 */
120
void setAllocator(IAllocator* newAllocator)
2✔
121
{
122
    delete allocator;
2✔
123
    allocator = newAllocator;
2✔
124
}
2✔
125

126
/**
127
 *  Lädt die Datei im Format ihrer Endung.
128
 *
129
 *  @param[in]  filepath    Dateiname der Datei
130
 *  @param[out] items   Archiv-Struktur, welche gefüllt wird
131
 *  @param[in]  palette Palette, welche benutzt werden soll
132
 *
133
 *  @return Null bei Erfolg, ein Wert ungleich Null bei Fehler
134
 */
135
int Load(const boost::filesystem::path& filepath, Archiv& items, const ArchivItem_Palette* palette)
152✔
136
{
137
    if(filepath.empty())
152✔
138
        return ErrorCode::INVALID_BUFFER;
×
139

140
    if(!filepath.has_extension())
152✔
141
        return ErrorCode::UNSUPPORTED_FORMAT;
×
142
    const std::string extension = s25util::toLower(filepath.extension().string().substr(1));
456✔
143

144
    int ret = ErrorCode::UNSUPPORTED_FORMAT;
152✔
145

146
    try
147
    {
148
        // Datei laden
149
        if(extension == "act")
152✔
150
            ret = loader::LoadACT(filepath, items);
30✔
151
        else if(extension == "bbm")
122✔
152
            ret = loader::LoadBBM(filepath, items);
1✔
153
        else if(extension == "bmp")
121✔
154
            ret = loader::LoadBMP(filepath, items, palette);
36✔
155
        else if(extension == "bob")
85✔
156
            ret = loader::LoadBOB(filepath, items, palette);
×
157
        else if(extension == "dat" || extension == "idx")
85✔
158
        {
159
            ret = loader::LoadDATIDX(filepath, items, palette);
×
160
            if(ret == ErrorCode::WRONG_HEADER && extension == "dat")
×
161
                ret = loader::LoadSND(filepath, items);
×
162
        } else if(extension == "lbm")
85✔
163
            ret = loader::LoadLBM(filepath, items);
13✔
164
        else if(extension == "lst")
72✔
165
            ret = loader::LoadLST(filepath, items, palette);
54✔
166
        else if(extension == "swd" || extension == "wld")
18✔
167
            ret = loader::LoadMAP(filepath, items);
4✔
168
        else if(extension == "ger" || extension == "eng")
14✔
169
            ret = loader::LoadTXT(filepath, items, true);
4✔
170
        else if(extension == "ini")
10✔
171
            ret = loader::LoadINI(filepath, items);
3✔
172
        else if(extension == "ogg" || extension == "wav" || extension == "mid" || extension == "midi"
13✔
173
                || extension == "xmi")
13✔
174
            ret = loader::LoadSND(filepath, items);
5✔
175
        else if(extension == "links")
2✔
176
            ret = loader::LoadTXT(filepath, items, false);
×
177
        else if(extension == "txt")
2✔
178
        {
179
            const bfs::path filename = filepath.stem();
4✔
180
            const std::string ext2 =
181
              s25util::toLower(filename.has_extension() ? filename.extension().string().substr(1) : filename.string());
6✔
182
            if(ext2 == "paletteanims")
2✔
183
                ret = loader::LoadPaletteAnim(filepath, items);
1✔
184
            else if(ext2 == "palette")
1✔
185
                ret = loader::LoadTxtPalette(filepath, items);
×
186
            else
187
                ret = loader::LoadTXT(filepath, items, false);
1✔
188
        } else
189
            std::cerr << "Unsupported extension: " << extension << std::endl;
×
190
    } catch(std::exception& error)
×
191
    {
192
        std::cerr << "Error while reading: " << error.what() << std::endl;
×
193
        // Mostly error on reading (e.g. unexpected end of filepath)
194
        return ErrorCode::CUSTOM;
×
195
    }
196

197
    return ret;
152✔
198
}
199

200
int LoadFolder(std::vector<FileEntry> folderInfos, Archiv& items, const ArchivItem_Palette* palette)
1✔
201
{
202
    std::sort(folderInfos.begin(), folderInfos.end());
1✔
203
    libsiedler2::PixelBufferBGRA buffer(1000, 1000);
5✔
204
    for(const FileEntry& entry : folderInfos)
7✔
205
    {
206
        // Ignore
207
        if(entry.bobtype == BobType::Unset)
6✔
208
            continue;
×
209
        std::unique_ptr<ArchivItem> newItem;
×
210
        if(entry.bobtype == BobType::Font)
6✔
211
        {
212
            auto font = getAllocator().create<ArchivItem_Font>(BobType::Font);
×
213
            font->isUnicode = s25util::toLower(entry.filePath.extension().string()) == ".fonx";
×
214
            try
215
            {
216
                font->setDx(numeric_cast<uint8_t>(entry.nx));
×
217
                font->setDy(numeric_cast<uint8_t>(entry.ny));
×
218
            } catch(const bad_numeric_cast&)
×
219
            {
220
                return ErrorCode::CUSTOM + 1;
×
221
            }
222
            int ec;
223
            if(bfs::is_directory(entry.filePath))
×
224
                ec = LoadFolder(ReadFolderInfo(entry.filePath), *font, palette);
×
225
            else
226
                ec = Load(entry.filePath, *font, palette);
×
227
            if(ec)
×
228
                return ec;
×
229

230
            newItem = std::move(font);
×
231
        } else if(entry.bobtype != BobType::None)
6✔
232
        {
233
            const ArchivItem_Palette* curPal = palette;
5✔
234
            if(entry.nr >= 0)
5✔
235
            {
236
                if(static_cast<unsigned>(entry.nr) < items.size() && items[entry.nr]->getBobType() == BobType::Palette)
3✔
237
                    curPal = static_cast<const ArchivItem_Palette*>(items[entry.nr]);
×
238
            } else if(!items.empty() && items[items.size() - 1u]
4✔
239
                      && items[items.size() - 1u]->getBobType() == BobType::Palette)
4✔
240
                curPal = static_cast<const ArchivItem_Palette*>(items[items.size() - 1u]);
×
241
            Archiv tmpItems;
5✔
242
            if(int ec = Load(entry.filePath, tmpItems, curPal))
5✔
243
                return ec;
×
244
            if(entry.bobtype == BobType::BitmapPlayer || entry.bobtype == BobType::Bitmap
5✔
245
               || entry.bobtype == BobType::BitmapRLE || entry.bobtype == BobType::BitmapShadow)
2✔
246
            {
247
                if(tmpItems.size() != 1)
4✔
248
                    return ErrorCode::UNSUPPORTED_FORMAT;
×
249

250
                if(entry.bobtype == tmpItems[0]->getBobType())
4✔
251
                {
252
                    // No conversion->Just take it
253
                    newItem = tmpItems.release(0);
2✔
254
                } else
255
                {
256
                    auto* bmp = dynamic_cast<ArchivItem_BitmapBase*>(tmpItems[0]);
2✔
257
                    if(!bmp)
2✔
258
                        return ErrorCode::UNSUPPORTED_FORMAT;
×
259
                    auto convertedBmp = getAllocator().create<ArchivItem_BitmapBase>(entry.bobtype);
2✔
260
                    std::fill(buffer.begin(), buffer.end(), ColorBGRA());
8✔
261
                    if(bmp->getBobType() == BobType::BitmapPlayer)
2✔
262
                    {
263
                        auto* bmpPlayer = dynamic_cast<ArchivItem_Bitmap_Player*>(bmp);
×
264
                        assert(bmpPlayer);
×
265
                        if(int ec = bmpPlayer->print(buffer, curPal))
×
266
                            return ec;
×
267
                    } else
268
                    {
269
                        auto* bmpBase = dynamic_cast<baseArchivItem_Bitmap*>(bmp);
2✔
270
                        assert(bmpBase);
2✔
271
                        if(int ec = bmpBase->print(buffer))
2✔
272
                            return ec;
×
273
                    }
274

275
                    switch(entry.bobtype)
2✔
276
                    {
277
                        case BobType::BitmapRLE:
1✔
278
                        case BobType::BitmapShadow:
279
                        case BobType::Bitmap:
280
                        {
281
                            auto* bmpBase = dynamic_cast<baseArchivItem_Bitmap*>(convertedBmp.get());
1✔
282
                            assert(bmpBase);
1✔
283
                            if(int ec = bmpBase->create(bmp->getWidth(), bmp->getHeight(), buffer)) //-V522
1✔
284
                                return ec;
×
285
                            break;
1✔
286
                        }
287
                        case BobType::BitmapPlayer:
1✔
288
                        {
289
                            auto* bmpPl = dynamic_cast<ArchivItem_Bitmap_Player*>(convertedBmp.get());
1✔
290
                            assert(bmpPl);
1✔
291
                            if(int ec = bmpPl->create(bmp->getWidth(), bmp->getHeight(), buffer, curPal)) //-V522
1✔
292
                                return ec;
×
293
                        }
294
                        break;
1✔
295
                        default: return ErrorCode::UNSUPPORTED_FORMAT;
×
296
                    }
297
                    newItem = std::move(convertedBmp);
2✔
298
                }
299
                auto* bmp = static_cast<ArchivItem_BitmapBase*>(newItem.get());
4✔
300
                try
301
                {
302
                    bmp->setNx(numeric_cast<int16_t>(entry.nx));
4✔
303
                    bmp->setNy(numeric_cast<int16_t>(entry.ny));
4✔
304
                } catch(const bad_numeric_cast&)
×
305
                {
306
                    return ErrorCode::CUSTOM + 1;
×
307
                }
308
                if(curPal && !bmp->getPalette())
4✔
309
                    bmp->setPaletteCopy(*curPal);
4✔
310
            } else if(entry.bobtype == BobType::PaletteAnim)
1✔
311
            {
312
                for(unsigned i = 0; i < tmpItems.size(); i++)
×
313
                {
314
                    if(!tmpItems[i])
×
315
                        continue;
×
316
                    if(items[i])
×
317
                        return ErrorCode::UNSUPPORTED_FORMAT;
×
318
                    if(i >= items.size())
×
319
                        items.alloc_inc(i - items.size() + 1);
×
320
                    items.set(i, tmpItems.release(i));
×
321
                }
322
                continue;
×
323
            } else
324
            {
325
                // todo: andere typen als pal und bmp haben evtl mehr items!
326
                if(tmpItems.size() != 1)
1✔
327
                    return ErrorCode::UNSUPPORTED_FORMAT;
×
328

329
                newItem = tmpItems.release(0);
1✔
330
            }
331
        }
332
        if(newItem)
6✔
333
            newItem->setName(entry.name);
5✔
334
        // had the filename a number? then set it to the corresponding item.
335
        if(entry.nr >= 0)
6✔
336
        {
337
            if(static_cast<unsigned>(entry.nr) >= items.size())
4✔
338
                items.alloc_inc(entry.nr - items.size() + 1);
4✔
339
            items.set(entry.nr, std::move(newItem));
4✔
340
        } else
341
            items.push(std::move(newItem));
2✔
342
    }
343
    return ErrorCode::NONE;
1✔
344
}
345

346
/**
347
 *  Schreibt die Datei im Format ihrer Endung.
348
 *
349
 *  @param[in] filepath    Dateiname der Datei
350
 *  @param[in] items   Archiv-Struktur, von welcher gelesen wird
351
 *  @param[in] palette Palette, welche benutzt werden soll
352
 *
353
 *  @return Null bei Erfolg, ein Wert ungleich Null bei Fehler
354
 */
355
int Write(const boost::filesystem::path& filepath, const Archiv& items, const ArchivItem_Palette* palette)
114✔
356
{
357
    if(filepath.empty())
114✔
358
        return ErrorCode::INVALID_BUFFER;
×
359

360
    if(!filepath.has_extension())
114✔
361
        return ErrorCode::UNSUPPORTED_FORMAT;
×
362
    const std::string extension = s25util::toLower(filepath.extension().string().substr(1));
342✔
363

364
    int ret = ErrorCode::UNSUPPORTED_FORMAT;
114✔
365

366
    try
367
    {
368
        // Datei schreiben
369
        if(extension == "act")
114✔
370
            ret = loader::WriteACT(filepath, items);
1✔
371
        else if(extension == "bbm")
113✔
372
            ret = loader::WriteBBM(filepath, items);
1✔
373
        else if(extension == "bmp")
112✔
374
            ret = loader::WriteBMP(filepath, items, palette);
35✔
375
        else if(extension == "lst")
77✔
376
            ret = loader::WriteLST(filepath, items, palette);
51✔
377
        else if(extension == "swd" || extension == "wld")
26✔
378
            ret = loader::WriteMAP(filepath, items);
3✔
379
        else if(extension == "ger" || extension == "eng")
23✔
380
            ret = loader::WriteTXT(filepath, items, true);
4✔
381
        else if(extension == "ini")
19✔
382
            ret = loader::WriteINI(filepath, items);
1✔
383
        else if(extension == "ogg" || extension == "wav" || extension == "mid" || extension == "midi"
35✔
384
                || extension == "xmi")
35✔
385
            ret = loader::WriteSND(filepath, items);
5✔
386
        else if(extension == "lbm")
13✔
387
            ret = loader::WriteLBM(filepath, items, palette);
12✔
388
        else if(extension == "txt")
1✔
389
            ret = loader::WritePaletteAnim(filepath, items);
1✔
390
        else
391
            std::cerr << "Unsupported extension: " << extension << std::endl;
×
392
    } catch(std::exception& error)
×
393
    {
394
        std::cerr << "Error while writing: " << error.what() << std::endl;
×
395
        // Mostly error on write to filepath
396
        return ErrorCode::CUSTOM;
×
397
    }
398

399
    return ret;
114✔
400
}
401

402
namespace {
403
    unsigned hexToInt(const std::string& hexStr)
×
404
    {
405
        s25util::ClassicImbuedStream<std::istringstream> sIn(hexStr);
×
406
        unsigned tmp;
407
        sIn >> std::hex >> tmp;
×
408
        if(!sIn || !sIn.eof())
×
409
            throw std::runtime_error("Invalid hex number 0x" + hexStr);
×
410
        return tmp;
×
411
    }
412
} // namespace
413

414
std::vector<FileEntry> ReadFolderInfo(const boost::filesystem::path& folderPath)
2✔
415
{
416
    std::vector<FileEntry> entries;
2✔
417
    for(const auto& it : bfs::directory_iterator(folderPath))
14✔
418
    {
419
        if(!bfs::is_regular_file(it.status()) && !bfs::is_directory(it.status()))
12✔
420
            continue;
×
421

422
        bfs::path curPath = it.path();
24✔
423
        curPath.make_preferred();
12✔
424

425
        FileEntry filepath(curPath);
24✔
426

427
        const std::string fileName = s25util::toLower(curPath.filename().string());
36✔
428
        std::vector<std::string> wf = Tokenizer(fileName, ".").explode();
36✔
429

430
        if(wf.back() == "fon" || wf.back() == "fonx")
12✔
431
            filepath.bobtype = BobType::Font;
×
432
        else if(wf.back() == "bmp")
12✔
433
            filepath.bobtype = BobType::Bitmap;
8✔
434
        else if(wf.back() == "bbm" || wf.back() == "act")
4✔
435
            filepath.bobtype = BobType::Palette;
×
436
        else if(wf.back() == "txt" || wf.back() == "ger" || wf.back() == "eng" || wf.back() == "links")
4✔
437
            filepath.bobtype = BobType::Text;
2✔
438
        else if(wf.back() == "empty")
2✔
439
            filepath.bobtype = BobType::None;
2✔
440
        else if(wf.back() == "midi" || wf.back() == "xmi" || wf.back() == "wav")
×
441
            filepath.bobtype = BobType::Sound;
×
442
        if(filepath.bobtype != BobType::Unset)
12✔
443
            wf.pop_back();
12✔
444

445
        std::string sNr = wf.empty() ? "" : wf.front();
24✔
446
        if(sNr.substr(0, 2) == "u+" || sNr.substr(0, 2) == "0x") // Allow Unicode filepath names (e.g. U+1234)
12✔
447
        {
448
            filepath.nr = hexToInt(sNr.substr(2));
×
449
            wf.erase(wf.begin());
×
450
        } else if(s25util::tryFromStringClassic(sNr, filepath.nr))
12✔
451
            wf.erase(wf.begin());
8✔
452
        else
453
            filepath.nr = -1;
4✔
454

455
        for(const std::string& part : wf)
30✔
456
        {
457
            if(part == "rle")
18✔
458
                filepath.bobtype = BobType::BitmapRLE;
2✔
459
            else if(part == "player")
16✔
460
                filepath.bobtype = BobType::BitmapPlayer;
2✔
461
            else if(part == "shadow")
14✔
462
                filepath.bobtype = BobType::BitmapShadow;
×
463
            else if(part == "paletteanims")
14✔
464
                filepath.bobtype = BobType::PaletteAnim;
×
465
            else if(part == "palette")
14✔
466
                filepath.bobtype = BobType::Palette;
×
467

468
            else if(part.substr(0, 2) == "nx" || part.substr(0, 2) == "dx")
14✔
469
                filepath.nx = s25util::fromStringClassic<int>(part.substr(2));
4✔
470
            else if(part.substr(0, 2) == "ny" || part.substr(0, 2) == "dy")
10✔
471
                filepath.ny = s25util::fromStringClassic<int>(part.substr(2));
4✔
472
            else
473
                filepath.name += (filepath.name.empty() ? "" : ".") + part;
6✔
474
        }
475

476
        entries.push_back(filepath);
12✔
477
    }
478
    return entries;
2✔
479
}
480

481
} // namespace libsiedler2
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc